Skip to content

Agent screen#26

Closed
Khallad1424 wants to merge 3 commits intomainfrom
Agent_Screen
Closed

Agent screen#26
Khallad1424 wants to merge 3 commits intomainfrom
Agent_Screen

Conversation

@Khallad1424
Copy link
Copy Markdown
Collaborator

@Khallad1424 Khallad1424 commented Apr 26, 2026

close #4

Summary by CodeRabbit

  • New Features
    • User registration and login with data persistence
    • Location-based address picker with automatic address lookup
    • Comprehensive form validation for user registration and login
    • Multi-step registration workflow for customers and agents with document uploads
    • Address and coordinate capture for service delivery areas

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

The PR implements comprehensive user authentication and registration infrastructure by introducing SQLite database persistence with user credential storage, converting registration screens to validated forms with location and document selection capabilities, integrating map-based location picking with reverse geocoding, and implementing login flow with database-backed credential validation across mobile and web platforms.

Changes

Cohort / File(s) Summary
Database Infrastructure
brightcleanproject/lib/core/database/database_helper.dart
Introduces DatabaseHelper singleton managing SQLite brightclean.db with schema versioning, users table creation, registerUser insertion, and loginUser credential matching. Supports both mobile (local path) and web (FFI with service worker) platforms.
Form-Enhanced Widgets
brightcleanproject/lib/core/widgets/custom_text_field.dart
Adds optional TextEditingController, validator callback, and TextInputFormatter list properties to enable external form control and input customization within TextFormField.
Location Selection
brightcleanproject/lib/core/widgets/map_picker_screen.dart
Introduces MapPickerScreen stateful widget rendering an interactive map centered on Jeddah, capturing selected coordinates via center pin overlay, and reverse-geocoding to Arabic address strings via placemarkFromCoordinates (fallback to raw coordinates on web).
Agent Registration
brightcleanproject/lib/features/auth/presentation/agent_registration_screen.dart
Converts AgentRegistrationScreen from static StatelessWidget to validated StatefulWidget with controller-backed inputs, image picker for document upload with preview, interactive services checkboxes, DOB/date picker, map-based address selection, and form validation with error/success snackbar feedback.
Customer Registration
brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart
Refactors CustomerRegistrationScreen to StatefulWidget with validated form inputs (name, phone, email, password with strength checking, gender dropdown, DOB picker), map-based address selection, database persistence via registerUser, and post-success redirect to login.
Login Flow
brightcleanproject/lib/features/auth/presentation/login_screen.dart
Upgrades LoginScreen to StatefulWidget with form-validated phone/password inputs, loading state management, async loginUser database lookup, success routing to /customer_home, and error handling via snackbars with mounted safety checks.
Web Service Worker Configuration
brightcleanproject/web/index.html, brightcleanproject/web/sqflite_sw.js
Adds service worker registration script in HTML and new worker file importing sql.js and sqflite_common_ffi_web dependencies to enable SQLite support on web platform.
Platform Plugin Setup — Linux
brightcleanproject/linux/flutter/generated_plugin_registrant.cc, brightcleanproject/linux/flutter/generated_plugins.cmake
Auto-registers FileSelectorPlugin and file_selector_linux in Flutter Linux plugin system with required headers and CMake linking.
Platform Plugin Setup — macOS
brightcleanproject/macos/Flutter/GeneratedPluginRegistrant.swift
Auto-registers FilePickerPlugin, FileSelectorPlugin, and SqflitePlugin in Flutter macOS environment via imports and registrar calls.
Platform Plugin Setup — Windows
brightcleanproject/windows/flutter/generated_plugin_registrant.cc, brightcleanproject/windows/flutter/generated_plugins.cmake
Auto-registers FileSelectorWindows plugin and adds file_selector_windows to CMake plugin list for Windows platform support.
Dependency Management
brightcleanproject/pubspec.yaml
Adds SQLite packages (sqflite, sqflite_common_ffi, sqflite_common_ffi_web), filesystem utilities (path), media/image integration (image_picker), mapping/geolocation (flutter_map, latlong2, geocoding), and file selection (file_picker).

Sequence Diagrams

sequenceDiagram
    participant User
    participant RegForm as Registration Form
    participant MapPicker as Map Picker Screen
    participant Geocoder as Geocoding Service
    participant DB as Database (SQLite)
    
    User->>RegForm: Fill form & tap "تأكيد الموقع"
    RegForm->>MapPicker: Navigate to location selection
    User->>MapPicker: Pan/select location on map
    User->>MapPicker: Tap "تأكيد الموقع"
    MapPicker->>Geocoder: reverse-geocode coordinates
    Geocoder-->>MapPicker: Return address placemark
    MapPicker->>MapPicker: Build Arabic address string
    MapPicker-->>RegForm: Return coordinates & address
    User->>RegForm: Fill remaining fields & submit
    RegForm->>RegForm: Validate all inputs
    RegForm->>DB: registerUser(userData map)
    DB-->>RegForm: User inserted with ID
    RegForm-->>User: Success snackbar & navigate to login
