Merge branch '208-p0-auth-05-get-started-screen' of github.com:Oloodi/krow-workforce into 208-p0-auth-05-get-started-screen
This commit is contained in:
@@ -7,8 +7,9 @@ KROW is a comprehensive workforce management platform designed to streamline ope
|
||||
### 📦 Apps (`/apps`)
|
||||
These are the production-ready applications for our users:
|
||||
- **`web-dashboard/`**: The primary React/Vite dashboard for Admin, Vendors, and Clients.
|
||||
- **`mobile-client/`**: Flutter application for final clients to manage orders and billing.
|
||||
- **`mobile-staff/`**: Flutter application for staff members (scheduling, clock-in/out, earnings).
|
||||
- **`mobile/`**: Flutter applications for client and staff.
|
||||
- `client`: The application for final clients to manage orders and billing.
|
||||
- `staff`: The application for staff members (scheduling, clock-in/out, earnings).
|
||||
|
||||
### ⚙️ Backend (`/backend`)
|
||||
The core data engine powering all applications:
|
||||
|
||||
1
apps/mobile-client/.keep
Normal file
1
apps/mobile-client/.keep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
apps/mobile-staff/.keep
Normal file
1
apps/mobile-staff/.keep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
68
apps/mobile/README.md
Normal file
68
apps/mobile/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# KROW Workforce Mobile 📱
|
||||
|
||||
This folder holds the mobile app code for the KROW Workforce apps.
|
||||
This project uses [Melos](https://melos.invertase.dev/) to manage multiple Flutter packages and applications.
|
||||
|
||||
## 📂 Project Structure
|
||||
|
||||
The project is organized into modular packages to ensure separation of concerns and maintainability.
|
||||
|
||||
- **`apps/`**: Main application entry points.
|
||||
- `client`: The application for businesses/clients.
|
||||
- `staff`: The application for workforce/staff.
|
||||
- `design_system_viewer`: A gallery of our design system components.
|
||||
- **`packages/`**: Shared logic and feature modules.
|
||||
- `features/`: UI and business logic for specific features (e.g., Auth, Home, Hubs).
|
||||
- `features/client`: Client specific features.
|
||||
- `features/staff`: Staff specific features.
|
||||
- `design_system/`: Shared UI components, tokens (colors, spacing), and core widgets.
|
||||
- `domain/`: Shared business entities and repository interfaces.
|
||||
- `data_connect/`: Data access layer (Mocks and Firebase Data Connect SDK).
|
||||
- `core_localization/`: Internationalization using Slang.
|
||||
- `core/`: Base utilities and common logic.
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### 1. Prerequisites
|
||||
Ensure you have the Flutter SDK installed and configured.
|
||||
|
||||
### 2. Initial Setup
|
||||
Run the following command from the **project root** to install Melos, bootstrap all packages, and generate localization files:
|
||||
|
||||
```bash
|
||||
# Using Makefile
|
||||
make mobile-install
|
||||
# Using Melos
|
||||
melos bootstrap
|
||||
```
|
||||
|
||||
### 3. Running the Apps
|
||||
You can run the applications using Melos scripts or through the `Makefile`:
|
||||
|
||||
#### Client App
|
||||
```bash
|
||||
# Using Melos
|
||||
melos run start:client -d android # or ios
|
||||
# Using Makefile
|
||||
make mobile-client-dev-android
|
||||
```
|
||||
|
||||
#### Staff App
|
||||
```bash
|
||||
# Using Melos
|
||||
melos run start:staff -d android # or ios
|
||||
# Using Makefile
|
||||
make mobile-staff-dev-android
|
||||
```
|
||||
|
||||
## 🛠 Useful Commands
|
||||
|
||||
- **Bootstrap**: `melos bootstrap` (Installs all dependencies)
|
||||
- **Generate All**: `melos run gen:all` (Localization + Code Generation)
|
||||
- **Analyze**: `melos run analyze:all`
|
||||
- **Help**: `melos run info` (Shows all available custom scripts)
|
||||
|
||||
## 🏗 Coding Principles
|
||||
- **Clean Architecture**: We strictly follow Domain-Driven Design and Clean Architecture.
|
||||
- **Modularity**: Every feature should be its own package in `packages/features/`. Client and staff specific features should be in their respective packages.
|
||||
- **Consistency**: Use the `design_system` package for all UI elements to ensure a premium, unified look.
|
||||
26
apps/mobile/analysis_options.yaml
Normal file
26
apps/mobile/analysis_options.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "**/dataconnect_generated/**"
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "**/*.config.dart"
|
||||
errors:
|
||||
# Set the severity of the always_specify_types rule to warning as requested.
|
||||
always_specify_types: warning
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# Every variable should have an explicit type.
|
||||
- always_specify_types
|
||||
|
||||
# Additional common best practices not always enforced by default
|
||||
- prefer_const_constructors
|
||||
- prefer_const_declarations
|
||||
- prefer_final_locals
|
||||
- avoid_void_async
|
||||
- unawaited_futures
|
||||
- sort_constructors_first
|
||||
- camel_case_types
|
||||
- library_private_types_in_public_api
|
||||
@@ -1 +1 @@
|
||||
include: ../../analytics_options.yaml
|
||||
include: ../../analysis_options.yaml
|
||||
@@ -9,6 +9,8 @@ import 'package:client_authentication/client_authentication.dart'
|
||||
import 'package:client_home/client_home.dart' as client_home;
|
||||
import 'package:client_settings/client_settings.dart' as client_settings;
|
||||
import 'package:client_hubs/client_hubs.dart' as client_hubs;
|
||||
import 'package:client_create_order/client_create_order.dart'
|
||||
as client_create_order;
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
|
||||
void main() async {
|
||||
@@ -20,10 +22,10 @@ void main() async {
|
||||
/// The main application module for the Client app.
|
||||
class AppModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [core_localization.LocalizationModule()];
|
||||
List<Module> get imports => <Module>[core_localization.LocalizationModule()];
|
||||
|
||||
@override
|
||||
void routes(r) {
|
||||
void routes(RouteManager r) {
|
||||
// Initial route points to the client authentication flow
|
||||
r.module('/', module: client_authentication.ClientAuthenticationModule());
|
||||
|
||||
@@ -38,6 +40,12 @@ class AppModule extends Module {
|
||||
|
||||
// Client hubs route
|
||||
r.module('/client-hubs', module: client_hubs.ClientHubsModule());
|
||||
|
||||
// Client create order route
|
||||
r.module(
|
||||
'/client/create-order',
|
||||
module: client_create_order.ClientCreateOrderModule(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +55,7 @@ class AppWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<core_localization.LocaleBloc>(
|
||||
create: (context) =>
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<core_localization.LocaleBloc>()
|
||||
..add(const core_localization.LoadLocale()),
|
||||
child:
|
||||
@@ -55,23 +63,25 @@ class AppWidget extends StatelessWidget {
|
||||
core_localization.LocaleBloc,
|
||||
core_localization.LocaleState
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return core_localization.TranslationProvider(
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: "Krow Client",
|
||||
theme: UiTheme.light,
|
||||
routerConfig: Modular.routerConfig,
|
||||
locale: state.locale,
|
||||
supportedLocales: state.supportedLocales,
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
builder:
|
||||
(BuildContext context, core_localization.LocaleState state) {
|
||||
return core_localization.TranslationProvider(
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: "Krow Client",
|
||||
theme: UiTheme.light,
|
||||
routerConfig: Modular.routerConfig,
|
||||
locale: state.locale,
|
||||
supportedLocales: state.supportedLocales,
|
||||
localizationsDelegates:
|
||||
const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,23 +10,31 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
# Architecture Packages
|
||||
design_system:
|
||||
path: ../../packages/design_system
|
||||
core_localization:
|
||||
path: ../../packages/core_localization
|
||||
|
||||
# Feature Packages
|
||||
client_authentication:
|
||||
path: ../../packages/features/client/authentication
|
||||
client_home:
|
||||
path: ../../packages/features/client/home
|
||||
client_settings:
|
||||
path: ../../packages/features/client/settings
|
||||
core_localization:
|
||||
path: ../../packages/core_localization
|
||||
client_hubs:
|
||||
path: ../../packages/features/client/hubs
|
||||
client_create_order:
|
||||
path: ../../packages/features/client/create_order
|
||||
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_modular: ^6.3.2
|
||||
flutter_bloc: ^8.1.3
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
firebase_core: ^4.4.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -1 +1 @@
|
||||
include: ../../analytics_options.yaml
|
||||
include: ../../analysis_options.yaml
|
||||
@@ -1 +1 @@
|
||||
include: ../../analytics_options.yaml
|
||||
include: ../../analysis_options.yaml
|
||||
@@ -13,12 +13,16 @@ dependencies:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_modular: ^6.3.0
|
||||
|
||||
# Architecture Packages
|
||||
design_system:
|
||||
path: ../../packages/design_system
|
||||
staff_authentication:
|
||||
path: ../../packages/features/staff/authentication
|
||||
core_localization:
|
||||
path: ../../packages/core_localization
|
||||
|
||||
# Feature Packages
|
||||
staff_authentication:
|
||||
path: ../../packages/features/staff/authentication
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -27,9 +27,16 @@ scripts:
|
||||
echo " CODE GENERATION:"
|
||||
echo " - melos run gen:l10n : Generate Slang l10n"
|
||||
echo " - melos run gen:build : Run build_runner"
|
||||
echo " - melos run gen:all : Run l10n and build_runner"
|
||||
echo "============================================================"
|
||||
description: "Display information about available custom Melos commands."
|
||||
|
||||
gen:all:
|
||||
run: |
|
||||
melos run gen:l10n
|
||||
melos run gen:build
|
||||
description: "Run both localization and build_runner generation across all packages."
|
||||
|
||||
gen:l10n:
|
||||
exec: dart run slang
|
||||
description: "Generate localization files using Slang across all packages."
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
include: ../../analysis_options.yaml
|
||||
|
||||
@@ -232,6 +232,20 @@
|
||||
"tag_identified": "Tag Identified",
|
||||
"assign_button": "Assign Tag"
|
||||
}
|
||||
},
|
||||
"client_create_order": {
|
||||
"title": "Create Order",
|
||||
"section_title": "ORDER TYPE",
|
||||
"types": {
|
||||
"rapid": "RAPID",
|
||||
"rapid_desc": "URGENT same-day Coverage",
|
||||
"one_time": "One-Time",
|
||||
"one_time_desc": "Single Event or Shift Request",
|
||||
"recurring": "Recurring",
|
||||
"recurring_desc": "Ongoing Weekly / Monthly Coverage",
|
||||
"permanent": "Permanent",
|
||||
"permanent_desc": "Long-Term Staffing Placement"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,5 +232,19 @@
|
||||
"tag_identified": "Etiqueta Identificada",
|
||||
"assign_button": "Asignar Etiqueta"
|
||||
}
|
||||
},
|
||||
"client_create_order": {
|
||||
"title": "Crear Orden",
|
||||
"section_title": "TIPO DE ORDEN",
|
||||
"types": {
|
||||
"rapid": "RÁPIDO",
|
||||
"rapid_desc": "Cobertura URGENTE mismo día",
|
||||
"one_time": "Única Vez",
|
||||
"one_time_desc": "Evento Único o Petición de Turno",
|
||||
"recurring": "Recurrente",
|
||||
"recurring_desc": "Cobertura Continua Semanal / Mensual",
|
||||
"permanent": "Permanente",
|
||||
"permanent_desc": "Colocación de Personal a Largo Plazo"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Basic Usage
|
||||
|
||||
```dart
|
||||
ExampleConnector.instance.createDocument(createDocumentVariables).execute();
|
||||
ExampleConnector.instance.updateDocument(updateDocumentVariables).execute();
|
||||
ExampleConnector.instance.deleteDocument(deleteDocumentVariables).execute();
|
||||
ExampleConnector.instance.createConversation(createConversationVariables).execute();
|
||||
ExampleConnector.instance.updateConversation(updateConversationVariables).execute();
|
||||
ExampleConnector.instance.updateConversationLastMessage(updateConversationLastMessageVariables).execute();
|
||||
ExampleConnector.instance.deleteConversation(deleteConversationVariables).execute();
|
||||
ExampleConnector.instance.listHubs().execute();
|
||||
ExampleConnector.instance.getHubById(getHubByIdVariables).execute();
|
||||
ExampleConnector.instance.getHubsByOwnerId(getHubsByOwnerIdVariables).execute();
|
||||
ExampleConnector.instance.createTeamHudDepartment(createTeamHudDepartmentVariables).execute();
|
||||
ExampleConnector.instance.updateTeamHudDepartment(updateTeamHudDepartmentVariables).execute();
|
||||
ExampleConnector.instance.deleteTeamHudDepartment(deleteTeamHudDepartmentVariables).execute();
|
||||
ExampleConnector.instance.listAssignments(listAssignmentsVariables).execute();
|
||||
ExampleConnector.instance.getAssignmentById(getAssignmentByIdVariables).execute();
|
||||
ExampleConnector.instance.listAssignmentsByWorkforceId(listAssignmentsByWorkforceIdVariables).execute();
|
||||
ExampleConnector.instance.listAssignmentsByWorkforceIds(listAssignmentsByWorkforceIdsVariables).execute();
|
||||
ExampleConnector.instance.listAssignmentsByShiftRole(listAssignmentsByShiftRoleVariables).execute();
|
||||
ExampleConnector.instance.filterAssignments(filterAssignmentsVariables).execute();
|
||||
ExampleConnector.instance.CreateCertificate(createCertificateVariables).execute();
|
||||
|
||||
```
|
||||
|
||||
@@ -23,8 +23,8 @@ Optional fields can be discovered based on classes that have `Optional` object t
|
||||
This is an example of a mutation with an optional field:
|
||||
|
||||
```dart
|
||||
await ExampleConnector.instance.filterVendorBenefitPlans({ ... })
|
||||
.vendorId(...)
|
||||
await ExampleConnector.instance.updateShift({ ... })
|
||||
.title(...)
|
||||
.execute();
|
||||
```
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,30 @@ class CreateTeamHubVariablesBuilder {
|
||||
String teamId;
|
||||
String hubName;
|
||||
String address;
|
||||
String city;
|
||||
String state;
|
||||
String zipCode;
|
||||
String managerName;
|
||||
Optional<String> _city = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<String> _state = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<String> _zipCode = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<String> _managerName = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<bool> _isActive = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<AnyValue> _departments = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
|
||||
final FirebaseDataConnect _dataConnect; CreateTeamHubVariablesBuilder isActive(bool? t) {
|
||||
final FirebaseDataConnect _dataConnect; CreateTeamHubVariablesBuilder city(String? t) {
|
||||
_city.value = t;
|
||||
return this;
|
||||
}
|
||||
CreateTeamHubVariablesBuilder state(String? t) {
|
||||
_state.value = t;
|
||||
return this;
|
||||
}
|
||||
CreateTeamHubVariablesBuilder zipCode(String? t) {
|
||||
_zipCode.value = t;
|
||||
return this;
|
||||
}
|
||||
CreateTeamHubVariablesBuilder managerName(String? t) {
|
||||
_managerName.value = t;
|
||||
return this;
|
||||
}
|
||||
CreateTeamHubVariablesBuilder isActive(bool? t) {
|
||||
_isActive.value = t;
|
||||
return this;
|
||||
}
|
||||
@@ -20,7 +36,7 @@ class CreateTeamHubVariablesBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
CreateTeamHubVariablesBuilder(this._dataConnect, {required this.teamId,required this.hubName,required this.address,required this.city,required this.state,required this.zipCode,required this.managerName,});
|
||||
CreateTeamHubVariablesBuilder(this._dataConnect, {required this.teamId,required this.hubName,required this.address,});
|
||||
Deserializer<CreateTeamHubData> dataDeserializer = (dynamic json) => CreateTeamHubData.fromJson(jsonDecode(json));
|
||||
Serializer<CreateTeamHubVariables> varsSerializer = (CreateTeamHubVariables vars) => jsonEncode(vars.toJson());
|
||||
Future<OperationResult<CreateTeamHubData, CreateTeamHubVariables>> execute() {
|
||||
@@ -28,7 +44,7 @@ class CreateTeamHubVariablesBuilder {
|
||||
}
|
||||
|
||||
MutationRef<CreateTeamHubData, CreateTeamHubVariables> ref() {
|
||||
CreateTeamHubVariables vars= CreateTeamHubVariables(teamId: teamId,hubName: hubName,address: address,city: city,state: state,zipCode: zipCode,managerName: managerName,isActive: _isActive,departments: _departments,);
|
||||
CreateTeamHubVariables vars= CreateTeamHubVariables(teamId: teamId,hubName: hubName,address: address,city: _city,state: _state,zipCode: _zipCode,managerName: _managerName,isActive: _isActive,departments: _departments,);
|
||||
return _dataConnect.mutation("createTeamHub", dataDeserializer, varsSerializer, vars);
|
||||
}
|
||||
}
|
||||
@@ -106,10 +122,10 @@ class CreateTeamHubVariables {
|
||||
final String teamId;
|
||||
final String hubName;
|
||||
final String address;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final String managerName;
|
||||
late final Optional<String>city;
|
||||
late final Optional<String>state;
|
||||
late final Optional<String>zipCode;
|
||||
late final Optional<String>managerName;
|
||||
late final Optional<bool>isActive;
|
||||
late final Optional<AnyValue>departments;
|
||||
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
|
||||
@@ -117,18 +133,26 @@ class CreateTeamHubVariables {
|
||||
|
||||
teamId = nativeFromJson<String>(json['teamId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']),
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
city = nativeFromJson<String>(json['city']),
|
||||
state = nativeFromJson<String>(json['state']),
|
||||
zipCode = nativeFromJson<String>(json['zipCode']),
|
||||
managerName = nativeFromJson<String>(json['managerName']) {
|
||||
address = nativeFromJson<String>(json['address']) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
city = Optional.optional(nativeFromJson, nativeToJson);
|
||||
city.value = json['city'] == null ? null : nativeFromJson<String>(json['city']);
|
||||
|
||||
|
||||
state = Optional.optional(nativeFromJson, nativeToJson);
|
||||
state.value = json['state'] == null ? null : nativeFromJson<String>(json['state']);
|
||||
|
||||
|
||||
zipCode = Optional.optional(nativeFromJson, nativeToJson);
|
||||
zipCode.value = json['zipCode'] == null ? null : nativeFromJson<String>(json['zipCode']);
|
||||
|
||||
|
||||
managerName = Optional.optional(nativeFromJson, nativeToJson);
|
||||
managerName.value = json['managerName'] == null ? null : nativeFromJson<String>(json['managerName']);
|
||||
|
||||
|
||||
isActive = Optional.optional(nativeFromJson, nativeToJson);
|
||||
@@ -169,10 +193,18 @@ class CreateTeamHubVariables {
|
||||
json['teamId'] = nativeToJson<String>(teamId);
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
json['city'] = nativeToJson<String>(city);
|
||||
json['state'] = nativeToJson<String>(state);
|
||||
json['zipCode'] = nativeToJson<String>(zipCode);
|
||||
json['managerName'] = nativeToJson<String>(managerName);
|
||||
if(city.state == OptionalState.set) {
|
||||
json['city'] = city.toJson();
|
||||
}
|
||||
if(state.state == OptionalState.set) {
|
||||
json['state'] = state.toJson();
|
||||
}
|
||||
if(zipCode.state == OptionalState.set) {
|
||||
json['zipCode'] = zipCode.toJson();
|
||||
}
|
||||
if(managerName.state == OptionalState.set) {
|
||||
json['managerName'] = managerName.toJson();
|
||||
}
|
||||
if(isActive.state == OptionalState.set) {
|
||||
json['isActive'] = isActive.toJson();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,10 +23,10 @@ class GetTeamHubByIdTeamHub {
|
||||
final String teamId;
|
||||
final String hubName;
|
||||
final String address;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final String managerName;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? zipCode;
|
||||
final String? managerName;
|
||||
final bool isActive;
|
||||
final AnyValue? departments;
|
||||
final Timestamp? createdAt;
|
||||
@@ -38,10 +38,10 @@ class GetTeamHubByIdTeamHub {
|
||||
teamId = nativeFromJson<String>(json['teamId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']),
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
city = nativeFromJson<String>(json['city']),
|
||||
state = nativeFromJson<String>(json['state']),
|
||||
zipCode = nativeFromJson<String>(json['zipCode']),
|
||||
managerName = nativeFromJson<String>(json['managerName']),
|
||||
city = json['city'] == null ? null : nativeFromJson<String>(json['city']),
|
||||
state = json['state'] == null ? null : nativeFromJson<String>(json['state']),
|
||||
zipCode = json['zipCode'] == null ? null : nativeFromJson<String>(json['zipCode']),
|
||||
managerName = json['managerName'] == null ? null : nativeFromJson<String>(json['managerName']),
|
||||
isActive = nativeFromJson<bool>(json['isActive']),
|
||||
departments = json['departments'] == null ? null : AnyValue.fromJson(json['departments']),
|
||||
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']),
|
||||
@@ -82,10 +82,18 @@ class GetTeamHubByIdTeamHub {
|
||||
json['teamId'] = nativeToJson<String>(teamId);
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
json['city'] = nativeToJson<String>(city);
|
||||
json['state'] = nativeToJson<String>(state);
|
||||
json['zipCode'] = nativeToJson<String>(zipCode);
|
||||
json['managerName'] = nativeToJson<String>(managerName);
|
||||
if (city != null) {
|
||||
json['city'] = nativeToJson<String?>(city);
|
||||
}
|
||||
if (state != null) {
|
||||
json['state'] = nativeToJson<String?>(state);
|
||||
}
|
||||
if (zipCode != null) {
|
||||
json['zipCode'] = nativeToJson<String?>(zipCode);
|
||||
}
|
||||
if (managerName != null) {
|
||||
json['managerName'] = nativeToJson<String?>(managerName);
|
||||
}
|
||||
json['isActive'] = nativeToJson<bool>(isActive);
|
||||
if (departments != null) {
|
||||
json['departments'] = departments!.toJson();
|
||||
@@ -107,10 +115,10 @@ class GetTeamHubByIdTeamHub {
|
||||
required this.teamId,
|
||||
required this.hubName,
|
||||
required this.address,
|
||||
required this.city,
|
||||
required this.state,
|
||||
required this.zipCode,
|
||||
required this.managerName,
|
||||
this.city,
|
||||
this.state,
|
||||
this.zipCode,
|
||||
this.managerName,
|
||||
required this.isActive,
|
||||
this.departments,
|
||||
this.createdAt,
|
||||
|
||||
@@ -23,10 +23,10 @@ class GetTeamHubsByTeamIdTeamHubs {
|
||||
final String teamId;
|
||||
final String hubName;
|
||||
final String address;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final String managerName;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? zipCode;
|
||||
final String? managerName;
|
||||
final bool isActive;
|
||||
final AnyValue? departments;
|
||||
final Timestamp? createdAt;
|
||||
@@ -38,10 +38,10 @@ class GetTeamHubsByTeamIdTeamHubs {
|
||||
teamId = nativeFromJson<String>(json['teamId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']),
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
city = nativeFromJson<String>(json['city']),
|
||||
state = nativeFromJson<String>(json['state']),
|
||||
zipCode = nativeFromJson<String>(json['zipCode']),
|
||||
managerName = nativeFromJson<String>(json['managerName']),
|
||||
city = json['city'] == null ? null : nativeFromJson<String>(json['city']),
|
||||
state = json['state'] == null ? null : nativeFromJson<String>(json['state']),
|
||||
zipCode = json['zipCode'] == null ? null : nativeFromJson<String>(json['zipCode']),
|
||||
managerName = json['managerName'] == null ? null : nativeFromJson<String>(json['managerName']),
|
||||
isActive = nativeFromJson<bool>(json['isActive']),
|
||||
departments = json['departments'] == null ? null : AnyValue.fromJson(json['departments']),
|
||||
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']),
|
||||
@@ -82,10 +82,18 @@ class GetTeamHubsByTeamIdTeamHubs {
|
||||
json['teamId'] = nativeToJson<String>(teamId);
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
json['city'] = nativeToJson<String>(city);
|
||||
json['state'] = nativeToJson<String>(state);
|
||||
json['zipCode'] = nativeToJson<String>(zipCode);
|
||||
json['managerName'] = nativeToJson<String>(managerName);
|
||||
if (city != null) {
|
||||
json['city'] = nativeToJson<String?>(city);
|
||||
}
|
||||
if (state != null) {
|
||||
json['state'] = nativeToJson<String?>(state);
|
||||
}
|
||||
if (zipCode != null) {
|
||||
json['zipCode'] = nativeToJson<String?>(zipCode);
|
||||
}
|
||||
if (managerName != null) {
|
||||
json['managerName'] = nativeToJson<String?>(managerName);
|
||||
}
|
||||
json['isActive'] = nativeToJson<bool>(isActive);
|
||||
if (departments != null) {
|
||||
json['departments'] = departments!.toJson();
|
||||
@@ -107,10 +115,10 @@ class GetTeamHubsByTeamIdTeamHubs {
|
||||
required this.teamId,
|
||||
required this.hubName,
|
||||
required this.address,
|
||||
required this.city,
|
||||
required this.state,
|
||||
required this.zipCode,
|
||||
required this.managerName,
|
||||
this.city,
|
||||
this.state,
|
||||
this.zipCode,
|
||||
this.managerName,
|
||||
required this.isActive,
|
||||
this.departments,
|
||||
this.createdAt,
|
||||
|
||||
@@ -22,10 +22,10 @@ class ListTeamHubsTeamHubs {
|
||||
final String teamId;
|
||||
final String hubName;
|
||||
final String address;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final String managerName;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? zipCode;
|
||||
final String? managerName;
|
||||
final bool isActive;
|
||||
final AnyValue? departments;
|
||||
final Timestamp? createdAt;
|
||||
@@ -37,10 +37,10 @@ class ListTeamHubsTeamHubs {
|
||||
teamId = nativeFromJson<String>(json['teamId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']),
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
city = nativeFromJson<String>(json['city']),
|
||||
state = nativeFromJson<String>(json['state']),
|
||||
zipCode = nativeFromJson<String>(json['zipCode']),
|
||||
managerName = nativeFromJson<String>(json['managerName']),
|
||||
city = json['city'] == null ? null : nativeFromJson<String>(json['city']),
|
||||
state = json['state'] == null ? null : nativeFromJson<String>(json['state']),
|
||||
zipCode = json['zipCode'] == null ? null : nativeFromJson<String>(json['zipCode']),
|
||||
managerName = json['managerName'] == null ? null : nativeFromJson<String>(json['managerName']),
|
||||
isActive = nativeFromJson<bool>(json['isActive']),
|
||||
departments = json['departments'] == null ? null : AnyValue.fromJson(json['departments']),
|
||||
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']),
|
||||
@@ -81,10 +81,18 @@ class ListTeamHubsTeamHubs {
|
||||
json['teamId'] = nativeToJson<String>(teamId);
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
json['city'] = nativeToJson<String>(city);
|
||||
json['state'] = nativeToJson<String>(state);
|
||||
json['zipCode'] = nativeToJson<String>(zipCode);
|
||||
json['managerName'] = nativeToJson<String>(managerName);
|
||||
if (city != null) {
|
||||
json['city'] = nativeToJson<String?>(city);
|
||||
}
|
||||
if (state != null) {
|
||||
json['state'] = nativeToJson<String?>(state);
|
||||
}
|
||||
if (zipCode != null) {
|
||||
json['zipCode'] = nativeToJson<String?>(zipCode);
|
||||
}
|
||||
if (managerName != null) {
|
||||
json['managerName'] = nativeToJson<String?>(managerName);
|
||||
}
|
||||
json['isActive'] = nativeToJson<bool>(isActive);
|
||||
if (departments != null) {
|
||||
json['departments'] = departments!.toJson();
|
||||
@@ -106,10 +114,10 @@ class ListTeamHubsTeamHubs {
|
||||
required this.teamId,
|
||||
required this.hubName,
|
||||
required this.address,
|
||||
required this.city,
|
||||
required this.state,
|
||||
required this.zipCode,
|
||||
required this.managerName,
|
||||
this.city,
|
||||
this.state,
|
||||
this.zipCode,
|
||||
this.managerName,
|
||||
required this.isActive,
|
||||
this.departments,
|
||||
this.createdAt,
|
||||
|
||||
@@ -23,10 +23,10 @@ class ListTeamHubsByOwnerIdTeamHubs {
|
||||
final String teamId;
|
||||
final String hubName;
|
||||
final String address;
|
||||
final String city;
|
||||
final String state;
|
||||
final String zipCode;
|
||||
final String managerName;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? zipCode;
|
||||
final String? managerName;
|
||||
final bool isActive;
|
||||
final AnyValue? departments;
|
||||
final Timestamp? createdAt;
|
||||
@@ -36,10 +36,10 @@ class ListTeamHubsByOwnerIdTeamHubs {
|
||||
teamId = nativeFromJson<String>(json['teamId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']),
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
city = nativeFromJson<String>(json['city']),
|
||||
state = nativeFromJson<String>(json['state']),
|
||||
zipCode = nativeFromJson<String>(json['zipCode']),
|
||||
managerName = nativeFromJson<String>(json['managerName']),
|
||||
city = json['city'] == null ? null : nativeFromJson<String>(json['city']),
|
||||
state = json['state'] == null ? null : nativeFromJson<String>(json['state']),
|
||||
zipCode = json['zipCode'] == null ? null : nativeFromJson<String>(json['zipCode']),
|
||||
managerName = json['managerName'] == null ? null : nativeFromJson<String>(json['managerName']),
|
||||
isActive = nativeFromJson<bool>(json['isActive']),
|
||||
departments = json['departments'] == null ? null : AnyValue.fromJson(json['departments']),
|
||||
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']);
|
||||
@@ -76,10 +76,18 @@ class ListTeamHubsByOwnerIdTeamHubs {
|
||||
json['teamId'] = nativeToJson<String>(teamId);
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
json['city'] = nativeToJson<String>(city);
|
||||
json['state'] = nativeToJson<String>(state);
|
||||
json['zipCode'] = nativeToJson<String>(zipCode);
|
||||
json['managerName'] = nativeToJson<String>(managerName);
|
||||
if (city != null) {
|
||||
json['city'] = nativeToJson<String?>(city);
|
||||
}
|
||||
if (state != null) {
|
||||
json['state'] = nativeToJson<String?>(state);
|
||||
}
|
||||
if (zipCode != null) {
|
||||
json['zipCode'] = nativeToJson<String?>(zipCode);
|
||||
}
|
||||
if (managerName != null) {
|
||||
json['managerName'] = nativeToJson<String?>(managerName);
|
||||
}
|
||||
json['isActive'] = nativeToJson<bool>(isActive);
|
||||
if (departments != null) {
|
||||
json['departments'] = departments!.toJson();
|
||||
@@ -95,10 +103,10 @@ class ListTeamHubsByOwnerIdTeamHubs {
|
||||
required this.teamId,
|
||||
required this.hubName,
|
||||
required this.address,
|
||||
required this.city,
|
||||
required this.state,
|
||||
required this.zipCode,
|
||||
required this.managerName,
|
||||
this.city,
|
||||
this.state,
|
||||
this.zipCode,
|
||||
this.managerName,
|
||||
required this.isActive,
|
||||
this.departments,
|
||||
this.createdAt,
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,39 +0,0 @@
|
||||
<!--
|
||||
This README describes the package. If you publish this package to pub.dev,
|
||||
this README's contents appear on the landing page for your package.
|
||||
|
||||
For information about how to write a good package README, see the guide for
|
||||
[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
|
||||
|
||||
For general information about developing packages, see the Dart guide for
|
||||
[creating packages](https://dart.dev/guides/libraries/create-packages)
|
||||
and the Flutter guide for
|
||||
[developing packages and plugins](https://flutter.dev/to/develop-packages).
|
||||
-->
|
||||
|
||||
TODO: Put a short description of the package here that helps potential users
|
||||
know whether this package might be useful for them.
|
||||
|
||||
## Features
|
||||
|
||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||
|
||||
## Getting started
|
||||
|
||||
TODO: List prerequisites and provide or point to information on how to
|
||||
start using the package.
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Include short and useful examples for package users. Add longer examples
|
||||
to `/example` folder.
|
||||
|
||||
```dart
|
||||
const like = 'sample';
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
TODO: Tell users more about the package: where to find more information, how to
|
||||
contribute to the package, how to file issues, what response they can expect
|
||||
from the package authors, and more.
|
||||
@@ -1 +1 @@
|
||||
include: ../../analytics_options.yaml
|
||||
include: ../../analysis_options.yaml
|
||||
|
||||
@@ -5,11 +5,11 @@ class UiConstants {
|
||||
UiConstants._();
|
||||
|
||||
// --- Border Radii ---
|
||||
|
||||
|
||||
/// Base radius: 12px
|
||||
static const double radiusBase = 12.0;
|
||||
static final BorderRadius radiusLg = BorderRadius.circular(radiusBase);
|
||||
|
||||
|
||||
/// Medium radius: 6px
|
||||
static const double radiusMdValue = 6.0;
|
||||
static final BorderRadius radiusMd = BorderRadius.circular(radiusMdValue);
|
||||
@@ -19,12 +19,12 @@ class UiConstants {
|
||||
|
||||
/// Extra small radius: 2px
|
||||
static final BorderRadius radiusXs = BorderRadius.circular(2.0);
|
||||
|
||||
|
||||
/// Large/Full radius
|
||||
static final BorderRadius radiusFull = BorderRadius.circular(999.0);
|
||||
|
||||
// --- Spacing ---
|
||||
|
||||
|
||||
static const double space0 = 0.0;
|
||||
static const double space1 = 4.0;
|
||||
static const double space2 = 8.0;
|
||||
@@ -35,4 +35,6 @@ class UiConstants {
|
||||
static const double space8 = 32.0;
|
||||
static const double space10 = 40.0;
|
||||
static const double space12 = 48.0;
|
||||
static const double space14 = 56.0;
|
||||
static const double space16 = 64.0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import '../../domain/repositories/auth_repository_interface.dart';
|
||||
|
||||
@@ -8,15 +8,15 @@ import '../../domain/repositories/auth_repository_interface.dart';
|
||||
/// This implementation integrates with Firebase Authentication for user
|
||||
/// identity management and Krow's Data Connect SDK for storing user profile data.
|
||||
class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
final firebase.FirebaseAuth _firebaseAuth;
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
final FirebaseAuth _firebaseAuth;
|
||||
final ExampleConnector _dataConnect;
|
||||
|
||||
/// Creates an [AuthRepositoryImpl] with the real dependencies.
|
||||
AuthRepositoryImpl({
|
||||
required firebase.FirebaseAuth firebaseAuth,
|
||||
required dc.ExampleConnector dataConnect,
|
||||
}) : _firebaseAuth = firebaseAuth,
|
||||
_dataConnect = dataConnect;
|
||||
required FirebaseAuth firebaseAuth,
|
||||
required ExampleConnector dataConnect,
|
||||
}) : _firebaseAuth = firebaseAuth,
|
||||
_dataConnect = dataConnect;
|
||||
|
||||
@override
|
||||
Future<domain.User> signInWithEmail({
|
||||
@@ -91,23 +91,25 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
// Client-specific business logic:
|
||||
// 1. Create a `Business` entity.
|
||||
// 2. Create a `User` entity associated with the business.
|
||||
final createBusinessResponse = await _dataConnect.createBusiness(
|
||||
businessName: companyName,
|
||||
userId: firebaseUser.uid,
|
||||
rateGroup: dc.BusinessRateGroup.STANDARD,
|
||||
status: dc.BusinessStatus.PENDING,
|
||||
).execute();
|
||||
final createBusinessResponse = await _dataConnect
|
||||
.createBusiness(
|
||||
businessName: companyName,
|
||||
userId: firebaseUser.uid,
|
||||
rateGroup: BusinessRateGroup.STANDARD,
|
||||
status: BusinessStatus.PENDING,
|
||||
)
|
||||
.execute();
|
||||
|
||||
final businessData = createBusinessResponse.data?.business_insert;
|
||||
if (businessData == null) {
|
||||
await firebaseUser.delete(); // Rollback if business creation fails
|
||||
throw Exception('Business creation failed after Firebase user registration.');
|
||||
throw Exception(
|
||||
'Business creation failed after Firebase user registration.',
|
||||
);
|
||||
}
|
||||
|
||||
final createUserResponse = await _dataConnect.createUser(
|
||||
id: firebaseUser.uid,
|
||||
role: dc.UserBaseRole.USER,
|
||||
)
|
||||
final createUserResponse = await _dataConnect
|
||||
.createUser(id: firebaseUser.uid, role: UserBaseRole.USER)
|
||||
.email(email)
|
||||
.userRole('BUSINESS')
|
||||
.execute();
|
||||
@@ -116,15 +118,16 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
if (newUserData == null) {
|
||||
await firebaseUser.delete(); // Rollback if user profile creation fails
|
||||
// TO-DO: Also delete the created Business if this fails
|
||||
throw Exception('User profile creation failed after Firebase user registration.');
|
||||
throw Exception(
|
||||
'User profile creation failed after Firebase user registration.',
|
||||
);
|
||||
}
|
||||
|
||||
return _getUserProfile(
|
||||
firebaseUserId: firebaseUser.uid,
|
||||
fallbackEmail: firebaseUser.email ?? email,
|
||||
);
|
||||
|
||||
} on firebase.FirebaseAuthException catch (e) {
|
||||
} on FirebaseAuthException catch (e) {
|
||||
if (e.code == 'weak-password') {
|
||||
throw Exception('The password provided is too weak.');
|
||||
} else if (e.code == 'email-already-in-use') {
|
||||
@@ -133,7 +136,9 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
throw Exception('Sign-up error: ${e.message}');
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Failed to sign up and create user data: ${e.toString()}');
|
||||
throw Exception(
|
||||
'Failed to sign up and create user data: ${e.toString()}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,14 +153,18 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
|
||||
@override
|
||||
Future<domain.User> signInWithSocial({required String provider}) {
|
||||
throw UnimplementedError('Social authentication with $provider is not yet implemented.');
|
||||
throw UnimplementedError(
|
||||
'Social authentication with $provider is not yet implemented.',
|
||||
);
|
||||
}
|
||||
|
||||
Future<domain.User> _getUserProfile({
|
||||
required String firebaseUserId,
|
||||
required String? fallbackEmail,
|
||||
}) async {
|
||||
final response = await _dataConnect.getUserById(id: firebaseUserId).execute();
|
||||
final response = await _dataConnect
|
||||
.getUserById(id: firebaseUserId)
|
||||
.execute();
|
||||
final user = response.data?.user;
|
||||
if (user == null) {
|
||||
throw Exception('Authenticated user profile not found in database.');
|
||||
@@ -166,10 +175,6 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
throw Exception('User email is missing in profile data.');
|
||||
}
|
||||
|
||||
return domain.User(
|
||||
id: user.id,
|
||||
email: email,
|
||||
role: user.role.stringValue,
|
||||
);
|
||||
return domain.User(id: user.id, email: email, role: user.role.stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,13 @@ dependencies:
|
||||
|
||||
# Architecture Packages
|
||||
design_system:
|
||||
path: ../../../../design_system
|
||||
path: ../../../design_system
|
||||
core_localization:
|
||||
path: ../../../core_localization
|
||||
krow_data_connect:
|
||||
path: ../../../data_connect
|
||||
krow_domain:
|
||||
path: ../../../domain
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
library client_create_order;
|
||||
|
||||
export 'src/create_order_module.dart';
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'presentation/blocs/client_create_order_bloc.dart';
|
||||
import 'presentation/pages/create_order_page.dart';
|
||||
import 'presentation/pages/one_time_order_page.dart';
|
||||
import 'presentation/pages/permanent_order_page.dart';
|
||||
import 'presentation/pages/rapid_order_page.dart';
|
||||
import 'presentation/pages/recurring_order_page.dart';
|
||||
|
||||
class ClientCreateOrderModule extends Module {
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
i.addSingleton<ClientCreateOrderBloc>(ClientCreateOrderBloc.new);
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(RouteManager r) {
|
||||
r.child('/',
|
||||
child: (BuildContext context) => const ClientCreateOrderPage());
|
||||
r.child('/rapid', child: (BuildContext context) => const RapidOrderPage());
|
||||
r.child('/one-time',
|
||||
child: (BuildContext context) => const OneTimeOrderPage());
|
||||
r.child('/recurring',
|
||||
child: (BuildContext context) => const RecurringOrderPage());
|
||||
r.child('/permanent',
|
||||
child: (BuildContext context) => const PermanentOrderPage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'client_create_order_event.dart';
|
||||
import 'client_create_order_state.dart';
|
||||
|
||||
class ClientCreateOrderBloc
|
||||
extends Bloc<ClientCreateOrderEvent, ClientCreateOrderState> {
|
||||
ClientCreateOrderBloc() : super(const ClientCreateOrderInitial()) {
|
||||
on<ClientCreateOrderTypesRequested>(_onTypesRequested);
|
||||
}
|
||||
|
||||
void _onTypesRequested(
|
||||
ClientCreateOrderTypesRequested event,
|
||||
Emitter<ClientCreateOrderState> emit,
|
||||
) {
|
||||
// In a real app, this might come from a repository or config
|
||||
final List<CreateOrderType> types = [
|
||||
const CreateOrderType(
|
||||
id: 'rapid',
|
||||
icon: UiIcons.zap,
|
||||
titleKey: 'client_create_order.types.rapid',
|
||||
descriptionKey: 'client_create_order.types.rapid_desc',
|
||||
backgroundColor: UiColors.tagError, // Red-ish background
|
||||
borderColor: UiColors.destructive, // Red border
|
||||
iconBackgroundColor: UiColors.tagError,
|
||||
iconColor: UiColors.destructive,
|
||||
textColor: UiColors.destructive,
|
||||
descriptionColor: UiColors.textError,
|
||||
),
|
||||
const CreateOrderType(
|
||||
id: 'one-time',
|
||||
icon: UiIcons.calendar,
|
||||
titleKey: 'client_create_order.types.one_time',
|
||||
descriptionKey: 'client_create_order.types.one_time_desc',
|
||||
backgroundColor: UiColors.tagInProgress, // Blue-ish
|
||||
borderColor: UiColors.primary,
|
||||
iconBackgroundColor: UiColors.tagInProgress,
|
||||
iconColor: UiColors.primary,
|
||||
textColor: UiColors.primary,
|
||||
descriptionColor: UiColors.primary,
|
||||
),
|
||||
const CreateOrderType(
|
||||
id: 'recurring',
|
||||
icon: UiIcons.rotateCcw,
|
||||
titleKey: 'client_create_order.types.recurring',
|
||||
descriptionKey: 'client_create_order.types.recurring_desc',
|
||||
backgroundColor: UiColors.tagRefunded, // Indigo-ish (Purple sub)
|
||||
borderColor: UiColors.primary, // No purple, use primary or mix
|
||||
iconBackgroundColor: UiColors.tagRefunded,
|
||||
iconColor: UiColors.primary,
|
||||
textColor: UiColors.primary,
|
||||
descriptionColor: UiColors.textSecondary,
|
||||
),
|
||||
const CreateOrderType(
|
||||
id: 'permanent',
|
||||
icon: UiIcons.briefcase,
|
||||
titleKey: 'client_create_order.types.permanent',
|
||||
descriptionKey: 'client_create_order.types.permanent_desc',
|
||||
backgroundColor: UiColors.tagSuccess, // Green
|
||||
borderColor: UiColors.textSuccess,
|
||||
iconBackgroundColor: UiColors.tagSuccess,
|
||||
iconColor: UiColors.textSuccess,
|
||||
textColor: UiColors.textSuccess,
|
||||
descriptionColor: UiColors.textSuccess,
|
||||
),
|
||||
];
|
||||
emit(ClientCreateOrderLoadSuccess(types));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class ClientCreateOrderEvent extends Equatable {
|
||||
const ClientCreateOrderEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ClientCreateOrderTypesRequested extends ClientCreateOrderEvent {
|
||||
const ClientCreateOrderTypesRequested();
|
||||
}
|
||||
|
||||
class ClientCreateOrderTypeSelected extends ClientCreateOrderEvent {
|
||||
final String typeId;
|
||||
|
||||
const ClientCreateOrderTypeSelected(this.typeId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [typeId];
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Represents an available order type.
|
||||
class CreateOrderType extends Equatable {
|
||||
final String id;
|
||||
final IconData icon;
|
||||
final String titleKey; // Key for translation
|
||||
final String descriptionKey; // Key for translation
|
||||
final Color backgroundColor;
|
||||
final Color borderColor;
|
||||
final Color iconBackgroundColor;
|
||||
final Color iconColor;
|
||||
final Color textColor;
|
||||
final Color descriptionColor;
|
||||
|
||||
const CreateOrderType({
|
||||
required this.id,
|
||||
required this.icon,
|
||||
required this.titleKey,
|
||||
required this.descriptionKey,
|
||||
required this.backgroundColor,
|
||||
required this.borderColor,
|
||||
required this.iconBackgroundColor,
|
||||
required this.iconColor,
|
||||
required this.textColor,
|
||||
required this.descriptionColor,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
icon,
|
||||
titleKey,
|
||||
descriptionKey,
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
iconBackgroundColor,
|
||||
iconColor,
|
||||
textColor,
|
||||
descriptionColor,
|
||||
];
|
||||
}
|
||||
|
||||
abstract class ClientCreateOrderState extends Equatable {
|
||||
const ClientCreateOrderState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ClientCreateOrderInitial extends ClientCreateOrderState {
|
||||
const ClientCreateOrderInitial();
|
||||
}
|
||||
|
||||
class ClientCreateOrderLoadSuccess extends ClientCreateOrderState {
|
||||
final List<CreateOrderType> orderTypes;
|
||||
|
||||
const ClientCreateOrderLoadSuccess(this.orderTypes);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [orderTypes];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
extension ClientCreateOrderNavigator on IModularNavigator {
|
||||
void pushCreateOrder() {
|
||||
pushNamed('/client/create-order/');
|
||||
}
|
||||
|
||||
void pushRapidOrder() {
|
||||
pushNamed('/client/create-order/rapid');
|
||||
}
|
||||
|
||||
void pushOneTimeOrder() {
|
||||
pushNamed('/client/create-order/one-time');
|
||||
}
|
||||
|
||||
void pushRecurringOrder() {
|
||||
pushNamed('/client/create-order/recurring');
|
||||
}
|
||||
|
||||
void pushPermanentOrder() {
|
||||
pushNamed('/client/create-order/permanent');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import '../blocs/client_create_order_bloc.dart';
|
||||
import '../blocs/client_create_order_event.dart';
|
||||
import '../blocs/client_create_order_state.dart';
|
||||
import '../navigation/client_create_order_navigator.dart';
|
||||
|
||||
/// One-time helper to map keys to translations since they are dynamic in BLoC state
|
||||
String _getTranslation(String key) {
|
||||
// Safe mapping - explicit keys expected
|
||||
if (key == 'client_create_order.types.rapid') {
|
||||
return t.client_create_order.types.rapid;
|
||||
} else if (key == 'client_create_order.types.rapid_desc') {
|
||||
return t.client_create_order.types.rapid_desc;
|
||||
} else if (key == 'client_create_order.types.one_time') {
|
||||
return t.client_create_order.types.one_time;
|
||||
} else if (key == 'client_create_order.types.one_time_desc') {
|
||||
return t.client_create_order.types.one_time_desc;
|
||||
} else if (key == 'client_create_order.types.recurring') {
|
||||
return t.client_create_order.types.recurring;
|
||||
} else if (key == 'client_create_order.types.recurring_desc') {
|
||||
return t.client_create_order.types.recurring_desc;
|
||||
} else if (key == 'client_create_order.types.permanent') {
|
||||
return t.client_create_order.types.permanent;
|
||||
} else if (key == 'client_create_order.types.permanent_desc') {
|
||||
return t.client_create_order.types.permanent_desc;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
class ClientCreateOrderPage extends StatelessWidget {
|
||||
const ClientCreateOrderPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ClientCreateOrderBloc>(
|
||||
create: (BuildContext context) => Modular.get<ClientCreateOrderBloc>()
|
||||
..add(const ClientCreateOrderTypesRequested()),
|
||||
child: const _CreateOrderView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CreateOrderView extends StatelessWidget {
|
||||
const _CreateOrderView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor: UiColors.white,
|
||||
elevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
title: Text(
|
||||
t.client_create_order.title,
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space6,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space6),
|
||||
child: Text(
|
||||
t.client_create_order.section_title,
|
||||
style: UiTypography.footnote1m.copyWith(
|
||||
color: UiColors.textDescription,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
BlocBuilder<ClientCreateOrderBloc, ClientCreateOrderState>(
|
||||
builder:
|
||||
(BuildContext context, ClientCreateOrderState state) {
|
||||
if (state is ClientCreateOrderLoadSuccess) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: UiConstants.space4,
|
||||
crossAxisSpacing: UiConstants.space4,
|
||||
childAspectRatio: 1,
|
||||
),
|
||||
itemCount: state.orderTypes.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final CreateOrderType type = state.orderTypes[index];
|
||||
return _OrderTypeCard(
|
||||
icon: type.icon,
|
||||
title: _getTranslation(type.titleKey),
|
||||
description: _getTranslation(
|
||||
type.descriptionKey,
|
||||
),
|
||||
backgroundColor: type.backgroundColor,
|
||||
borderColor: type.borderColor,
|
||||
iconBackgroundColor: type.iconBackgroundColor,
|
||||
iconColor: type.iconColor,
|
||||
textColor: type.textColor,
|
||||
descriptionColor: type.descriptionColor,
|
||||
onTap: () {
|
||||
switch (type.id) {
|
||||
case 'rapid':
|
||||
Modular.to.pushRapidOrder();
|
||||
break;
|
||||
case 'one-time':
|
||||
Modular.to.pushOneTimeOrder();
|
||||
break;
|
||||
case 'recurring':
|
||||
Modular.to.pushRecurringOrder();
|
||||
break;
|
||||
case 'permanent':
|
||||
Modular.to.pushPermanentOrder();
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OrderTypeCard extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String description;
|
||||
final Color backgroundColor;
|
||||
final Color borderColor;
|
||||
final Color iconBackgroundColor;
|
||||
final Color iconColor;
|
||||
final Color textColor;
|
||||
final Color descriptionColor;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _OrderTypeCard({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.backgroundColor,
|
||||
required this.borderColor,
|
||||
required this.iconBackgroundColor,
|
||||
required this.iconColor,
|
||||
required this.textColor,
|
||||
required this.descriptionColor,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(color: borderColor, width: 2),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
margin: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
color: iconBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
),
|
||||
child: Icon(icon, color: iconColor, size: 24),
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: UiTypography.body2b.copyWith(color: textColor),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
description,
|
||||
style: UiTypography.footnote1r.copyWith(color: descriptionColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
class OneTimeOrderPage extends StatelessWidget {
|
||||
const OneTimeOrderPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: AppBar(
|
||||
title:
|
||||
Text('One-Time Order', style: UiTypography.headline3m.textPrimary),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
backgroundColor: UiColors.white,
|
||||
elevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Text('One-Time Order Flow (WIP)',
|
||||
style: UiTypography.body1r.textSecondary),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
class PermanentOrderPage extends StatelessWidget {
|
||||
const PermanentOrderPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: AppBar(
|
||||
title:
|
||||
Text('Permanent Order', style: UiTypography.headline3m.textPrimary),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
backgroundColor: UiColors.white,
|
||||
elevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Text('Permanent Order Flow (WIP)',
|
||||
style: UiTypography.body1r.textSecondary),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
class RapidOrderPage extends StatelessWidget {
|
||||
const RapidOrderPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: AppBar(
|
||||
title: Text('Rapid Order', style: UiTypography.headline3m.textPrimary),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
backgroundColor: UiColors.white,
|
||||
elevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Text('Rapid Order Flow (WIP)',
|
||||
style: UiTypography.body1r.textSecondary),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
class RecurringOrderPage extends StatelessWidget {
|
||||
const RecurringOrderPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: AppBar(
|
||||
title:
|
||||
Text('Recurring Order', style: UiTypography.headline3m.textPrimary),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
backgroundColor: UiColors.white,
|
||||
elevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Text('Recurring Order Flow (WIP)',
|
||||
style: UiTypography.body1r.textSecondary),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
851
apps/mobile/packages/features/client/create_order/pubspec.lock
Normal file
851
apps/mobile/packages/features/client/create_order/pubspec.lock
Normal file
@@ -0,0 +1,851 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "91.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.4.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
auto_injector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: auto_injector
|
||||
sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
bloc:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bloc
|
||||
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.4"
|
||||
bloc_test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: bloc_test
|
||||
sha256: "165a6ec950d9252ebe36dc5335f2e6eb13055f33d56db0eeb7642768849b43d2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.7"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
core_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../core_localization"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
csv:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csv
|
||||
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
design_system:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../design_system"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
diff_match_patch:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: diff_match_patch
|
||||
sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_bloc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_bloc
|
||||
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.6"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_modular:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_modular
|
||||
sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
font_awesome_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: font_awesome_flutter
|
||||
sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.12.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
google_fonts:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: "6996212014b996eaa17074e02b1b925b212f5e053832d9048970dc27255a8fb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
krow_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "../../../core"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
krow_domain:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../domain"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
lucide_icons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lucide_icons
|
||||
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.257.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mocktail:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mocktail
|
||||
sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
modular_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: modular_core
|
||||
sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_c
|
||||
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.4"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "9922a1ad59ac5afb154cc948aa6ded01987a75003651d0a2866afc23f4da624e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.3"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5+1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
result_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: result_dart
|
||||
sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.18"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
slang:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: slang
|
||||
sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
slang_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: slang_flutter
|
||||
sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.26.3"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.12"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.10.7 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
@@ -0,0 +1,25 @@
|
||||
name: client_create_order
|
||||
description: Client create order feature
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_bloc: ^8.1.3
|
||||
flutter_modular: ^6.3.2
|
||||
equatable: ^2.0.5
|
||||
design_system:
|
||||
path: ../../../design_system
|
||||
core_localization:
|
||||
path: ../../../core_localization
|
||||
krow_domain:
|
||||
path: ../../../domain
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
bloc_test: ^9.1.5
|
||||
@@ -1,133 +0,0 @@
|
||||
# Client Home Feature - Architecture Refactor Summary
|
||||
|
||||
## ✅ Completed Refactor
|
||||
|
||||
The `packages/features/client/home` feature has been successfully refactored to fully comply with KROW Clean Architecture principles.
|
||||
|
||||
## 📋 Changes Made
|
||||
|
||||
### 1. Domain Layer Improvements
|
||||
|
||||
**Created:**
|
||||
- `lib/src/domain/entities/home_dashboard_data.dart`
|
||||
- Proper domain entity to replace raw `Map<String, dynamic>`
|
||||
- Immutable, equatable data class
|
||||
- Clear field definitions with documentation
|
||||
|
||||
**Updated:**
|
||||
- `lib/src/domain/repositories/home_repository_interface.dart`
|
||||
- Changed from `abstract class` to `abstract interface class`
|
||||
- Return type changed from `Map<String, dynamic>` to `HomeDashboardData`
|
||||
|
||||
- `lib/src/domain/usecases/get_dashboard_data_usecase.dart`
|
||||
- Return type updated to `HomeDashboardData`
|
||||
|
||||
### 2. Data Layer Improvements
|
||||
|
||||
**Updated:**
|
||||
- `lib/src/data/repositories_impl/home_repository_impl.dart`
|
||||
- Returns `HomeDashboardData` entity instead of raw map
|
||||
- Properly typed mock data
|
||||
|
||||
### 3. Presentation Layer Refactor
|
||||
|
||||
**Major Changes to `client_home_page.dart`:**
|
||||
- ✅ Converted from `StatefulWidget` to `StatelessWidget`
|
||||
- ✅ Removed local state management (moved to BLoC)
|
||||
- ✅ BLoC lifecycle managed by `BlocProvider.create`
|
||||
- ✅ All event dispatching uses `BlocProvider.of<ClientHomeBloc>(context)`
|
||||
- ✅ Removed direct BLoC instance storage
|
||||
- ✅ Fixed deprecated `withOpacity` → `withValues(alpha:)`
|
||||
|
||||
**Updated `client_home_state.dart`:**
|
||||
- Replaced individual primitive fields with `HomeDashboardData` entity
|
||||
- Simplified state structure
|
||||
- Cleaner `copyWith` implementation
|
||||
|
||||
**Updated `client_home_bloc.dart`:**
|
||||
- Simplified event handler to use entity directly
|
||||
- No more manual field extraction from maps
|
||||
|
||||
**Widget Updates:**
|
||||
- `coverage_widget.dart`: Now accepts typed parameters
|
||||
- All widgets: Fixed deprecated `withOpacity` calls
|
||||
- `shift_order_form_sheet.dart`: Fixed deprecated `value` → `initialValue`
|
||||
|
||||
## 🎯 Architecture Compliance
|
||||
|
||||
### ✅ Clean Architecture Rules
|
||||
- [x] Domain layer is pure Dart (entities only)
|
||||
- [x] Repository interfaces in domain, implementations in data
|
||||
- [x] Use cases properly delegate to repositories
|
||||
- [x] Presentation layer depends on domain abstractions
|
||||
- [x] No feature-to-feature imports
|
||||
|
||||
### ✅ Presentation Rules
|
||||
- [x] Page is `StatelessWidget`
|
||||
- [x] State managed by BLoC
|
||||
- [x] No business logic in page
|
||||
- [x] BLoC lifecycle properly managed
|
||||
- [x] Named parameters used throughout
|
||||
|
||||
### ✅ Code Quality
|
||||
- [x] No deprecation warnings
|
||||
- [x] All files have doc comments
|
||||
- [x] Consistent naming conventions
|
||||
- [x] `flutter analyze` passes with 0 issues
|
||||
|
||||
## 📊 Before vs After
|
||||
|
||||
### Before:
|
||||
```dart
|
||||
// StatefulWidget with local state
|
||||
class ClientHomePage extends StatefulWidget {
|
||||
late final ClientHomeBloc _homeBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_homeBloc = Modular.get<ClientHomeBloc>();
|
||||
}
|
||||
}
|
||||
|
||||
// Raw maps in domain
|
||||
Future<Map<String, dynamic>> getDashboardData();
|
||||
|
||||
// Manual field extraction
|
||||
weeklySpending: data['weeklySpending'] as double?,
|
||||
```
|
||||
|
||||
### After:
|
||||
```dart
|
||||
// StatelessWidget, BLoC-managed
|
||||
class ClientHomePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ClientHomeBloc>(
|
||||
create: (context) => Modular.get<ClientHomeBloc>()..add(ClientHomeStarted()),
|
||||
// ...
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Typed entities
|
||||
Future<HomeDashboardData> getDashboardData();
|
||||
|
||||
// Direct entity usage
|
||||
dashboardData: data,
|
||||
```
|
||||
|
||||
## 🔍 Reference Alignment
|
||||
|
||||
The refactored code now matches the structure of `packages/features/staff/authentication`:
|
||||
- StatelessWidget pages
|
||||
- BLoC-managed state
|
||||
- Typed domain entities
|
||||
- Clean separation of concerns
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
The feature is now production-ready and follows all architectural guidelines. Future enhancements should:
|
||||
1. Add unit tests for use cases
|
||||
2. Add widget tests for pages
|
||||
3. Add integration tests for complete flows
|
||||
4. Consider extracting reusable widgets to design_system if used across features
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
/// Extension on [IModularNavigator] to provide strongly-typed navigation
|
||||
/// for the client home feature.
|
||||
extension ClientHomeNavigator on IModularNavigator {
|
||||
/// Navigates to the client home page.
|
||||
void pushClientHome() {
|
||||
pushNamed('/client-home/');
|
||||
}
|
||||
|
||||
/// Navigates to the settings page.
|
||||
void pushSettings() {
|
||||
pushNamed('/client-settings/');
|
||||
}
|
||||
|
||||
void pushCreateOrder() {
|
||||
pushNamed('/client/create-order/');
|
||||
}
|
||||
|
||||
void pushRapidOrder() {
|
||||
pushNamed('/client/create-order/rapid');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,8 +324,8 @@ class ClientHomePage extends StatelessWidget {
|
||||
switch (id) {
|
||||
case 'actions':
|
||||
return ActionsWidget(
|
||||
onRapidPressed: () {},
|
||||
onCreateOrderPressed: () => _openOrderFormSheet(context, null),
|
||||
onRapidPressed: () => Modular.to.pushRapidOrder(),
|
||||
onCreateOrderPressed: () => Modular.to.pushCreateOrder(),
|
||||
);
|
||||
case 'reorder':
|
||||
return ReorderWidget(
|
||||
|
||||
@@ -16,6 +16,7 @@ dependencies:
|
||||
equatable: ^2.0.5
|
||||
lucide_icons: ^0.257.0
|
||||
|
||||
# Architecture Packages
|
||||
design_system:
|
||||
path: ../../../design_system
|
||||
core_localization:
|
||||
|
||||
@@ -2,6 +2,7 @@ library client_hubs;
|
||||
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'src/data/repositories_impl/hub_repository_impl.dart';
|
||||
import 'src/domain/repositories/hub_repository_interface.dart';
|
||||
import 'src/domain/usecases/assign_nfc_tag_usecase.dart';
|
||||
@@ -22,7 +23,10 @@ class ClientHubsModule extends Module {
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.addLazySingleton<HubRepositoryInterface>(
|
||||
() => HubRepositoryImpl(mock: i.get<BusinessRepositoryMock>()),
|
||||
() => HubRepositoryImpl(
|
||||
firebaseAuth: firebase.FirebaseAuth.instance,
|
||||
dataConnect: ExampleConnector.instance,
|
||||
),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
|
||||
@@ -1,45 +1,151 @@
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import '../../domain/repositories/hub_repository_interface.dart';
|
||||
|
||||
/// Implementation of [HubRepositoryInterface] that uses [BusinessRepositoryMock].
|
||||
///
|
||||
/// This class serves as a data adapter that bridges the domain repository interface
|
||||
/// with the backend data source provided by the `data_connect` package. It strictly
|
||||
/// delegates all operations to the [BusinessRepositoryMock], ensuring no business
|
||||
/// logic resides in the data layer.
|
||||
/// Implementation of [HubRepositoryInterface] backed by Data Connect.
|
||||
class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
/// The business repository mock from data connect.
|
||||
final BusinessRepositoryMock mock;
|
||||
final firebase.FirebaseAuth _firebaseAuth;
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
|
||||
/// Creates a [HubRepositoryImpl] instance.
|
||||
///
|
||||
/// Takes a [BusinessRepositoryMock] as a dependency to perform data operations.
|
||||
HubRepositoryImpl({required this.mock});
|
||||
HubRepositoryImpl({
|
||||
required firebase.FirebaseAuth firebaseAuth,
|
||||
required dc.ExampleConnector dataConnect,
|
||||
}) : _firebaseAuth = firebaseAuth,
|
||||
_dataConnect = dataConnect;
|
||||
|
||||
@override
|
||||
Future<List<Hub>> getHubs() {
|
||||
// In a production environment, the business ID would be retrieved from
|
||||
// a session manager or authentication state. For the current mock strategy,
|
||||
// we use a hardcoded value 'biz_1' to represent the active client.
|
||||
return mock.getHubs('biz_1');
|
||||
Future<List<domain.Hub>> getHubs() async {
|
||||
final business = await _getBusinessForCurrentUser();
|
||||
final teamId = await _getOrCreateTeamId(business);
|
||||
return _fetchHubsForTeam(teamId: teamId, businessId: business.id);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Hub> createHub({required String name, required String address}) {
|
||||
// Delegates hub creation to the mock repository.
|
||||
return mock.createHub(businessId: 'biz_1', name: name, address: address);
|
||||
Future<domain.Hub> createHub({
|
||||
required String name,
|
||||
required String address,
|
||||
}) async {
|
||||
final business = await _getBusinessForCurrentUser();
|
||||
final teamId = await _getOrCreateTeamId(business);
|
||||
final city = business.city;
|
||||
|
||||
final result = await _dataConnect
|
||||
.createTeamHub(
|
||||
teamId: teamId,
|
||||
hubName: name,
|
||||
address: address,
|
||||
)
|
||||
.city(city?.isNotEmpty == true ? city : '')
|
||||
.execute();
|
||||
final createdId = result.data?.teamHub_insert.id;
|
||||
if (createdId == null) {
|
||||
throw Exception('Hub creation failed.');
|
||||
}
|
||||
|
||||
final hubs = await _fetchHubsForTeam(
|
||||
teamId: teamId,
|
||||
businessId: business.id,
|
||||
);
|
||||
domain.Hub? createdHub;
|
||||
for (final hub in hubs) {
|
||||
if (hub.id == createdId) {
|
||||
createdHub = hub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return createdHub ??
|
||||
domain.Hub(
|
||||
id: createdId,
|
||||
businessId: business.id,
|
||||
name: name,
|
||||
address: address,
|
||||
nfcTagId: null,
|
||||
status: domain.HubStatus.active,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteHub(String id) {
|
||||
// Delegates hub deletion to the mock repository.
|
||||
return mock.deleteHub(id);
|
||||
Future<void> deleteHub(String id) async {
|
||||
await _dataConnect.deleteTeamHub(id: id).execute();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> assignNfcTag({required String hubId, required String nfcTagId}) {
|
||||
// Delegates NFC tag assignment to the mock repository.
|
||||
return mock.assignNfcTag(hubId: hubId, nfcTagId: nfcTagId);
|
||||
Future<void> assignNfcTag({
|
||||
required String hubId,
|
||||
required String nfcTagId,
|
||||
}) {
|
||||
throw UnimplementedError('NFC tag assignment is not supported for team hubs.');
|
||||
}
|
||||
|
||||
Future<dc.GetBusinessesByUserIdBusinesses> _getBusinessForCurrentUser() async {
|
||||
final user = _firebaseAuth.currentUser;
|
||||
if (user == null) {
|
||||
throw Exception('User is not authenticated.');
|
||||
}
|
||||
|
||||
final result = await _dataConnect.getBusinessesByUserId(
|
||||
userId: user.uid,
|
||||
).execute();
|
||||
if (result.data.businesses.isEmpty) {
|
||||
await _firebaseAuth.signOut();
|
||||
throw Exception('No business found for this user. Please sign in again.');
|
||||
}
|
||||
|
||||
return result.data.businesses.first;
|
||||
}
|
||||
|
||||
Future<String> _getOrCreateTeamId(
|
||||
dc.GetBusinessesByUserIdBusinesses business,
|
||||
) async {
|
||||
final teamsResult = await _dataConnect.getTeamsByOwnerId(
|
||||
ownerId: business.id,
|
||||
).execute();
|
||||
if (teamsResult.data.teams.isNotEmpty) {
|
||||
return teamsResult.data.teams.first.id;
|
||||
}
|
||||
|
||||
final createTeamBuilder = _dataConnect.createTeam(
|
||||
teamName: '${business.businessName} Team',
|
||||
ownerId: business.id,
|
||||
ownerName: business.contactName ?? '',
|
||||
ownerRole: 'OWNER',
|
||||
);
|
||||
if (business.email != null) {
|
||||
createTeamBuilder.email(business.email);
|
||||
}
|
||||
|
||||
final createTeamResult = await createTeamBuilder.execute();
|
||||
final teamId = createTeamResult.data?.team_insert.id;
|
||||
if (teamId == null) {
|
||||
throw Exception('Team creation failed.');
|
||||
}
|
||||
|
||||
return teamId;
|
||||
}
|
||||
|
||||
Future<List<domain.Hub>> _fetchHubsForTeam({
|
||||
required String teamId,
|
||||
required String businessId,
|
||||
}) async {
|
||||
final hubsResult = await _dataConnect.getTeamHubsByTeamId(
|
||||
teamId: teamId,
|
||||
).execute();
|
||||
|
||||
return hubsResult.data.teamHubs
|
||||
.map(
|
||||
(hub) => domain.Hub(
|
||||
id: hub.id,
|
||||
businessId: businessId,
|
||||
name: hub.hubName,
|
||||
address: hub.address,
|
||||
nfcTagId: null,
|
||||
status:
|
||||
hub.isActive
|
||||
? domain.HubStatus.active
|
||||
: domain.HubStatus.inactive,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import '../blocs/client_hubs_bloc.dart';
|
||||
import '../blocs/client_hubs_event.dart';
|
||||
@@ -47,14 +46,26 @@ class ClientHubsPage extends StatelessWidget {
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8FAFC), // slate-50
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsAddDialogToggled(visible: true)),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: const Icon(UiIcons.add),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
CustomScrollView(
|
||||
slivers: [
|
||||
slivers: <Widget>[
|
||||
_buildAppBar(context),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 100),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space5,
|
||||
).copyWith(bottom: 100),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
if (state.status == ClientHubsStatus.loading)
|
||||
@@ -85,7 +96,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
const HubInfoCard(),
|
||||
]),
|
||||
),
|
||||
@@ -120,7 +131,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
if (state.status == ClientHubsStatus.actionInProgress)
|
||||
Container(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
color: UiColors.black.withValues(alpha: 0.1),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
],
|
||||
@@ -133,20 +144,19 @@ class ClientHubsPage extends StatelessWidget {
|
||||
|
||||
Widget _buildAppBar(BuildContext context) {
|
||||
return SliverAppBar(
|
||||
backgroundColor: const Color(0xFF121826),
|
||||
backgroundColor: UiColors.foreground, // Dark Slate equivalent
|
||||
automaticallyImplyLeading: false,
|
||||
expandedHeight: 140,
|
||||
pinned: true,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Color(0xFF121826), Color(0xFF1E293B)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
color: UiColors.foreground,
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space12,
|
||||
UiConstants.space5,
|
||||
0,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(20, 48, 20, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -156,51 +166,35 @@ class ClientHubsPage extends StatelessWidget {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.arrowLeft,
|
||||
color: Colors.white,
|
||||
UiIcons.arrowLeft,
|
||||
color: UiColors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_hubs.title,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24,
|
||||
),
|
||||
style: UiTypography.headline1m.white,
|
||||
),
|
||||
Text(
|
||||
t.client_hubs.subtitle,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFCBD5E1), // slate-300
|
||||
fontSize: 14,
|
||||
style: UiTypography.body2r.copyWith(
|
||||
color: UiColors.switchInactive,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
UiButton.primary(
|
||||
onPressed: () => BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsAddDialogToggled(visible: true)),
|
||||
text: t.client_hubs.add_hub,
|
||||
leadingIcon: LucideIcons.plus,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: Size(0, 40),
|
||||
maximumSize: Size.fromHeight(40),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -42,20 +42,17 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
color: UiColors.bgOverlay,
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
padding: const EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 20,
|
||||
),
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: UiColors.popupShadow, blurRadius: 20),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
@@ -64,29 +61,27 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
children: [
|
||||
Text(
|
||||
t.client_hubs.add_hub_dialog.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
_buildFieldLabel(t.client_hubs.add_hub_dialog.name_label),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
style: UiTypography.body1r.textPrimary,
|
||||
decoration: _buildInputDecoration(
|
||||
t.client_hubs.add_hub_dialog.name_hint,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
_buildFieldLabel(t.client_hubs.add_hub_dialog.address_label),
|
||||
TextField(
|
||||
controller: _addressController,
|
||||
style: UiTypography.body1r.textPrimary,
|
||||
decoration: _buildInputDecoration(
|
||||
t.client_hubs.add_hub_dialog.address_hint,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -95,7 +90,7 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
text: t.common.cancel,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: UiButton.primary(
|
||||
onPressed: () {
|
||||
@@ -121,36 +116,32 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
|
||||
Widget _buildFieldLabel(String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space2),
|
||||
child: Text(label, style: UiTypography.body2m.textPrimary),
|
||||
);
|
||||
}
|
||||
|
||||
InputDecoration _buildInputDecoration(String hint) {
|
||||
return InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 14),
|
||||
hintStyle: UiTypography.body2r.textPlaceholder,
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFF8FAFC),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
fillColor: UiColors.input,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: 14,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
borderSide: const BorderSide(color: UiColors.border),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
borderSide: const BorderSide(color: UiColors.border),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Color(0xFF2563EB), width: 2),
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
borderSide: const BorderSide(color: UiColors.ring, width: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
/// A card displaying information about a single hub.
|
||||
@@ -27,68 +27,56 @@ class HubCard extends StatelessWidget {
|
||||
final bool hasNfc = hub.nfcTagId != null;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
margin: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
color: UiColors.popupShadow,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF6FF), // blue-50
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: UiColors.tagInProgress,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
),
|
||||
child: Icon(
|
||||
hasNfc ? LucideIcons.checkCircle : LucideIcons.nfc,
|
||||
color: hasNfc
|
||||
? const Color(0xFF16A34A)
|
||||
: const Color(0xFF94A3B8), // green-600 or slate-400
|
||||
hasNfc ? UiIcons.success : UiIcons.nfc,
|
||||
color: hasNfc ? UiColors.iconSuccess : UiColors.iconThird,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const SizedBox(width: UiConstants.space4),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
hub.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
Text(hub.name, style: UiTypography.body1b.textPrimary),
|
||||
if (hub.address.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
padding: const EdgeInsets.only(top: UiConstants.space1),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
LucideIcons.mapPin,
|
||||
UiIcons.mapPin,
|
||||
size: 12,
|
||||
color: Color(0xFF94A3B8),
|
||||
color: UiColors.iconThird,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Expanded(
|
||||
child: Text(
|
||||
hub.address,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B),
|
||||
fontSize: 12,
|
||||
),
|
||||
style: UiTypography.footnote1r.textSecondary,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -98,14 +86,12 @@ class HubCard extends StatelessWidget {
|
||||
),
|
||||
if (hasNfc)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
padding: const EdgeInsets.only(top: UiConstants.space1),
|
||||
child: Text(
|
||||
t.client_hubs.hub_card.tag_label(id: hub.nfcTagId!),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF16A34A),
|
||||
fontSize: 12,
|
||||
style: UiTypography.footnote1b.copyWith(
|
||||
color: UiColors.textSuccess,
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -117,20 +103,20 @@ class HubCard extends StatelessWidget {
|
||||
IconButton(
|
||||
onPressed: onNfcPressed,
|
||||
icon: const Icon(
|
||||
LucideIcons.nfc,
|
||||
color: Color(0xFF2563EB),
|
||||
UiIcons.nfc,
|
||||
color: UiColors.primary,
|
||||
size: 20,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
splashRadius: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
IconButton(
|
||||
onPressed: onDeletePressed,
|
||||
icon: const Icon(
|
||||
LucideIcons.trash2,
|
||||
color: Color(0xFFDC2626),
|
||||
UiIcons.delete,
|
||||
color: UiColors.destructive,
|
||||
size: 20,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
/// Widget displayed when there are no hubs.
|
||||
@@ -14,15 +13,15 @@ class HubEmptyState extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(32),
|
||||
padding: const EdgeInsets.all(UiConstants.space8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
color: UiColors.bgBanner,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
color: UiColors.popupShadow,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -32,35 +31,27 @@ class HubEmptyState extends StatelessWidget {
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF1F5F9), // slate-100
|
||||
color: UiColors.secondary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.nfc,
|
||||
size: 32,
|
||||
color: Color(0xFF94A3B8),
|
||||
),
|
||||
child: const Icon(UiIcons.nfc, size: 32, color: UiColors.iconThird),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(
|
||||
t.client_hubs.empty_state.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
style: UiTypography.title1m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(
|
||||
t.client_hubs.empty_state.description,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Color(0xFF64748B), fontSize: 14),
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
UiButton.primary(
|
||||
onPressed: onAddPressed,
|
||||
text: t.client_hubs.empty_state.button,
|
||||
leadingIcon: LucideIcons.plus,
|
||||
leadingIcon: UiIcons.add,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
/// A card with information about how hubs work.
|
||||
@@ -10,34 +10,29 @@ class HubInfoCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF6FF), // blue-50
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: UiColors.tagInProgress,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(LucideIcons.nfc, size: 20, color: Color(0xFF2563EB)),
|
||||
const SizedBox(width: 12),
|
||||
const Icon(UiIcons.nfc, size: 20, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
t.client_hubs.about_hubs.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
style: UiTypography.body2b.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
t.client_hubs.about_hubs.description,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF334155),
|
||||
fontSize: 12,
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
@@ -40,20 +39,17 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
color: UiColors.bgOverlay,
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
padding: const EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 20,
|
||||
),
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: UiColors.popupShadow, blurRadius: 20),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
@@ -61,57 +57,45 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
children: [
|
||||
Text(
|
||||
t.client_hubs.nfc_dialog.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFEFF6FF), // blue-50
|
||||
color: UiColors.tagInProgress,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.nfc,
|
||||
UiIcons.nfc,
|
||||
size: 40,
|
||||
color: Color(0xFF2563EB),
|
||||
color: UiColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
widget.hub.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(widget.hub.name, style: UiTypography.body1b.textPrimary),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(
|
||||
t.client_hubs.nfc_dialog.instruction,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B),
|
||||
fontSize: 14,
|
||||
),
|
||||
style: UiTypography.body2m.textSecondary,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
UiButton.secondary(
|
||||
onPressed: _simulateNFCScan,
|
||||
text: t.client_hubs.nfc_dialog.scan_button,
|
||||
leadingIcon: LucideIcons.nfc,
|
||||
leadingIcon: UiIcons.nfc,
|
||||
),
|
||||
if (_nfcTagId != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0FDF4), // green-50
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: UiColors.tagSuccess,
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -119,39 +103,35 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
LucideIcons.checkCircle,
|
||||
UiIcons.success,
|
||||
size: 20,
|
||||
color: Color(0xFF16A34A),
|
||||
color: UiColors.textSuccess,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Text(
|
||||
t.client_hubs.nfc_dialog.tag_identified,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
style: UiTypography.body2b.textPrimary,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
horizontal: UiConstants.space3,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFDCE8E0)),
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusMdValue + 2,
|
||||
),
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Text(
|
||||
_nfcTagId!,
|
||||
style: const TextStyle(
|
||||
style: UiTypography.footnote1b.copyWith(
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
color: Color(0xFF334155),
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -159,7 +139,7 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 32),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -168,7 +148,7 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
text: t.common.cancel,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: UiButton.primary(
|
||||
onPressed: _nfcTagId != null
|
||||
|
||||
@@ -16,7 +16,7 @@ dependencies:
|
||||
equatable: ^2.0.5
|
||||
lucide_icons: ^0.257.0
|
||||
|
||||
# KROW Packages
|
||||
# Architecture Packages
|
||||
krow_core:
|
||||
path: ../../../core
|
||||
krow_domain:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'src/data/repositories_impl/settings_repository_impl.dart';
|
||||
import 'src/domain/repositories/settings_repository_interface.dart';
|
||||
import 'src/domain/usecases/sign_out_usecase.dart';
|
||||
@@ -8,14 +8,11 @@ import 'src/presentation/pages/client_settings_page.dart';
|
||||
|
||||
/// A [Module] for the client settings feature.
|
||||
class ClientSettingsModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [DataConnectModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.addLazySingleton<SettingsRepositoryInterface>(
|
||||
() => SettingsRepositoryImpl(mock: i.get<AuthRepositoryMock>()),
|
||||
() => SettingsRepositoryImpl(firebaseAuth: FirebaseAuth.instance),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import '../../domain/repositories/settings_repository_interface.dart';
|
||||
|
||||
/// Implementation of [SettingsRepositoryInterface].
|
||||
///
|
||||
/// This implementation delegates data access to the [AuthRepositoryMock]
|
||||
/// from the `data_connect` package.
|
||||
/// This implementation delegates authentication operations to [FirebaseAuth].
|
||||
class SettingsRepositoryImpl implements SettingsRepositoryInterface {
|
||||
/// The auth mock from data connect.
|
||||
final AuthRepositoryMock mock;
|
||||
/// The Firebase Auth instance.
|
||||
final FirebaseAuth _firebaseAuth;
|
||||
|
||||
/// Creates a [SettingsRepositoryImpl] with the required [mock].
|
||||
SettingsRepositoryImpl({required this.mock});
|
||||
/// Creates a [SettingsRepositoryImpl] with the required [_firebaseAuth].
|
||||
SettingsRepositoryImpl({required FirebaseAuth firebaseAuth})
|
||||
: _firebaseAuth = firebaseAuth;
|
||||
|
||||
@override
|
||||
Future<void> signOut() {
|
||||
return mock.signOut();
|
||||
Future<void> signOut() async {
|
||||
try {
|
||||
await _firebaseAuth.signOut();
|
||||
} catch (e) {
|
||||
throw Exception('Error signing out: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,16 @@ class ClientSettingsPage extends StatelessWidget {
|
||||
const ClientSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
/// Builds the client settings page UI.
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ClientSettingsBloc>(
|
||||
create: (context) => Modular.get<ClientSettingsBloc>(),
|
||||
child: BlocListener<ClientSettingsBloc, ClientSettingsState>(
|
||||
listener: (context, state) {
|
||||
if (state is ClientSettingsSignOutSuccess) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Signed out successfully')),
|
||||
);
|
||||
Modular.to.navigate('/');
|
||||
}
|
||||
if (state is ClientSettingsError) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:client_settings/src/presentation/navigation/client_settings_navigator.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import '../../blocs/client_settings_bloc.dart';
|
||||
|
||||
/// A widget that displays the primary actions for the settings page.
|
||||
@@ -10,27 +12,38 @@ class SettingsActions extends StatelessWidget {
|
||||
const SettingsActions({super.key});
|
||||
|
||||
@override
|
||||
/// Builds the settings actions UI.
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
// Get the translations for the client settings profile.
|
||||
final TranslationsClientSettingsProfileEn labels =
|
||||
t.client_settings.profile;
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
delegate: SliverChildListDelegate(<Widget>[
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
|
||||
// Edit profile button
|
||||
UiButton.primary(text: labels.edit_profile, onPressed: () {}),
|
||||
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
UiButton.primary(text: labels.hubs, onPressed: () {}),
|
||||
|
||||
// Hubs button
|
||||
UiButton.primary(
|
||||
text: labels.hubs,
|
||||
onPressed: () => Modular.to.pushHubs(),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Log out button
|
||||
BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, ClientSettingsState state) {
|
||||
return UiButton.secondary(
|
||||
text: labels.log_out,
|
||||
onPressed: state is ClientSettingsLoading
|
||||
? null
|
||||
: () => BlocProvider.of<ClientSettingsBloc>(
|
||||
context,
|
||||
).add(const ClientSettingsSignOutRequested()),
|
||||
: () => _showSignOutDialog(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -38,4 +51,42 @@ class SettingsActions extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Shows a confirmation dialog for signing out.
|
||||
Future<void> _showSignOutDialog(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
backgroundColor: UiColors.bgPopup,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
|
||||
title: Text(
|
||||
t.client_settings.profile.log_out,
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
),
|
||||
content: Text(
|
||||
'Are you sure you want to log out?',
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
actions: <Widget>[
|
||||
// Log out button
|
||||
UiButton.secondary(
|
||||
text: t.client_settings.profile.log_out,
|
||||
onPressed: () {
|
||||
Modular.to.pop();
|
||||
BlocProvider.of<ClientSettingsBloc>(
|
||||
context,
|
||||
).add(const ClientSettingsSignOutRequested());
|
||||
},
|
||||
),
|
||||
|
||||
// Cancel button
|
||||
UiButton.secondary(
|
||||
text: t.common.cancel,
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,68 +9,66 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
const SettingsProfileHeader({super.key});
|
||||
|
||||
@override
|
||||
/// Builds the profile header UI.
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
|
||||
return SliverAppBar(
|
||||
backgroundColor: UiColors.primary,
|
||||
expandedHeight: 200,
|
||||
backgroundColor: UiColors.bgSecondary,
|
||||
expandedHeight: 140,
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
shape: const Border(bottom: BorderSide(color: UiColors.border, width: 1)),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [UiColors.primary, Color(0xFF0047FF)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space8),
|
||||
margin: const EdgeInsets.only(top: UiConstants.space16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: UiConstants.space4,
|
||||
children: [
|
||||
const SizedBox(height: UiConstants.space5 * 2),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: UiColors.white.withValues(alpha: 0.24),
|
||||
width: 4,
|
||||
),
|
||||
border: Border.all(color: UiColors.border, width: 2),
|
||||
color: UiColors.white,
|
||||
),
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'C',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
style: UiTypography.headline1m.copyWith(
|
||||
color: UiColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Text(
|
||||
'Your Company',
|
||||
style: UiTypography.body1b.copyWith(color: UiColors.white),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
UiIcons.mail,
|
||||
size: 14,
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Text(
|
||||
'client@example.com',
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
Text('Your Company', style: UiTypography.body1b.textPrimary),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: UiConstants.space1,
|
||||
children: [
|
||||
Icon(
|
||||
UiIcons.mail,
|
||||
size: 14,
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
Text(
|
||||
'client@example.com',
|
||||
style: UiTypography.footnote1r.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -78,14 +76,7 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.arrowLeft, color: UiColors.white),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
title: Text(
|
||||
labels.title,
|
||||
style: UiTypography.body1b.copyWith(color: UiColors.white),
|
||||
),
|
||||
title: Text(labels.title, style: UiTypography.body1b.textPrimary),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ class SettingsQuickLinks extends StatelessWidget {
|
||||
const SettingsQuickLinks({super.key});
|
||||
|
||||
@override
|
||||
/// Builds the quick links UI.
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
|
||||
@@ -54,10 +55,16 @@ class SettingsQuickLinks extends StatelessWidget {
|
||||
|
||||
/// Internal widget for a single quick link item.
|
||||
class _QuickLinkItem extends StatelessWidget {
|
||||
/// The icon to display.
|
||||
final IconData icon;
|
||||
|
||||
/// The title of the link.
|
||||
final String title;
|
||||
|
||||
/// Callback when the link is tapped.
|
||||
final VoidCallback onTap;
|
||||
|
||||
/// Creates a [_QuickLinkItem].
|
||||
const _QuickLinkItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
@@ -65,6 +72,7 @@ class _QuickLinkItem extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
/// Builds the quick link item UI.
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
|
||||
@@ -15,7 +15,9 @@ dependencies:
|
||||
flutter_modular: ^6.3.0
|
||||
equatable: ^2.0.5
|
||||
lucide_icons: ^0.257.0
|
||||
firebase_auth: ^6.1.2
|
||||
|
||||
# Architecture Packages
|
||||
design_system:
|
||||
path: ../../../design_system
|
||||
core_localization:
|
||||
|
||||
@@ -185,6 +185,13 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
client_create_order:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "packages/features/client/create_order"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -415,7 +422,7 @@ packages:
|
||||
source: hosted
|
||||
version: "8.1.6"
|
||||
flutter_lints:
|
||||
dependency: transitive
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
|
||||
@@ -20,6 +20,7 @@ workspace:
|
||||
|
||||
dev_dependencies:
|
||||
melos: ^7.3.0
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
melos:
|
||||
scripts:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
connectorId: example
|
||||
generate:
|
||||
dartSdk:
|
||||
- outputDir: ../../../apps/packages/data_connect/lib/src/dataconnect_generated
|
||||
- outputDir: ../../../apps/mobile/packages/data_connect/lib/src/dataconnect_generated
|
||||
package: dataconnect_generated/generated.dart
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mutation createTeam(
|
||||
$teamName: String!
|
||||
$ownerId: String!
|
||||
$ownerId: UUID!
|
||||
$ownerName: String!
|
||||
$ownerRole: String!
|
||||
$email: String
|
||||
|
||||
@@ -44,7 +44,7 @@ query getTeamById($id: UUID!) @auth(level: USER) {
|
||||
}
|
||||
}
|
||||
|
||||
query getTeamsByOwnerId($ownerId: String!) @auth(level: USER) {
|
||||
query getTeamsByOwnerId($ownerId: UUID!) @auth(level: USER) {
|
||||
teams(where: { ownerId: { eq: $ownerId } }) {
|
||||
id
|
||||
teamName
|
||||
|
||||
@@ -2,10 +2,10 @@ mutation createTeamHub(
|
||||
$teamId: UUID!
|
||||
$hubName: String!
|
||||
$address: String!
|
||||
$city: String!
|
||||
$state: String!
|
||||
$zipCode: String!
|
||||
$managerName: String!
|
||||
$city: String
|
||||
$state: String
|
||||
$zipCode: String
|
||||
$managerName: String
|
||||
$isActive: Boolean
|
||||
$departments: Any
|
||||
) @auth(level: USER) {
|
||||
|
||||
@@ -56,7 +56,7 @@ query getTeamHubsByTeamId($teamId: UUID!) @auth(level: USER) {
|
||||
# LIST TEAM HUBS BY OWNER (Vendor/Business)
|
||||
# ------------------------------------------------------------
|
||||
query listTeamHubsByOwnerId(
|
||||
$ownerId: String!
|
||||
$ownerId: UUID!
|
||||
) @auth(level: USER) {
|
||||
teamHubs(
|
||||
where: {
|
||||
|
||||
@@ -3,7 +3,7 @@ type Team @table(name: "teams") {
|
||||
id: UUID! @default(expr: "uuidV4()")
|
||||
teamName: String!
|
||||
|
||||
ownerId: String! #vendor/business
|
||||
ownerId: UUID! #vendor/business
|
||||
ownerName: String!
|
||||
ownerRole: String!
|
||||
email: String
|
||||
|
||||
@@ -6,10 +6,10 @@ type TeamHub @table(name: "team_hubs") {
|
||||
|
||||
hubName: String!
|
||||
address: String!
|
||||
city: String!
|
||||
state: String!
|
||||
zipCode: String!
|
||||
managerName: String!
|
||||
city: String
|
||||
state: String
|
||||
zipCode: String
|
||||
managerName: String
|
||||
isActive: Boolean! @default(expr: "true")
|
||||
departments: Any
|
||||
|
||||
|
||||
Reference in New Issue
Block a user