Loading
sequenceDiagram
    participant User
    participant LoginForm as Login Form
    participant DB as Database (SQLite)
    participant Router as Navigation
    
    User->>LoginForm: Enter phone & password
    User->>LoginForm: Tap Login button
    LoginForm->>LoginForm: Validate form fields
    LoginForm->>LoginForm: Set loading = true
    LoginForm->>DB: loginUser(phone, password)
    DB->>DB: Query users table by phone & password
    DB-->>LoginForm: Return user record or null
    alt User found
        LoginForm->>Router: Navigate to /customer_home
        Router-->>User: Display customer home screen
    else User not found or error
        LoginForm->>User: Show error snackbar
    end
    LoginForm->>LoginForm: Set loading = false
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A hop through forms and fields so grand,
With maps and pins across the land,
SQLite dreams now safely stored,
Login flows and geocoded rewards—
The warren cheers this auth-quest won! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Agent screen' is vague and generic; it does not specify the primary change (implementation/refactoring of agent registration with validation). Use a more descriptive title like 'Implement agent registration form with validation and file upload' to clearly convey the main objective.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR addresses issue #4 requirements: adds validation for required fields (names, phone, email, password, services, address), implements file/document selection with visual preview, and includes a map picker for location selection with improved navigation flow.
Out of Scope Changes check ✅ Passed Beyond the agent registration focus, the PR includes supporting infrastructure: database helper (user persistence), custom text field enhancements, map picker, login/customer registration screens, and platform-specific plugin registrations—all reasonably foundational for the agent registration feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Agent_Screen
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch Agent_Screen

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
brightcleanproject/pubspec.yaml (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Typo in package name: brightcleanprojet.

name: brightcleanprojet is missing a c (likely intended brightcleanproject). This name becomes the import prefix for the package and any downstream import paths — fixing it later will require updates to every package:brightcleanproject/... import. Consider correcting now while the project is still small.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/pubspec.yaml` at line 1, The package name value
"brightcleanprojet" in the pubspec (the YAML key/value "name:
brightcleanprojet") is misspelled; change it to "brightcleanproject" and then
run the package manager (flutter pub get / dart pub get) to refresh locks; also
search the repo for any existing imports using the old package prefix
"package:brightcleanprojet/" and update them to "package:brightcleanproject/" so
all package import paths remain consistent.
🧹 Nitpick comments (8)
brightcleanproject/web/index.html (1)

36-48: Use console.error for the failure branch and be aware of the load/bootstrap race.

Two small things on the inline registration script:

  1. The catch handler logs registration failures via console.log; using console.error surfaces them at the right severity in DevTools and any log aggregation, which matters because database_helper.dart relies on this worker and only fails after a 15s timeout — making early visibility important.
  2. flutter_bootstrap.js is loaded with async, so Flutter (and therefore _initDatabase) can start before the load-gated serviceWorker.register(...) resolves. The 15s timeout in database_helper.dart masks this, but on slow connections the very first DB call may still race. Consider registering the SW eagerly (outside the load listener) so registration starts as early as possible.
♻️ Suggested tweak
-  <script>
-    // تسجيل الخدمة اليدوية لقاعدة البيانات
-    window.addEventListener('load', function() {
-      if ('serviceWorker' in navigator) {
-        navigator.serviceWorker.register('sqflite_sw.js')
-          .then(function(registration) {
-            console.log('SQFLite Service Worker registered successfully:', registration.scope);
-          }).catch(function(error) {
-            console.log('SQFLite ServiceWorker registration failed:', error);
-          });
-      }
-    });
-  </script>
+  <script>
+    // Register the sqflite service worker as early as possible so the
+    // Flutter bootstrap (loaded async below) can use it without racing.
+    if ('serviceWorker' in navigator) {
+      navigator.serviceWorker.register('sqflite_sw.js')
+        .then(function(registration) {
+          console.log('SQFLite Service Worker registered:', registration.scope);
+        })
+        .catch(function(error) {
+          console.error('SQFLite Service Worker registration failed:', error);
+        });
+    }
+  </script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/web/index.html` around lines 36 - 48, The registration
failure path uses console.log and delays SW startup until window.load, which
hides errors and can race with Flutter's _initDatabase in database_helper.dart;
change the catch to use console.error when calling
navigator.serviceWorker.register('sqflite_sw.js') and move the register call out
of window.addEventListener('load') so registration starts immediately (still
guard with 'serviceWorker' in navigator) to reduce bootstrap race with
flutter_bootstrap.js and _initDatabase.
brightcleanproject/lib/core/widgets/map_picker_screen.dart (2)

14-19: Hard-coded default center will be far from many users.

Defaulting to Jeddah is reasonable for an MVP, but if the OS gives you location permission you'll want to center on the user's actual position. Even without permission, you may want to expose this default as a constant (or read from config) so it's obvious where to tweak it for other regions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/core/widgets/map_picker_screen.dart` around lines 14 -
19, The hard-coded Jeddah LatLng in _MapPickerScreenState (_currentCenter)
should be replaced so the map centers on the user's actual position when
available and the default is configurable; implement location fetching and
permission checks in initState (or a helper like _initLocation) to request
permission, get the device location and update _currentCenter and _mapController
accordingly, and if the location is unavailable fall back to a named constant
(e.g., kDefaultMapCenter) or a value read from config so the default isn’t
buried in the field initializer.

30-53: Web users will always get raw coordinates, never a human-readable address.

The kIsWeb branch unconditionally falls back to the lat/lng-formatted string. For a customer/agent registering on the web build, the saved "address" will literally read موقع محدد (21.5433, 39.1728), which is not useful for fulfillment/dispatch later. Consider calling a free reverse-geocoding HTTP API (e.g. Nominatim with a proper user agent and rate-limit caching, or Google/Mapbox if licensed) on web so both platforms produce comparable data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/core/widgets/map_picker_screen.dart` around lines 30 -
53, The web branch currently bypasses reverse geocoding (kIsWeb) and leaves
addressName as raw coordinates; implement a web-side reverse-geocoding call
instead of the debug fallback by adding an HTTP request to a free geocoding API
(e.g., Nominatim) when kIsWeb is true: use _currentCenter.latitude and
_currentCenter.longitude to query the API, parse the returned address components
into the same elements list format used in the placemarkFromCoordinates branch,
then set addressName accordingly (joining elements with '، '); ensure you
include a proper User-Agent, basic error handling/fallback to the coordinate
string, and consider simple caching/rate-limit handling to avoid excessive
requests (modify the kIsWeb branch around placemarkFromCoordinates and
addressName).
brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart (2)

1-9: Consider using SafeArea and a resizeToAvoidBottomInset-aware layout to satisfy issue #4 acceptance criterion 3.

Issue #4 explicitly calls out "improve form scrolling on small screens to prevent the keyboard from covering form fields." SingleChildScrollView already helps here, but for the agent screen (which has many more fields than the customer one) you'll want to:

  • wrap the Scaffold body in a SafeArea,
  • ensure Scaffold.resizeToAvoidBottomInset: true (the default) is preserved,
  • add padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom + 24) to the SingleChildScrollView so the focused field can scroll above the keyboard.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart`
around lines 1 - 9, Wrap the Scaffold body in a SafeArea and ensure
Scaffold.resizeToAvoidBottomInset remains true, then update the
SingleChildScrollView (used in customer registration) to include padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom + 24) so form
fields can scroll above the keyboard; locate the Scaffold and its
SingleChildScrollView in customer_registration_screen.dart and apply these
changes to improve form scrolling on small screens.

191-205: Password validator has redundant/contradictory checks.

Line 198 already requires [A-Z], and line 201 requires [a-zA-Z\u0600-\u06FF] or [0-9] (note the ||) — but with the prior uppercase requirement the letter half is always satisfied, so 201 effectively only enforces "must contain a digit". Either drop line 201 or rewrite it as a positive "must contain digit" check, mirroring the agent screen (_validatePassword lines 254‑260) which is cleaner.

♻️ Proposed fix
-    if (!RegExp(r'[a-zA-Z\u0600-\u06FF]').hasMatch(value) || !RegExp(r'[0-9]').hasMatch(value)) {
-      return 'يجب أن تحتوي على حرف ورقم على الأقل';
-    }
+    if (!RegExp(r'[0-9]').hasMatch(value)) {
+      return 'يجب أن تحتوي على رقم واحد على الأقل';
+    }

Consider extracting the validators shared with agent_registration_screen.dart into a single core/validators.dart to remove the duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart`
around lines 191 - 205, The _validatePassword function currently has a
redundant/contradictory check: after requiring an uppercase letter with
RegExp(r'[A-Z]'), the subsequent condition uses RegExp(r'[a-zA-Z\u0600-\u06FF]')
|| RegExp(r'[0-9]') which makes the letter-side always true and thus only
enforces a digit; replace that combined condition with an explicit digit-only
check (e.g. validate using RegExp(r'[0-9]')) so the validator clearly enforces:
non-empty, min length 8, at least one uppercase, and at least one digit;
optionally refactor this logic into a shared validator (e.g.,
core/validators.dart) matching the agent screen's _validatePassword
implementation to remove duplication.
brightcleanproject/lib/features/auth/presentation/login_screen.dart (1)

33-33: Use Future<void> instead of void for async handlers.

void _handleLogin() async makes the function fire-and-forget; any thrown exception that escapes the try/catch becomes an unawaitable error and analysis tools (e.g. avoid_void_async/unawaited_futures) can't reason about callers. Prefer Future<void> _handleLogin() async.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/features/auth/presentation/login_screen.dart` at line
33, Change the async handler declaration from void to Future<void> for
_handleLogin so callers and static analysis can await it; update the signature
to Future<void> _handleLogin() async in login_screen.dart and ensure any call
sites (e.g., button onPressed or other invocations) either await the call or
explicitly handle the returned Future so exceptions are not fire-and-forget.
brightcleanproject/lib/features/auth/presentation/agent_registration_screen.dart (2)

96-109: initialDate of year 2000 may exceed allowed range and there's no minimum-age check.

showDatePicker requires initialDate to lie within [firstDate, lastDate]; today (DateTime.now()) easily satisfies that, but a future maintainer changing lastDate could trigger an assertion. More importantly, agents are presumably adults, but the picker allows selecting any date up to today — consider clamping lastDate to e.g. DateTime(now.year - 18, now.month, now.day) to enforce a minimum age at the picker level (and mirror the same in customer_registration_screen.dart lines 50‑55).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@brightcleanproject/lib/features/auth/presentation/agent_registration_screen.dart`
around lines 96 - 109, The _selectDOB method uses showDatePicker with
initialDate fixed to DateTime(2000) and lastDate set to DateTime.now(), which
can break the date-picker assertion and doesn't enforce a minimum age; update
_selectDOB to compute now = DateTime.now(), compute an adultCutoff =
DateTime(now.year - 18, now.month, now.day) and use adultCutoff as lastDate (so
users must be >=18), then clamp initialDate to lie between firstDate (e.g.,
DateTime(1900)) and adultCutoff before passing it to showDatePicker, and keep
setting _dobController.text as before; apply the same change pattern to the
customer_registration_screen.dart date-picker implementation referenced in the
comment.

132-175: Document placeholder lacks a "remove/replace" affordance and an accessibility label.

Once a file is selected, the only way to change it is to re-tap and pick again; there is no way to clear/remove the selection. Also, GestureDetector + Container is not announced as a button by screen readers — wrap it with Semantics(button: true, label: title) (or use InkWell/OutlinedButton.icon) so assistive tech can describe the upload action.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@brightcleanproject/lib/features/auth/presentation/agent_registration_screen.dart`
around lines 132 - 175, The upload tile built with GestureDetector/Container
(using isUploaded, imageFile, title, onTap) lacks an accessibility role and a
clear way to remove/replace the file; wrap or replace the GestureDetector with a
Semantics widget (e.g., Semantics(button: true, label: title)) or use an
interactive Material widget (InkWell/OutlinedButton.icon) so screen readers
announce it as a button, and when isUploaded is true render a visible
remove/replace affordance (e.g., a trailing IconButton or small overlay “remove”
icon) that calls a new clear callback (or reuses onTap with a mode) to clear
imageFile and reset isUploaded so users can remove the selection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@brightcleanproject/lib/core/database/database_helper.dart`:
- Around line 67-80: The users table schema in _createDB must mark phone as
UNIQUE to prevent duplicate accounts—update the CREATE TABLE statement in
_createDB to add UNIQUE to the phone column; then update registerUser to catch
DatabaseException (inspect exception code for SQLITE_CONSTRAINT) and return a
friendly "phone already registered" error instead of a generic failure. Also
modify loginUser to query WHERE phone = ? first and then verify the password
hash against the returned record (rather than relying on results.first), so
authentication is deterministic and safe.
- Around line 61-65: The _upgradeDB function currently runs `DROP TABLE IF
EXISTS users` which will erase all accounts on any schema version bump; update
_upgradeDB (and ensure callers via _initDatabase) to avoid destructive drops in
production by either wrapping the DROP logic behind a debug-only guard (use
assert or kDebugMode) or replace it with proper non-destructive migrations
(ALTER TABLE / data-preserving steps) that handle oldVersion→newVersion
upgrades. Locate the `_upgradeDB` function and change its behavior so production
runs perform safe migrations while debug/dev runs may still drop+recreate for
convenience.
- Around line 36-39: Replace the current .timeout(... onTimeout: ...) usage in
database_helper.dart with a try/catch around the await that calls timeout so you
can log with debugPrint and preserve/rethrow the original error: remove the
onTimeout handler, wrap the future call that uses .timeout(const
Duration(seconds: 15)) in a try { ... } on TimeoutException catch (e, st) {
debugPrint("Service worker initialization timed out: $e\n$st"); rethrow; } and
for other exceptions use catch (e, st) { debugPrint("Service worker
initialization failed: $e\n$st"); rethrow; } — this keeps debugPrint (from
material.dart) instead of print and preserves the original exception and stack
trace for functions/blocks around the timeout call.
- Around line 13-17: The lazy getter DatabaseHelper.instance.database has a race
where multiple callers can call _initDatabase concurrently; fix it by
introducing a single-flight Future/Completer (e.g. a private Future<Database>?
_initializingDatabase or Completer<Database>? _dbCompleter) that you set before
calling _initDatabase, have subsequent callers await that future instead of
calling _initDatabase again, and on completion assign _database and clear the
single-flight field (and complete/completeError the completer on success/error);
update the getter to check _database first, then the single-flight future, and
only call _initDatabase when neither exists (also handle clearing the future on
errors to allow retries).
- Around line 67-93: Schema and auth currently store plaintext passwords (see
_createDB creating users.password and seed insert, and places using db.insert in
registerUser and WHERE phone = ? AND password = ? in loginUser); change to store
Argon2id hashes and salts: alter the users schema in _createDB to add
password_salt TEXT NOT NULL, remove plaintext seeds or replace password with a
hashed value, and in registerUser generate a secure random salt, derive an
Argon2id key from the provided password (using the cryptography package
Argon2id), base64-encode and store both the hash and salt via db.insert; in
loginUser query by phone only, retrieve password_salt and stored password hash,
derive the Argon2id key from the supplied password and the stored salt,
base64-encode and compare to the stored hash to authenticate.

In `@brightcleanproject/lib/core/widgets/map_picker_screen.dart`:
- Around line 56-67: The finally block calls setState(() { _isLoading = false;
}) without checking mounted, which can throw after dispose; update the finally
to check mounted before calling setState (e.g. if (mounted) setState(() {
_isLoading = false; });) and keep the existing mounted check for Navigator.pop
that uses _currentCenter and addressName, so both state update and navigation
only run when mounted.
- Around line 86-91: onPositionChanged is calling setState on every camera frame
causing excessive rebuilds; remove the setState call inside the
onPositionChanged callback and update the _currentCenter field directly (e.g.,
_currentCenter = camera.center) so the map/Stack/FlutterMap aren't rebuilt
continuously, and only call setState when the user commits the selection (e.g.,
inside _confirmLocation) if UI needs to reflect the confirmed center; also
ensure the fixed center pin is a const Center (not driven by _currentCenter) so
it doesn't rely on rebuilds.
- Around line 93-96: The TileLayer call currently uses a placeholder
userAgentPackageName ('com.example.brightcleanprojet'); update the TileLayer's
userAgentPackageName to your real production bundle identifier (the Android
applicationId or iOS CFBundleIdentifier) so OSM tiles receive a valid User-Agent
(e.g., change in the TileLayer widget to 'com.brightclean.app' or your actual
app id); ensure the value set in userAgentPackageName in TileLayer matches the
app's configured applicationId/CFBundleIdentifier.

In
`@brightcleanproject/lib/features/auth/presentation/agent_registration_screen.dart`:
- Around line 437-446: The form currently allows submission without uploaded
documents because _submitForm does not validate _commercialRegImage or _idImage;
update _submitForm to include non-null checks for _commercialRegImage and
_idImage (same pattern used for _selectedAddress) and prevent submission if
either is null by setting state (e.g., set _hasAttemptedSubmit = true and
return) so validation runs. Then surface inline errors under each placeholder in
the UI by either extending _buildFileUploadPlaceholder to accept an optional
errorText or by rendering a Text error widget directly below each
_buildFileUploadPlaceholder when _hasAttemptedSubmit && imageFile == null (use
_commercialRegImage and _idImage to decide which error to show). Ensure error
messaging appears only after an attempted submit and follows existing styling
conventions.
- Around line 363-367: The validator on CustomTextField using
_businessNameController currently allows whitespace-only and unbounded names;
update the validator for the business name field (the CustomTextField using
_businessNameController) to check value.trim().isEmpty and enforce a sane
maximum length (e.g. 100 chars) — return the existing "يرجى إدخال اسم المغسلة"
for empty/whitespace-only and a new message for too-long values; also add a
maxLength/inputFormatter to the CustomTextField if it supports it to prevent
oversized input at the UI level.
- Around line 246-252: The email validator in _validateEmail uses a regex that
limits TLD length to {2,4}, rejecting valid longer TLDs; replace the pattern
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$') with a more permissive one such as
RegExp(r'^[\w\-.]+@([\w\-]+\.)+[\w\-]{2,}$') (or switch to a vetted
email-validation package) and update the identical occurrence in the other
_validateEmail implementation (customer registration) so both screens use the
new pattern; keep the existing null/empty checks and Arabic messages unchanged.
- Around line 268-300: _submitForm currently only validates and shows a snackbar
but never persists the agent to the database or navigates, so implement
persistence and UX state: call DatabaseHelper.instance.registerUser(...) with
the constructed user/agent payload (include agent-specific fields like
businessName, selectedServices, document paths, coordinates) inside _submitForm
after successful validation, wrap the operation with a loading state boolean
(e.g., _isSubmitting) set with setState before/after the await to disable UI and
show a progress indicator, handle exceptions to show an error SnackBar on
failure, and on success navigate away (Navigator.pop or pushReplacement to
LoginScreen or a success screen) and show a success SnackBar; update
database_helper.dart schema as needed to store agent fields and ensure
registerUser returns a meaningful result to branch success vs failure.

In
`@brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart`:
- Around line 345-349: The validation message for location is shown immediately
because there's no submit-attempt guard; add a boolean state field named
_hasAttemptedSubmit initialized to false and set it to true at the start of the
_submitForm method, then change the conditional that shows the red Text (which
currently checks _selectedAddress == null) to require both _selectedAddress ==
null && _hasAttemptedSubmit so the error only appears after a user attempts to
submit (mirroring the pattern used in agent_registration_screen.dart).
- Around line 92-148: The form currently validates but never enforces or
persists the map-picked address: add a check similar to
agent_registration_screen.dart (guarding on _selectedAddress != null) before
proceeding after _formKey.currentState!.validate(), and include the selected
address and coordinates in the userData map (add keys like 'address',
'latitude', 'longitude' populated from _selectedAddress and
_selectedCoordinates) so DatabaseHelper.registerUser receives them; update
DatabaseHelper._createDB to add address/latitude/longitude columns and adjust
registerUser to insert those fields. Ensure UI feedback matches the validation
(show SnackBar when _selectedAddress is null) and keep the existing kIsWeb
bypass logic for coordinates.
- Around line 128-139: The catch block that builds errorMessage (inside the
catch (e) where you show a SnackBar via
ScaffoldMessenger.of(context).showSnackBar) should not rely on the Arabic
substring check; instead check the exception type (e.g. use `if (e is
DatabaseException)` or inspect `e.code` if available) and set a localized
DB-specific message like 'تعذر الاتصال بقاعدة البيانات المحلية' for that branch,
otherwise set a generic localized message without interpolating the raw `$e`;
then call the existing SnackBar display with the safe message. Ensure you
import/resolve `DatabaseException` from sqflite (or the appropriate DB exception
type) and keep the mounted check and SnackBar call as-is.

In `@brightcleanproject/lib/features/auth/presentation/login_screen.dart`:
- Around line 41-42: Remove the artificial 1.5s delay by deleting the await
Future.delayed(const Duration(milliseconds: 1500)) call in login_screen.dart
(the line containing Future.delayed(const Duration(milliseconds: 1500))). This
line currently lives in the login flow (e.g., the handler that performs
authentication) and should be removed so authentication uses the actual SQLite
round-trip time; keep the surrounding authentication logic and spinner state
management intact.
- Around line 50-63: The current post-login routing always sends authenticated
users to '/customer_home' even though loginUser returns a user record with a
role; update the routing logic in the block that checks if (mounted) after login
to inspect user['role'] (from the loginUser result) and branch accordingly
(e.g., context.go('/agent_home') for role == 'agent',
context.go('/customer_home') for role == 'customer'), and handle unrecognized
roles by showing an error SnackBar or failing the login; keep the existing
mounted check and Snackbar usage (ScaffoldMessenger.of(context).showSnackBar)
and only change the branch that currently always calls
context.go('/customer_home').

In `@brightcleanproject/pubspec.yaml`:
- Line 48: Remove the unused dependency entry "file_picker: ^11.0.2" from
pubspec.yaml; search the codebase for any imports or references to "file_picker"
or "FilePicker" to confirm there are no usages (the project uses "image_picker"
instead), delete the dependency line, run flutter pub get to update lockfiles,
and if present remove any platform-specific configuration or Android/iOS
permission entries that were added only for file_picker.

In `@brightcleanproject/web/sqflite_sw.js`:
- Around line 1-2: The service worker currently imports sql.js and sqflite_sw.js
from unpkg via importScripts, which is unsafe and unreliable; instead vendor the
runtime assets locally (run dart run sqflite_common_ffi_web:setup to copy
sqflite_sw.js and the matching sqlite3.wasm/sql-wasm.js into your project's
web/), replace the external importScripts calls in sqflite_sw.js with relative
local paths to those vendored files, and ensure the vendored sql.js/wasm version
matches the version resolved in pubspec.lock (e.g., sql.js 1.8.0 / corresponding
sqlite3.wasm expected by sqflite_common_ffi_web version) so the worker and Dart
package remain compatible.

---

Outside diff comments:
In `@brightcleanproject/pubspec.yaml`:
- Line 1: The package name value "brightcleanprojet" in the pubspec (the YAML
key/value "name: brightcleanprojet") is misspelled; change it to
"brightcleanproject" and then run the package manager (flutter pub get / dart
pub get) to refresh locks; also search the repo for any existing imports using
the old package prefix "package:brightcleanprojet/" and update them to
"package:brightcleanproject/" so all package import paths remain consistent.

---

Nitpick comments:
In `@brightcleanproject/lib/core/widgets/map_picker_screen.dart`:
- Around line 14-19: The hard-coded Jeddah LatLng in _MapPickerScreenState
(_currentCenter) should be replaced so the map centers on the user's actual
position when available and the default is configurable; implement location
fetching and permission checks in initState (or a helper like _initLocation) to
request permission, get the device location and update _currentCenter and
_mapController accordingly, and if the location is unavailable fall back to a
named constant (e.g., kDefaultMapCenter) or a value read from config so the
default isn’t buried in the field initializer.
- Around line 30-53: The web branch currently bypasses reverse geocoding
(kIsWeb) and leaves addressName as raw coordinates; implement a web-side
reverse-geocoding call instead of the debug fallback by adding an HTTP request
to a free geocoding API (e.g., Nominatim) when kIsWeb is true: use
_currentCenter.latitude and _currentCenter.longitude to query the API, parse the
returned address components into the same elements list format used in the
placemarkFromCoordinates branch, then set addressName accordingly (joining
elements with '، '); ensure you include a proper User-Agent, basic error
handling/fallback to the coordinate string, and consider simple
caching/rate-limit handling to avoid excessive requests (modify the kIsWeb
branch around placemarkFromCoordinates and addressName).

In
`@brightcleanproject/lib/features/auth/presentation/agent_registration_screen.dart`:
- Around line 96-109: The _selectDOB method uses showDatePicker with initialDate
fixed to DateTime(2000) and lastDate set to DateTime.now(), which can break the
date-picker assertion and doesn't enforce a minimum age; update _selectDOB to
compute now = DateTime.now(), compute an adultCutoff = DateTime(now.year - 18,
now.month, now.day) and use adultCutoff as lastDate (so users must be >=18),
then clamp initialDate to lie between firstDate (e.g., DateTime(1900)) and
adultCutoff before passing it to showDatePicker, and keep setting
_dobController.text as before; apply the same change pattern to the
customer_registration_screen.dart date-picker implementation referenced in the
comment.
- Around line 132-175: The upload tile built with GestureDetector/Container
(using isUploaded, imageFile, title, onTap) lacks an accessibility role and a
clear way to remove/replace the file; wrap or replace the GestureDetector with a
Semantics widget (e.g., Semantics(button: true, label: title)) or use an
interactive Material widget (InkWell/OutlinedButton.icon) so screen readers
announce it as a button, and when isUploaded is true render a visible
remove/replace affordance (e.g., a trailing IconButton or small overlay “remove”
icon) that calls a new clear callback (or reuses onTap with a mode) to clear
imageFile and reset isUploaded so users can remove the selection.

In
`@brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart`:
- Around line 1-9: Wrap the Scaffold body in a SafeArea and ensure
Scaffold.resizeToAvoidBottomInset remains true, then update the
SingleChildScrollView (used in customer registration) to include padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom + 24) so form
fields can scroll above the keyboard; locate the Scaffold and its
SingleChildScrollView in customer_registration_screen.dart and apply these
changes to improve form scrolling on small screens.
- Around line 191-205: The _validatePassword function currently has a
redundant/contradictory check: after requiring an uppercase letter with
RegExp(r'[A-Z]'), the subsequent condition uses RegExp(r'[a-zA-Z\u0600-\u06FF]')
|| RegExp(r'[0-9]') which makes the letter-side always true and thus only
enforces a digit; replace that combined condition with an explicit digit-only
check (e.g. validate using RegExp(r'[0-9]')) so the validator clearly enforces:
non-empty, min length 8, at least one uppercase, and at least one digit;
optionally refactor this logic into a shared validator (e.g.,
core/validators.dart) matching the agent screen's _validatePassword
implementation to remove duplication.

In `@brightcleanproject/lib/features/auth/presentation/login_screen.dart`:
- Line 33: Change the async handler declaration from void to Future<void> for
_handleLogin so callers and static analysis can await it; update the signature
to Future<void> _handleLogin() async in login_screen.dart and ensure any call
sites (e.g., button onPressed or other invocations) either await the call or
explicitly handle the returned Future so exceptions are not fire-and-forget.

In `@brightcleanproject/web/index.html`:
- Around line 36-48: The registration failure path uses console.log and delays
SW startup until window.load, which hides errors and can race with Flutter's
_initDatabase in database_helper.dart; change the catch to use console.error
when calling navigator.serviceWorker.register('sqflite_sw.js') and move the
register call out of window.addEventListener('load') so registration starts
immediately (still guard with 'serviceWorker' in navigator) to reduce bootstrap
race with flutter_bootstrap.js and _initDatabase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7dd09e1e-8518-4b86-a41f-238a0161adfe

📥 Commits

Reviewing files that changed from the base of the PR and between 2421f82 and 426ef08.

⛔ Files ignored due to path filters (1)
  • brightcleanproject/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • brightcleanproject/lib/core/database/database_helper.dart
  • brightcleanproject/lib/core/widgets/custom_text_field.dart
  • brightcleanproject/lib/core/widgets/map_picker_screen.dart
  • brightcleanproject/lib/features/auth/presentation/agent_registration_screen.dart
  • brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart
  • brightcleanproject/lib/features/auth/presentation/login_screen.dart
  • brightcleanproject/linux/flutter/generated_plugin_registrant.cc
  • brightcleanproject/linux/flutter/generated_plugins.cmake
  • brightcleanproject/macos/Flutter/GeneratedPluginRegistrant.swift
  • brightcleanproject/pubspec.yaml
  • brightcleanproject/web/index.html
  • brightcleanproject/web/sqflite_sw.js
  • brightcleanproject/windows/flutter/generated_plugin_registrant.cc
  • brightcleanproject/windows/flutter/generated_plugins.cmake

Comment on lines +13 to +17
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase('brightclean.db');
return _database!;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Race condition in the lazy database getter.

If two callers await DatabaseHelper.instance.database concurrently before _database is initialized, both will see _database == null and both will call _initDatabase (and on web, both will reassign the global databaseFactory). Wrap the initialization in a single-flight Completer<Database>? (or a Future<Database>?) so only the first caller opens the DB and the rest await the same future.

🛠️ Proposed fix
-  static Database? _database;
+  static Database? _database;
+  static Future<Database>? _opening;
 
   DatabaseHelper._init();
 
   Future<Database> get database async {
     if (_database != null) return _database!;
-    _database = await _initDatabase('brightclean.db');
-    return _database!;
+    _opening ??= _initDatabase('brightclean.db').then((db) {
+      _database = db;
+      return db;
+    });
+    return _opening!;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/core/database/database_helper.dart` around lines 13 -
17, The lazy getter DatabaseHelper.instance.database has a race where multiple
callers can call _initDatabase concurrently; fix it by introducing a
single-flight Future/Completer (e.g. a private Future<Database>?
_initializingDatabase or Completer<Database>? _dbCompleter) that you set before
calling _initDatabase, have subsequent callers await that future instead of
calling _initDatabase again, and on completion assign _database and clear the
single-flight field (and complete/completeError the completer on success/error);
update the getter to check _database first, then the single-flight future, and
only call _initDatabase when neither exists (also handle clearing the future on
errors to allow retries).

Comment on lines +36 to +39
).timeout(const Duration(seconds: 15), onTimeout: () {
print("ERROR: Service worker initialization timed out or failed to connect.");
throw Exception("خطأ في تهيئة قاعدة البيانات - تأكد من رسائل F12 (Service Worker Timeout)");
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use debugPrint instead of print, and don't lose the original error.

print lands in production logs (and gets rate-limited on Android). Also, the onTimeout swaps the underlying error for a string with no inner cause, making field debugging harder. Use debugPrint (already imported via material.dart) and include the original exception when available.

♻️ Proposed fix
-        ).timeout(const Duration(seconds: 15), onTimeout: () {
-          print("ERROR: Service worker initialization timed out or failed to connect.");
-          throw Exception("خطأ في تهيئة قاعدة البيانات - تأكد من رسائل F12 (Service Worker Timeout)");
-        });
+        ).timeout(const Duration(seconds: 15), onTimeout: () {
+          debugPrint(
+            "ERROR: Service worker initialization timed out or failed to connect.",
+          );
+          throw TimeoutException(
+            "خطأ في تهيئة قاعدة البيانات - تأكد من رسائل F12 (Service Worker Timeout)",
+          );
+        });

(TimeoutException lives in dart:async.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
).timeout(const Duration(seconds: 15), onTimeout: () {
print("ERROR: Service worker initialization timed out or failed to connect.");
throw Exception("خطأ في تهيئة قاعدة البيانات - تأكد من رسائل F12 (Service Worker Timeout)");
});
).timeout(const Duration(seconds: 15), onTimeout: () {
debugPrint(
"ERROR: Service worker initialization timed out or failed to connect.",
);
throw TimeoutException(
"خطأ في تهيئة قاعدة البيانات - تأكد من رسائل F12 (Service Worker Timeout)",
);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/core/database/database_helper.dart` around lines 36 -
39, Replace the current .timeout(... onTimeout: ...) usage in
database_helper.dart with a try/catch around the await that calls timeout so you
can log with debugPrint and preserve/rethrow the original error: remove the
onTimeout handler, wrap the future call that uses .timeout(const
Duration(seconds: 15)) in a try { ... } on TimeoutException catch (e, st) {
debugPrint("Service worker initialization timed out: $e\n$st"); rethrow; } and
for other exceptions use catch (e, st) { debugPrint("Service worker
initialization failed: $e\n$st"); rethrow; } — this keeps debugPrint (from
material.dart) instead of print and preserves the original exception and stack
trace for functions/blocks around the timeout call.

Comment on lines +61 to +65
Future _upgradeDB(Database db, int oldVersion, int newVersion) async {
// Drop existing table and recreate it with the new schema for development purposes
await db.execute('DROP TABLE IF EXISTS users');
await _createDB(db, newVersion);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

_upgradeDB drops the users table — every schema bump erases all accounts.

DROP TABLE IF EXISTS users is fine during early development, but the comment on line 62 ("for development purposes") suggests this is intentional. Once this code is shipped to a real device, bumping version in _initDatabase will silently delete every customer's and agent's account on next app launch. Either guard this behind a debug flag (assert/kDebugMode) or implement proper ALTER TABLE migrations now, before any production data exists.

🛡️ Suggested guard
+import 'package:flutter/foundation.dart' show kDebugMode;
+
 Future _upgradeDB(Database db, int oldVersion, int newVersion) async {
-  // Drop existing table and recreate it with the new schema for development purposes
-  await db.execute('DROP TABLE IF EXISTS users');
-  await _createDB(db, newVersion);
+  assert(kDebugMode, 'Destructive upgrade path running in release build!');
+  // TODO: replace with non-destructive ALTER TABLE migrations before launch.
+  await db.execute('DROP TABLE IF EXISTS users');
+  await _createDB(db, newVersion);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future _upgradeDB(Database db, int oldVersion, int newVersion) async {
// Drop existing table and recreate it with the new schema for development purposes
await db.execute('DROP TABLE IF EXISTS users');
await _createDB(db, newVersion);
}
import 'package:flutter/foundation.dart' show kDebugMode;
Future _upgradeDB(Database db, int oldVersion, int newVersion) async {
assert(kDebugMode, 'Destructive upgrade path running in release build!');
// TODO: replace with non-destructive ALTER TABLE migrations before launch.
await db.execute('DROP TABLE IF EXISTS users');
await _createDB(db, newVersion);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/core/database/database_helper.dart` around lines 61 -
65, The _upgradeDB function currently runs `DROP TABLE IF EXISTS users` which
will erase all accounts on any schema version bump; update _upgradeDB (and
ensure callers via _initDatabase) to avoid destructive drops in production by
either wrapping the DROP logic behind a debug-only guard (use assert or
kDebugMode) or replace it with proper non-destructive migrations (ALTER TABLE /
data-preserving steps) that handle oldVersion→newVersion upgrades. Locate the
`_upgradeDB` function and change its behavior so production runs perform safe
migrations while debug/dev runs may still drop+recreate for convenience.

Comment on lines +67 to +80
Future _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
first_name TEXT,
last_name TEXT,
phone TEXT NOT NULL,
email TEXT,
password TEXT NOT NULL,
gender TEXT,
dob TEXT,
role TEXT NOT NULL
)
''');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No UNIQUE constraint on phone — duplicate accounts can be registered, and login becomes ambiguous.

phone is the de-facto login identifier, but the schema only marks it NOT NULL. Two users (or the seed user plus a new registration with phone 0500000000) can coexist; loginUser then returns results.first, which is non-deterministic and could log the user into someone else's account if passwords happen to match. Add UNIQUE to phone, handle the resulting DatabaseException (code = SQLITE_CONSTRAINT) in registerUser with a friendly "phone already registered" message, and consider WHERE phone = ? first then verifying the password hash separately.

🛡️ Proposed schema change
 CREATE TABLE users (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   first_name TEXT,
   last_name TEXT,
-  phone TEXT NOT NULL,
+  phone TEXT NOT NULL UNIQUE,
   email TEXT,
   password TEXT NOT NULL,
   gender TEXT,
   dob TEXT,
   role TEXT NOT NULL
 )

Also applies to: 102-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/core/database/database_helper.dart` around lines 67 -
80, The users table schema in _createDB must mark phone as UNIQUE to prevent
duplicate accounts—update the CREATE TABLE statement in _createDB to add UNIQUE
to the phone column; then update registerUser to catch DatabaseException
(inspect exception code for SQLITE_CONSTRAINT) and return a friendly "phone
already registered" error instead of a generic failure. Also modify loginUser to
query WHERE phone = ? first and then verify the password hash against the
returned record (rather than relying on results.first), so authentication is
deterministic and safe.

Comment on lines +67 to +93
Future _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
first_name TEXT,
last_name TEXT,
phone TEXT NOT NULL,
email TEXT,
password TEXT NOT NULL,
gender TEXT,
dob TEXT,
role TEXT NOT NULL
)
''');

// Insert seed data
await db.insert('users', {
'first_name': 'عميل',
'last_name': 'تجريبي',
'phone': '0500000000',
'email': 'customer@test.com',
'password': 'password1234',
'gender': 'M',
'dob': '2000-01-01',
'role': 'customer',
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Flutter recommended password hashing package 2026 argon2 bcrypt

💡 Result:

The recommended package for password hashing in Flutter as of 2026 is the cryptography package (with cryptography_flutter for Flutter), which explicitly states "Argon2id is the recommended algorithm for password hashing." It provides a modern, cross-platform cryptographic library supporting Argon2id, the current standard for password hashing due to its memory-hardness and resistance to GPU attacks. For bcrypt, the bcrypt package is available and commonly used, implementing the classic Blowfish-based algorithm. However, Argon2 (specifically Argon2id) is superior and preferred over bcrypt per modern security recommendations (e.g., password-hashing.net, recent benchmarks showing Argon2id's GPU resistance). Example usage with cryptography for Argon2id: import 'package:cryptography/cryptography.dart'; final algorithm = Argon2id( memory: 10*1000, // 10 MB parallelism: 2, iterations: 1, hashLength: 32, ); final key = await algorithm.deriveKeyFromPassword( password: SecretKey('chpassword'.codeUnits), nonce: List.filled(16, 0), // Use random salt ); final hashBytes = await key.extractBytes; Other Argon2 options include argon2 (pure Dart, multi-platform) and dargon2_flutter (native C bindings for performance), but cryptography is the most authoritative and actively maintained with the explicit recommendation.

Citations:


🏁 Script executed:

cat -n brightcleanproject/lib/core/database/database_helper.dart | sed -n '67,120p'

Repository: Klvvr1/BrightClean

Length of output: 1598


Passwords are stored and compared in plaintext.

The schema stores password TEXT NOT NULL, the seed inserts 'password1234', registerUser writes the password from the form directly via db.insert('users', userData) without processing, and loginUser performs WHERE phone = ? AND password = ? against the raw value. Even for a local-only SQLite DB this is unsafe — anyone with file access (rooted device, leaked backup) can read every user's credential, and users routinely re-use passwords across services. Hash with Argon2id on registration and compare hashes on login.

🛡️ Suggested approach

Use the cryptography package (recommended by Flutter security standards as of 2026) with Argon2id:

// On registration
import 'package:cryptography/cryptography.dart';

final algorithm = Argon2id(
  memory: 10 * 1000, // 10 MB
  parallelism: 2,
  iterations: 1,
  hashLength: 32,
);
final salt = List<int>.filled(16, 0); // Replace with random bytes in production
final key = await algorithm.deriveKeyFromPassword(
  password: SecretKey(password.codeUnits),
  nonce: salt,
);
final hash = await key.extractBytes();
await db.insert('users', {
  ...userData,
  'password': base64Encode(hash),
  'password_salt': base64Encode(salt),
});

// On login
final row = await db.query('users', where: 'phone = ?', whereArgs: [phone]);
if (row.isEmpty) return null;
final key = await algorithm.deriveKeyFromPassword(
  password: SecretKey(password.codeUnits),
  nonce: base64Decode(row.first['password_salt']),
);
final hash = await key.extractBytes();
return base64Encode(hash) == row.first['password'] ? row.first : null;

Add password_salt TEXT NOT NULL column to the schema and update both registration screens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/core/database/database_helper.dart` around lines 67 -
93, Schema and auth currently store plaintext passwords (see _createDB creating
users.password and seed insert, and places using db.insert in registerUser and
WHERE phone = ? AND password = ? in loginUser); change to store Argon2id hashes
and salts: alter the users schema in _createDB to add password_salt TEXT NOT
NULL, remove plaintext seeds or replace password with a hashed value, and in
registerUser generate a secure random salt, derive an Argon2id key from the
provided password (using the cryptography package Argon2id), base64-encode and
store both the hash and salt via db.insert; in loginUser query by phone only,
retrieve password_salt and stored password hash, derive the Argon2id key from
the supplied password and the stored salt, base64-encode and compare to the
stored hash to authenticate.

Comment on lines +345 to +349
if (_selectedAddress == null) // Very basic required validation for map visually
const Padding(
padding: EdgeInsets.only(top: 8, right: 12),
child: Text('الرجاء تحديد الموقع', style: TextStyle(color: Colors.red, fontSize: 12)),
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

"Please select location" error is shown on first render before the user has done anything.

Unlike the agent screen, there's no _hasAttemptedSubmit gate here, so the red الرجاء تحديد الموقع message renders the moment the screen is opened. Track an attempt flag (or only show after submit is pressed) so the form doesn't look pre-failed.

♻️ Proposed fix

Mirror the pattern from agent_registration_screen.dart lines 481‑488:

-                  if (_selectedAddress == null) // Very basic required validation for map visually
+                  if (_hasAttemptedSubmit && _selectedAddress == null)
                     const Padding(
                       padding: EdgeInsets.only(top: 8, right: 12),
                       child: Text('الرجاء تحديد الموقع', style: TextStyle(color: Colors.red, fontSize: 12)),
                     ),

(Add a bool _hasAttemptedSubmit = false; to state and set it at the start of _submitForm.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@brightcleanproject/lib/features/auth/presentation/customer_registration_screen.dart`
around lines 345 - 349, The validation message for location is shown immediately
because there's no submit-attempt guard; add a boolean state field named
_hasAttemptedSubmit initialized to false and set it to true at the start of the
_submitForm method, then change the conditional that shows the red Text (which
currently checks _selectedAddress == null) to require both _selectedAddress ==
null && _hasAttemptedSubmit so the error only appears after a user attempts to
submit (mirroring the pattern used in agent_registration_screen.dart).

Comment on lines +41 to +42
// Simulate a network request taking 1.5 seconds
await Future.delayed(const Duration(milliseconds: 1500));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove the artificial 1.5 s delay before authentication.

await Future.delayed(const Duration(milliseconds: 1500)) was likely added to demo the spinner, but it now penalizes every real login by 1.5 seconds — and runs even on the failure path. The DB call is local SQLite, so the round-trip is already fast; just delete this line.

♻️ Proposed fix
       try {
-        // Simulate a network request taking 1.5 seconds
-        await Future.delayed(const Duration(milliseconds: 1500));
-
         // DB Authentication Logic
         final user = await DatabaseHelper.instance.loginUser(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/features/auth/presentation/login_screen.dart` around
lines 41 - 42, Remove the artificial 1.5s delay by deleting the await
Future.delayed(const Duration(milliseconds: 1500)) call in login_screen.dart
(the line containing Future.delayed(const Duration(milliseconds: 1500))). This
line currently lives in the login flow (e.g., the handler that performs
authentication) and should be removed so authentication uses the actual SQLite
round-trip time; keep the surrounding authentication logic and spinner state
management intact.

Comment on lines +50 to +63
// After async gaps, check if the widget is still mounted
if (mounted) {
if (user != null) {
// If needed, check user['role'] to route appropriately
context.go('/customer_home');
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('رقم الجوال أو كلمة المرور غير صحيحة'),
backgroundColor: Colors.red,
),
);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Role is fetched but ignored — agents will land on /customer_home.

loginUser returns the full user row (including role), and the comment on line 53 acknowledges this, yet every authenticated user is routed to /customer_home. With the agent registration flow being added in this PR, an agent who logs in successfully will land on the customer experience. Branch on user['role'] and route to the appropriate home (e.g. /agent_home) — or fail the login if the role isn't recognized.

🛡️ Proposed fix
           if (user != null) {
-            // If needed, check user['role'] to route appropriately
-            context.go('/customer_home');
+            final role = user['role'] as String?;
+            switch (role) {
+              case 'customer':
+                context.go('/customer_home');
+                break;
+              case 'agent':
+                context.go('/agent_home');
+                break;
+              default:
+                ScaffoldMessenger.of(context).showSnackBar(
+                  const SnackBar(content: Text('دور المستخدم غير معروف')),
+                );
+            }
           } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// After async gaps, check if the widget is still mounted
if (mounted) {
if (user != null) {
// If needed, check user['role'] to route appropriately
context.go('/customer_home');
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('رقم الجوال أو كلمة المرور غير صحيحة'),
backgroundColor: Colors.red,
),
);
}
}
// After async gaps, check if the widget is still mounted
if (mounted) {
if (user != null) {
final role = user['role'] as String?;
switch (role) {
case 'customer':
context.go('/customer_home');
break;
case 'agent':
context.go('/agent_home');
break;
default:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('دور المستخدم غير معروف')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('رقم الجوال أو كلمة المرور غير صحيحة'),
backgroundColor: Colors.red,
),
);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/lib/features/auth/presentation/login_screen.dart` around
lines 50 - 63, The current post-login routing always sends authenticated users
to '/customer_home' even though loginUser returns a user record with a role;
update the routing logic in the block that checks if (mounted) after login to
inspect user['role'] (from the loginUser result) and branch accordingly (e.g.,
context.go('/agent_home') for role == 'agent', context.go('/customer_home') for
role == 'customer'), and handle unrecognized roles by showing an error SnackBar
or failing the login; keep the existing mounted check and Snackbar usage
(ScaffoldMessenger.of(context).showSnackBar) and only change the branch that
currently always calls context.go('/customer_home').

flutter_map: ^8.3.0
latlong2: ^0.9.1
geocoding: ^4.0.0
file_picker: ^11.0.2
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify file_picker is genuinely unused in the codebase
rg -nP "package:file_picker|FilePicker|file_picker" --type=dart

Repository: Klvvr1/BrightClean

Length of output: 44


Remove unused dependency: file_picker: ^11.0.2

This package is not imported or used anywhere in the codebase. The registration flow uses image_picker exclusively. Unused native plugins increase app size and may introduce unnecessary Android/iOS permissions and configuration requirements—remove this entry from pubspec.yaml.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/pubspec.yaml` at line 48, Remove the unused dependency
entry "file_picker: ^11.0.2" from pubspec.yaml; search the codebase for any
imports or references to "file_picker" or "FilePicker" to confirm there are no
usages (the project uses "image_picker" instead), delete the dependency line,
run flutter pub get to update lockfiles, and if present remove any
platform-specific configuration or Android/iOS permission entries that were
added only for file_picker.

Comment on lines +1 to +2
importScripts('https://unpkg.com/sql.js@1.8.0/dist/sql-wasm.js');
importScripts('https://unpkg.com/sqflite_common_ffi_web@0.4.0/assets/sqflite_sw.js');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid loading the SQLite worker dependencies from a third‑party CDN at runtime.

Pulling sql.js and sqflite_common_ffi_web from unpkg.com inside a service worker has several real downsides:

  • Reliability/offline: any unpkg outage or restrictive network (corporate proxies, regions where unpkg is blocked) breaks the DB layer, after the 15s timeout in database_helper.dart.
  • Supply chain / security: importScripts does not support Subresource Integrity, so a compromised CDN response runs arbitrary code in your origin's service-worker scope (full access to caches, fetch interception, IndexedDB).
  • Privacy: every user load reveals the origin to a third party.
  • Version drift risk: the sqflite_common_ffi_web@0.4.0 JS must match the Dart sqflite_common_ffi_web version resolved in pubspec.lock; a mismatch can produce subtle DB init failures.

The recommended setup from the sqflite_common_ffi_web package is to vendor sqflite_sw.js and sqlite3.wasm (or sql-wasm.js) into web/ (e.g. via dart run sqflite_common_ffi_web:setup) and load them locally:

♻️ Suggested replacement (after vendoring assets into web/)
-importScripts('https://unpkg.com/sql.js@1.8.0/dist/sql-wasm.js');
-importScripts('https://unpkg.com/sqflite_common_ffi_web@0.4.0/assets/sqflite_sw.js');
+importScripts('sql-wasm.js');
+importScripts('sqflite_sw_helper.js'); // or whatever the setup script emits
What is the recommended setup for sqflite_common_ffi_web service worker on Flutter web, and what versions of sql.js / sqlite3.wasm does it expect?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brightcleanproject/web/sqflite_sw.js` around lines 1 - 2, The service worker
currently imports sql.js and sqflite_sw.js from unpkg via importScripts, which
is unsafe and unreliable; instead vendor the runtime assets locally (run dart
run sqflite_common_ffi_web:setup to copy sqflite_sw.js and the matching
sqlite3.wasm/sql-wasm.js into your project's web/), replace the external
importScripts calls in sqflite_sw.js with relative local paths to those vendored
files, and ensure the vendored sql.js/wasm version matches the version resolved
in pubspec.lock (e.g., sql.js 1.8.0 / corresponding sqlite3.wasm expected by
sqflite_common_ffi_web version) so the worker and Dart package remain
compatible.

@Klvvr1 Klvvr1 closed this Apr 27, 2026
@Klvvr1
Copy link
Copy Markdown
Owner

Klvvr1 commented Apr 27, 2026

Very Distracted Changes for 15 Files and more than 1500 Lines, Although The task is for the Agent screen only.

@Klvvr1 Klvvr1 deleted the Agent_Screen branch April 27, 2026 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent Registration Screen

2 participants