Compare commits
4 Commits
211ebdf1d8
...
cae04b3ae7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cae04b3ae7 | ||
|
|
9fb4ba621b | ||
|
|
19d9a3dc2d | ||
|
|
fc9b5e967f |
@@ -420,7 +420,7 @@ ref.watch(userProvider).when(
|
||||
|
||||
data: (user) => UserView(user),
|
||||
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
|
||||
error: (error, stack) => ErrorView(error),
|
||||
|
||||
@@ -443,7 +443,7 @@ switch (userState) {
|
||||
|
||||
case AsyncLoading():
|
||||
|
||||
return CircularProgressIndicator();
|
||||
return const CustomLoadingIndicator();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1117,5 +1117,5 @@ All recent implementations follow:
|
||||
- ✅ AppBar standardization
|
||||
- ✅ CachedNetworkImage for all remote images
|
||||
- ✅ Proper error handling
|
||||
- ✅ Loading states (CircularProgressIndicator)
|
||||
- ✅ Loading states (CustomLoadingIndicator)
|
||||
- ✅ Empty states with helpful messages
|
||||
|
||||
@@ -3,6 +3,9 @@ import java.io.FileInputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services")
|
||||
// END: FlutterFire Configuration
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
|
||||
29
android/app/google-services.json
Normal file
29
android/app/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "147309310656",
|
||||
"project_id": "dbiz-partner",
|
||||
"storage_bucket": "dbiz-partner.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:147309310656:android:86613d8ffc85576fdc7325",
|
||||
"android_client_info": {
|
||||
"package_name": "com.dbiz.partner"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyA60iGPuHOQMJUA0m5aSimzevPAiiaB4pE"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -20,6 +20,9 @@ pluginManagement {
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.1" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services") version("4.3.15") apply false
|
||||
// END: FlutterFire Configuration
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
}
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ class _CartPageState extends ConsumerState<CartPage> {
|
||||
}
|
||||
: null,
|
||||
child: _isSyncing
|
||||
? CircularProgressIndicator() // Show loading while syncing
|
||||
? const CustomLoadingIndicator() // Show loading while syncing
|
||||
: Text('Tiến hành đặt hàng'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -768,5 +768,5 @@ end
|
||||
- ✅ Vietnamese localization
|
||||
- ✅ CachedNetworkImage for all remote images
|
||||
- ✅ Proper error handling
|
||||
- ✅ Loading states (CircularProgressIndicator)
|
||||
- ✅ Loading states (CustomLoadingIndicator)
|
||||
- ✅ Empty states with helpful messages
|
||||
|
||||
@@ -257,7 +257,7 @@ int stars = apiRatingToStars(0.8); // 4
|
||||
- Added date formatting function (`_formatDate`)
|
||||
|
||||
**States**:
|
||||
1. **Loading**: Shows CircularProgressIndicator
|
||||
1. **Loading**: Shows CustomLoadingIndicator
|
||||
2. **Error**: Shows error icon and message
|
||||
3. **Empty**: Shows "Chưa có đánh giá nào" with call-to-action
|
||||
4. **Data**: Shows rating overview and review list
|
||||
@@ -553,7 +553,7 @@ Widget build(BuildContext context, WidgetRef ref) {
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ RatingProvider CountProvider in UI components)
|
||||
```
|
||||
1. Initial State (Loading)
|
||||
├─► productReviewsProvider returns AsyncValue.loading()
|
||||
└─► UI shows CircularProgressIndicator
|
||||
└─► UI shows CustomLoadingIndicator
|
||||
|
||||
2. Loading State → Data State
|
||||
├─► API call succeeds
|
||||
|
||||
@@ -60,7 +60,7 @@ class ReviewsListPage extends ConsumerWidget {
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
error: (error, stack) => Center(
|
||||
child: Text('Error: $error'),
|
||||
@@ -263,7 +263,7 @@ class _SimpleReviewFormState extends ConsumerState<SimpleReviewForm> {
|
||||
child: ElevatedButton(
|
||||
onPressed: _isSubmitting ? null : _submitReview,
|
||||
child: _isSubmitting
|
||||
? const CircularProgressIndicator()
|
||||
? const const CustomLoadingIndicator()
|
||||
: const Text('Submit Review'),
|
||||
),
|
||||
),
|
||||
@@ -351,7 +351,7 @@ class _PaginatedReviewsListState
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator()
|
||||
? const const CustomLoadingIndicator()
|
||||
: ElevatedButton(
|
||||
onPressed: _loadMoreReviews,
|
||||
child: const Text('Load More'),
|
||||
@@ -430,7 +430,7 @@ class RefreshableReviewsList extends ConsumerWidget {
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(40),
|
||||
child: CircularProgressIndicator(),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -540,7 +540,7 @@ class _FilteredReviewsListState extends ConsumerState<FilteredReviewsList> {
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
error: (error, stack) => Center(
|
||||
child: Text('Error: $error'),
|
||||
@@ -662,7 +662,7 @@ class ReviewsWithRetry extends ConsumerWidget {
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
|
||||
@@ -30,7 +30,7 @@ final reviewsAsync = ref.watch(productReviewsProvider(itemId));
|
||||
|
||||
reviewsAsync.when(
|
||||
data: (reviews) => /* show reviews */,
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => /* show error */,
|
||||
);
|
||||
```
|
||||
|
||||
1
firebase.json
Normal file
1
firebase.json
Normal file
@@ -0,0 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"dbiz-partner","appId":"1:147309310656:android:86613d8ffc85576fdc7325","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"dbiz-partner","appId":"1:147309310656:ios:aa59724d2c6b4620dc7325","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"dbiz-partner","configurations":{"android":"1:147309310656:android:86613d8ffc85576fdc7325","ios":"1:147309310656:ios:aa59724d2c6b4620dc7325"}}}}}}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '13.0'
|
||||
platform :ios, '15.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@@ -39,7 +39,7 @@ end
|
||||
# OneSignal Notification Service Extension (OUTSIDE Runner target)
|
||||
target 'OneSignalNotificationServiceExtension' do
|
||||
use_frameworks!
|
||||
pod 'OneSignalXCFramework', '>= 5.0.0', '< 6.0'
|
||||
pod 'OneSignalXCFramework', '5.2.14'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
@@ -48,7 +48,7 @@ post_install do |installer|
|
||||
|
||||
# Ensure consistent deployment target
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
225
ios/Podfile.lock
225
ios/Podfile.lock
@@ -35,65 +35,132 @@ PODS:
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Firebase/CoreOnly (12.4.0):
|
||||
- FirebaseCore (~> 12.4.0)
|
||||
- Firebase/Messaging (12.4.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 12.4.0)
|
||||
- firebase_analytics (12.0.4):
|
||||
- firebase_core
|
||||
- FirebaseAnalytics (= 12.4.0)
|
||||
- Flutter
|
||||
- firebase_core (4.2.1):
|
||||
- Firebase/CoreOnly (= 12.4.0)
|
||||
- Flutter
|
||||
- firebase_messaging (16.0.4):
|
||||
- Firebase/Messaging (= 12.4.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- FirebaseAnalytics (12.4.0):
|
||||
- FirebaseAnalytics/Default (= 12.4.0)
|
||||
- FirebaseCore (~> 12.4.0)
|
||||
- FirebaseInstallations (~> 12.4.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseAnalytics/Default (12.4.0):
|
||||
- FirebaseCore (~> 12.4.0)
|
||||
- FirebaseInstallations (~> 12.4.0)
|
||||
- GoogleAppMeasurement/Default (= 12.4.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseCore (12.4.0):
|
||||
- FirebaseCoreInternal (~> 12.4.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- FirebaseCoreInternal (12.4.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FirebaseInstallations (12.4.0):
|
||||
- FirebaseCore (~> 12.4.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (12.4.0):
|
||||
- FirebaseCore (~> 12.4.0)
|
||||
- FirebaseInstallations (~> 12.4.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Reachability (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- Flutter (1.0.0)
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- GoogleDataTransport (9.4.1):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30911.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleMLKit/BarcodeScanning (6.0.0):
|
||||
- GoogleMLKit/MLKitCore
|
||||
- MLKitBarcodeScanning (~> 5.0.0)
|
||||
- GoogleMLKit/MLKitCore (6.0.0):
|
||||
- MLKitCommon (~> 11.0.0)
|
||||
- GoogleToolboxForMac/Defines (4.2.1)
|
||||
- GoogleToolboxForMac/Logger (4.2.1):
|
||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
|
||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||
- GoogleUtilities/Environment (7.13.3):
|
||||
- GoogleAdsOnDeviceConversion (3.1.0):
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/Core (12.4.0):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/Default (12.4.0):
|
||||
- GoogleAdsOnDeviceConversion (~> 3.1.0)
|
||||
- GoogleAppMeasurement/Core (= 12.4.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (= 12.4.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (12.4.0):
|
||||
- GoogleAppMeasurement/Core (= 12.4.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleDataTransport (10.1.0):
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Privacy
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.13.3):
|
||||
- GoogleUtilities/Environment (8.1.0):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (7.13.3)
|
||||
- GoogleUtilities/UserDefaults (7.13.3):
|
||||
- GoogleUtilities/MethodSwizzler (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilitiesComponents (1.1.0):
|
||||
- GoogleUtilities/Network (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMSessionFetcher/Core (3.5.0)
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (8.1.0)":
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.1.0)
|
||||
- GoogleUtilities/Reachability (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- integration_test (0.0.1):
|
||||
- Flutter
|
||||
- MLImage (1.0.0-beta5)
|
||||
- MLKitBarcodeScanning (5.0.0):
|
||||
- MLKitCommon (~> 11.0)
|
||||
- MLKitVision (~> 7.0)
|
||||
- MLKitCommon (11.0.0):
|
||||
- GoogleDataTransport (< 10.0, >= 9.4.1)
|
||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||
- GoogleUtilities/UserDefaults (< 8.0, >= 7.13.0)
|
||||
- GoogleUtilitiesComponents (~> 1.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||
- MLKitVision (7.0.0):
|
||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||
- MLImage (= 1.0.0-beta5)
|
||||
- MLKitCommon (~> 11.0)
|
||||
- mobile_scanner (5.2.3):
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- GoogleMLKit/BarcodeScanning (~> 6.0.0)
|
||||
- nanopb (2.30910.0):
|
||||
- nanopb/decode (= 2.30910.0)
|
||||
- nanopb/encode (= 2.30910.0)
|
||||
- nanopb/decode (2.30910.0)
|
||||
- nanopb/encode (2.30910.0)
|
||||
- FlutterMacOS
|
||||
- nanopb (3.30910.0):
|
||||
- nanopb/decode (= 3.30910.0)
|
||||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- onesignal_flutter (5.3.4):
|
||||
- Flutter
|
||||
- OneSignalXCFramework (= 5.2.14)
|
||||
@@ -149,9 +216,9 @@ PODS:
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.4.0)
|
||||
- SDWebImage (5.21.3):
|
||||
- SDWebImage/Core (= 5.21.3)
|
||||
- SDWebImage/Core (5.21.3)
|
||||
- SDWebImage (5.21.4):
|
||||
- SDWebImage/Core (= 5.21.4)
|
||||
- SDWebImage/Core (5.21.4)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -167,13 +234,16 @@ PODS:
|
||||
DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- onesignal_flutter (from `.symlinks/plugins/onesignal_flutter/ios`)
|
||||
- OneSignalXCFramework (< 6.0, >= 5.0.0)
|
||||
- OneSignalXCFramework (= 5.2.14)
|
||||
- open_file_ios (from `.symlinks/plugins/open_file_ios/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
@@ -185,16 +255,16 @@ SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- Firebase
|
||||
- FirebaseAnalytics
|
||||
- FirebaseCore
|
||||
- FirebaseCoreInternal
|
||||
- FirebaseInstallations
|
||||
- FirebaseMessaging
|
||||
- GoogleAdsOnDeviceConversion
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
- GoogleMLKit
|
||||
- GoogleToolboxForMac
|
||||
- GoogleUtilities
|
||||
- GoogleUtilitiesComponents
|
||||
- GTMSessionFetcher
|
||||
- MLImage
|
||||
- MLKitBarcodeScanning
|
||||
- MLKitCommon
|
||||
- MLKitVision
|
||||
- nanopb
|
||||
- OneSignalXCFramework
|
||||
- PromisesObjC
|
||||
@@ -206,6 +276,12 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
firebase_analytics:
|
||||
:path: ".symlinks/plugins/firebase_analytics/ios"
|
||||
firebase_core:
|
||||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
firebase_messaging:
|
||||
:path: ".symlinks/plugins/firebase_messaging/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_secure_storage:
|
||||
@@ -215,7 +291,7 @@ EXTERNAL SOURCES:
|
||||
integration_test:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/ios"
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
onesignal_flutter:
|
||||
:path: ".symlinks/plugins/onesignal_flutter/ios"
|
||||
open_file_ios:
|
||||
@@ -236,34 +312,37 @@ SPEC CHECKSUMS:
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
||||
firebase_analytics: 2b372cc13c077de5f1ac37e232bacd5bacb41963
|
||||
firebase_core: e6b8bb503b7d1d9856e698d4f193f7b414e6bf1f
|
||||
firebase_messaging: fc7b6af84f4cd885a4999f51ea69ef20f380d70d
|
||||
FirebaseAnalytics: 0fc2b20091f0ddd21bf73397cf8f0eb5346dc24f
|
||||
FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3
|
||||
FirebaseCoreInternal: d7f5a043c2cd01a08103ab586587c1468047bca6
|
||||
FirebaseInstallations: ae9f4902cb5bf1d0c5eaa31ec1f4e5495a0714e2
|
||||
FirebaseMessaging: d33971b7bb252745ea6cd31ab190d1a1df4b8ed5
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
|
||||
GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065
|
||||
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
|
||||
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
|
||||
GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1
|
||||
GoogleAppMeasurement: 1e718274b7e015cefd846ac1fcf7820c70dc017d
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
MLImage: 1824212150da33ef225fbd3dc49f184cf611046c
|
||||
MLKitBarcodeScanning: 10ca0845a6d15f2f6e911f682a1998b68b973e8b
|
||||
MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1
|
||||
MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1
|
||||
mobile_scanner: 96e91f2e1fb396bb7df8da40429ba8dfad664740
|
||||
nanopb: 438bc412db1928dac798aa6fd75726007be04262
|
||||
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
onesignal_flutter: f69ff09eeaf41cea4b7a841de5a61e79e7fd9a5a
|
||||
OneSignalXCFramework: 7112f3e89563e41ebc23fe807788f11985ac541c
|
||||
open_file_ios: 461db5853723763573e140de3193656f91990d9e
|
||||
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||
SDWebImage: d0184764be51240d49c761c37f53dd017e1ccaaf
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||
|
||||
PODFILE CHECKSUM: 41022e80ca79dfdcc337fcf6a6cca3b7d3cb6958
|
||||
PODFILE CHECKSUM: d8ed968486e3d2e023338569c014e9285ba7bc53
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
41D57CDF80C517B01729B4E6 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1D94728EBD25906AAA78A0D5 /* GoogleService-Info.plist */; };
|
||||
48D410762ED7067500A8B931 /* OneSignalNotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 48D4106F2ED7067500A8B931 /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
58215889146B2DBBD9C81410 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2545A56CA7C5FCC88F0D6DF7 /* Pods_Runner.framework */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
@@ -67,6 +68,7 @@
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
18121E1016DEC4038E74F1F0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
1D94728EBD25906AAA78A0D5 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
23D173C6FEE4F53025C06238 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2545A56CA7C5FCC88F0D6DF7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
@@ -175,6 +177,7 @@
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
D39C332D04678D8C49EEA401 /* Pods */,
|
||||
E0C416BADC6D23D3F5D8CCA9 /* Frameworks */,
|
||||
1D94728EBD25906AAA78A0D5 /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -365,6 +368,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
41D57CDF80C517B01729B4E6 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -73,6 +73,12 @@
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-FIRDebugEnabled"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
|
||||
@@ -7,6 +7,11 @@ import UIKit
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
#if DEBUG
|
||||
var args = ProcessInfo.processInfo.arguments
|
||||
args.append("-FIRDebugEnabled")
|
||||
ProcessInfo.processInfo.setValue(args, forKey: "arguments")
|
||||
#endif
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
30
ios/Runner/GoogleService-Info.plist
Normal file
30
ios/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyAMgNFpkK0ss_uzNl51OqGyQHd0vFc9SxQ</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>147309310656</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.dbiz.partner</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>dbiz-partner</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>dbiz-partner.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:147309310656:ios:aa59724d2c6b4620dc7325</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -10,6 +10,7 @@ library;
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -569,10 +570,10 @@ Future<AuthInterceptor> authInterceptor(Ref ref, Dio dio) async {
|
||||
@riverpod
|
||||
LoggingInterceptor loggingInterceptor(Ref ref) {
|
||||
// Only enable logging in debug mode
|
||||
const bool isDebug = true; // TODO: Replace with kDebugMode from Flutter
|
||||
const bool isDebug = kDebugMode; // TODO: Replace with kDebugMode from Flutter
|
||||
|
||||
return LoggingInterceptor(
|
||||
enableRequestLogging: false,
|
||||
enableRequestLogging: true,
|
||||
enableResponseLogging: isDebug,
|
||||
enableErrorLogging: isDebug,
|
||||
);
|
||||
|
||||
@@ -189,7 +189,7 @@ final class LoggingInterceptorProvider
|
||||
}
|
||||
|
||||
String _$loggingInterceptorHash() =>
|
||||
r'6afa480caa6fcd723dab769bb01601b8a37e20fd';
|
||||
r'79e90e0eb78663d2645d2d7c467e01bc18a30551';
|
||||
|
||||
/// Provider for ErrorTransformerInterceptor
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'dart:developer' as developer;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -248,13 +249,13 @@ class CustomCurlLoggerInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
final curl = _cURLRepresentation(options);
|
||||
// debugPrint(
|
||||
// '╔╣ CURL Request ╠══════════════════════════════════════════════════',
|
||||
// );
|
||||
// debugPrint(curl);
|
||||
// debugPrint(
|
||||
// '╚═════════════════════════════════════════════════════════════════',
|
||||
// );
|
||||
debugPrint(
|
||||
'╔╣ CURL Request ╠══════════════════════════════════════════════════',
|
||||
);
|
||||
debugPrint(curl);
|
||||
debugPrint(
|
||||
'╚═════════════════════════════════════════════════════════════════',
|
||||
);
|
||||
// Also log to dart:developer for better filtering in DevTools
|
||||
developer.log(curl, name: 'DIO_CURL', time: DateTime.now());
|
||||
handler.next(options);
|
||||
@@ -467,7 +468,7 @@ Future<Dio> dio(Ref ref) async {
|
||||
// Add interceptors in order
|
||||
// 1. Custom Curl interceptor (first to log cURL commands)
|
||||
// Uses debugPrint and developer.log for better visibility
|
||||
..interceptors.add(CustomCurlLoggerInterceptor())
|
||||
// ..interceptors.add(CustomCurlLoggerInterceptor())
|
||||
// 2. Logging interceptor
|
||||
..interceptors.add(ref.watch(loggingInterceptorProvider))
|
||||
// 3. Auth interceptor (add tokens to requests)
|
||||
|
||||
@@ -131,7 +131,7 @@ final class DioProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$dioHash() => r'd15bfe824d6501e5cbd56ff152de978030d97be4';
|
||||
String _$dioHash() => r'f15495e99d11744c245e2be892657748aeeb8ae7';
|
||||
|
||||
/// Provider for DioClient
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Future<User> user(UserRef ref, String id) async {
|
||||
final userAsync = ref.watch(userProvider('123'));
|
||||
userAsync.when(
|
||||
data: (user) => Text(user.name),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (e, _) => Text('Error: $e'),
|
||||
);
|
||||
```
|
||||
@@ -202,7 +202,7 @@ final newValue = ref.refresh(userProvider);
|
||||
```dart
|
||||
asyncValue.when(
|
||||
data: (value) => Text(value),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
```
|
||||
@@ -215,7 +215,7 @@ switch (asyncValue) {
|
||||
case AsyncError(:final error):
|
||||
return Text('Error: $error');
|
||||
case AsyncLoading():
|
||||
return CircularProgressIndicator();
|
||||
return const CustomLoadingIndicator();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Connectivity connectivity(Ref ref) {
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -83,7 +83,7 @@ Future<ConnectivityStatus> currentConnectivity(Ref ref) async {
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -428,7 +428,7 @@ final version = ref.watch(appVersionProvider);
|
||||
final userData = ref.watch(userDataProvider);
|
||||
userData.when(
|
||||
data: (data) => Text(data),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
|
||||
@@ -466,7 +466,7 @@ switch (profileState) {
|
||||
case AsyncError(:final error):
|
||||
return Text('Error: $error');
|
||||
case AsyncLoading():
|
||||
return CircularProgressIndicator();
|
||||
return const CustomLoadingIndicator();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ library;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/services/analytics_service.dart';
|
||||
|
||||
import 'package:worker/features/account/domain/entities/address.dart';
|
||||
import 'package:worker/features/account/presentation/pages/address_form_page.dart';
|
||||
@@ -64,7 +65,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
return GoRouter(
|
||||
// Initial route - start with splash screen
|
||||
initialLocation: RouteNames.splash,
|
||||
|
||||
observers: [AnalyticsService.observer],
|
||||
// Redirect based on auth state
|
||||
redirect: (context, state) {
|
||||
final isLoading = authState.isLoading;
|
||||
@@ -131,16 +132,22 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
GoRoute(
|
||||
path: RouteNames.splash,
|
||||
name: RouteNames.splash,
|
||||
pageBuilder: (context, state) =>
|
||||
MaterialPage(key: state.pageKey, child: const SplashPage()),
|
||||
pageBuilder: (context, state) => MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: RouteNames.splash,
|
||||
child: const SplashPage(),
|
||||
),
|
||||
),
|
||||
|
||||
// Authentication Routes
|
||||
GoRoute(
|
||||
path: RouteNames.login,
|
||||
name: RouteNames.login,
|
||||
pageBuilder: (context, state) =>
|
||||
MaterialPage(key: state.pageKey, child: const LoginPage()),
|
||||
pageBuilder: (context, state) => MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: RouteNames.login,
|
||||
child: const LoginPage(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteNames.forgotPassword,
|
||||
@@ -192,16 +199,22 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
GoRoute(
|
||||
path: RouteNames.home,
|
||||
name: RouteNames.home,
|
||||
pageBuilder: (context, state) =>
|
||||
MaterialPage(key: state.pageKey, child: const MainScaffold()),
|
||||
pageBuilder: (context, state) => MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: 'home',
|
||||
child: const MainScaffold(),
|
||||
),
|
||||
),
|
||||
|
||||
// Products Route (full screen, no bottom nav)
|
||||
GoRoute(
|
||||
path: RouteNames.products,
|
||||
name: RouteNames.products,
|
||||
pageBuilder: (context, state) =>
|
||||
MaterialPage(key: state.pageKey, child: const ProductsPage()),
|
||||
pageBuilder: (context, state) => MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: 'products',
|
||||
child: const ProductsPage(),
|
||||
),
|
||||
),
|
||||
|
||||
// Product Detail Route
|
||||
@@ -212,6 +225,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
final productId = state.pathParameters['id'];
|
||||
return MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: 'product_detail',
|
||||
child: ProductDetailPage(productId: productId ?? ''),
|
||||
);
|
||||
},
|
||||
@@ -224,6 +238,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
final productId = state.pathParameters['id'];
|
||||
return MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: 'write_review',
|
||||
child: WriteReviewPage(productId: productId ?? ''),
|
||||
);
|
||||
},
|
||||
@@ -239,6 +254,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
final promotionId = state.pathParameters['id'];
|
||||
return MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: 'promotion_detail',
|
||||
child: PromotionDetailPage(promotionId: promotionId),
|
||||
);
|
||||
},
|
||||
@@ -248,8 +264,11 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
GoRoute(
|
||||
path: RouteNames.cart,
|
||||
name: RouteNames.cart,
|
||||
pageBuilder: (context, state) =>
|
||||
MaterialPage(key: state.pageKey, child: const CartPage()),
|
||||
pageBuilder: (context, state) => MaterialPage(
|
||||
key: state.pageKey,
|
||||
name: 'cart',
|
||||
child: const CartPage(),
|
||||
),
|
||||
),
|
||||
|
||||
// Checkout Route
|
||||
|
||||
88
lib/core/services/analytics_service.dart
Normal file
88
lib/core/services/analytics_service.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Firebase Analytics service for tracking user events across the app.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// // Log add to cart event
|
||||
/// AnalyticsService.logAddToCart(
|
||||
/// productId: 'SKU123',
|
||||
/// productName: 'Gạch men 60x60',
|
||||
/// price: 150000,
|
||||
/// quantity: 2,
|
||||
/// );
|
||||
/// ```
|
||||
class AnalyticsService {
|
||||
AnalyticsService._();
|
||||
|
||||
static final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
|
||||
|
||||
/// Get the analytics instance for NavigatorObserver
|
||||
static FirebaseAnalytics get instance => _analytics;
|
||||
|
||||
/// Get the observer for automatic screen tracking in GoRouter
|
||||
static FirebaseAnalyticsObserver get observer => FirebaseAnalyticsObserver(
|
||||
analytics: _analytics,
|
||||
nameExtractor: (settings) {
|
||||
// GoRouter uses the path as the route name
|
||||
final name = settings.name;
|
||||
if (name != null && name.isNotEmpty && name != '/') {
|
||||
return name;
|
||||
}
|
||||
return settings.name ?? '/';
|
||||
},
|
||||
routeFilter: (route) => route is PageRoute,
|
||||
);
|
||||
|
||||
/// Log screen view manually
|
||||
static Future<void> logScreenView({
|
||||
required String screenName,
|
||||
String? screenClass,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logScreenView(
|
||||
screenName: screenName,
|
||||
screenClass: screenClass,
|
||||
);
|
||||
debugPrint('📊 Analytics: screen_view - $screenName');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log add to cart event
|
||||
///
|
||||
/// [productId] - Product SKU or ID
|
||||
/// [productName] - Product display name
|
||||
/// [price] - Unit price in VND
|
||||
/// [quantity] - Quantity added
|
||||
/// [category] - Optional product category
|
||||
static Future<void> logAddToCart({
|
||||
required String productId,
|
||||
required String productName,
|
||||
required double price,
|
||||
required int quantity,
|
||||
String? brand,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logAddToCart(
|
||||
currency: 'VND',
|
||||
value: price * quantity,
|
||||
items: [
|
||||
AnalyticsEventItem(
|
||||
itemId: productId,
|
||||
itemName: productName,
|
||||
price: price,
|
||||
quantity: quantity,
|
||||
itemBrand: brand,
|
||||
),
|
||||
],
|
||||
);
|
||||
debugPrint('📊 Analytics: add_to_cart - $productName x$quantity');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class FrappeAuthService {
|
||||
}
|
||||
}
|
||||
|
||||
final url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeLogin}';
|
||||
const url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeLogin}';
|
||||
|
||||
// Build cookie header
|
||||
final storedSession = await getStoredSession();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
/// Button variant types for different use cases.
|
||||
enum ButtonVariant {
|
||||
@@ -106,14 +107,7 @@ class CustomButton extends StatelessWidget {
|
||||
/// Builds the button content (text, icon, or loading indicator)
|
||||
Widget _buildContent() {
|
||||
if (isLoading) {
|
||||
return const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
);
|
||||
return const CustomLoadingIndicator(size: 20, color: Colors.white);
|
||||
}
|
||||
|
||||
if (icon != null) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loading_animation_widget/loading_animation_widget.dart';
|
||||
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
|
||||
/// Custom loading indicator widget with optional message text.
|
||||
///
|
||||
/// Displays a centered circular progress indicator with an optional
|
||||
/// Displays a centered three rotating dots animation with an optional
|
||||
/// message below it. Used for loading states throughout the app.
|
||||
///
|
||||
/// Example usage:
|
||||
@@ -32,19 +33,14 @@ class CustomLoadingIndicator extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
color ?? AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
LoadingAnimationWidget.threeRotatingDots(
|
||||
color: color ?? colorScheme.primary,
|
||||
size: size,
|
||||
),
|
||||
if (message != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/database/hive_initializer.dart';
|
||||
import 'package:worker/core/database/models/enums.dart';
|
||||
@@ -360,12 +361,7 @@ class _ProfileCardSection extends ConsumerWidget {
|
||||
shape: BoxShape.circle,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: colorScheme.primary,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.md),
|
||||
Expanded(
|
||||
@@ -492,11 +488,8 @@ class _ProfileCardSection extends ConsumerWidget {
|
||||
shape: BoxShape.circle,
|
||||
color: colorScheme.primaryContainer,
|
||||
),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
child: CustomLoadingIndicator(
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
@@ -676,7 +669,7 @@ class _LogoutButton extends ConsumerWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
CustomLoadingIndicator(),
|
||||
SizedBox(height: 16),
|
||||
Text('Đang đăng xuất...'),
|
||||
],
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -450,13 +451,9 @@ class AddressFormPage extends HookConsumerWidget {
|
||||
isSaving,
|
||||
),
|
||||
icon: isSaving.value
|
||||
? SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: colorScheme.onPrimary,
|
||||
),
|
||||
? CustomLoadingIndicator(
|
||||
color: colorScheme.onPrimary,
|
||||
size: 18,
|
||||
)
|
||||
: const FaIcon(FontAwesomeIcons.floppyDisk, size: 18),
|
||||
label: Text(
|
||||
@@ -800,13 +797,9 @@ class AddressFormPage extends HookConsumerWidget {
|
||||
),
|
||||
if (isLoading) ...[
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
CustomLoadingIndicator(
|
||||
color: colorScheme.primary,
|
||||
size: 12,
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -856,13 +849,9 @@ class AddressFormPage extends HookConsumerWidget {
|
||||
suffixIcon: isLoading
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
child: CustomLoadingIndicator(
|
||||
color: colorScheme.primary,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -253,7 +254,7 @@ class AddressesPage extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -61,21 +62,8 @@ class ProfileEditPage extends HookConsumerWidget {
|
||||
),
|
||||
centerTitle: false,
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(color: colorScheme.primary),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
'Đang tải thông tin...',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: const CustomLoadingIndicator(
|
||||
message: 'Đang tải thông tin...',
|
||||
),
|
||||
),
|
||||
error: (error, stack) => Scaffold(
|
||||
|
||||
@@ -67,7 +67,7 @@ Future<GetUserInfo> getUserInfoUseCase(Ref ref) async {
|
||||
///
|
||||
/// userInfoAsync.when(
|
||||
/// data: (userInfo) => Text(userInfo.fullName),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
///
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/core/utils/validators.dart';
|
||||
@@ -294,15 +295,9 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
|
||||
),
|
||||
),
|
||||
child: _isLoading
|
||||
? SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
? CustomLoadingIndicator(
|
||||
color: colorScheme.onPrimary,
|
||||
size: 20,
|
||||
)
|
||||
: const Text(
|
||||
'Gửi mã OTP',
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
@@ -483,15 +484,9 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
? CustomLoadingIndicator(
|
||||
color: colorScheme.onPrimary,
|
||||
size: 20,
|
||||
)
|
||||
: const Text(
|
||||
'Đăng nhập',
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
@@ -377,15 +378,9 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
|
||||
),
|
||||
),
|
||||
child: _isLoading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
? CustomLoadingIndicator(
|
||||
color: colorScheme.onPrimary,
|
||||
size: 20,
|
||||
)
|
||||
: const Text(
|
||||
'Xác nhận',
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
@@ -410,18 +411,8 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
centerTitle: false,
|
||||
),
|
||||
body: _isLoadingData
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
'Đang tải dữ liệu...',
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
? const CustomLoadingIndicator(
|
||||
message: 'Đang tải dữ liệu...',
|
||||
)
|
||||
: SafeArea(
|
||||
child: Form(
|
||||
@@ -646,15 +637,9 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
),
|
||||
),
|
||||
child: _isLoading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
? CustomLoadingIndicator(
|
||||
color: colorScheme.onPrimary,
|
||||
size: 20,
|
||||
)
|
||||
: const Text(
|
||||
'Đăng ký',
|
||||
@@ -801,9 +786,12 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const SizedBox(
|
||||
loading: () => SizedBox(
|
||||
height: 48,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: CustomLoadingIndicator(
|
||||
color: colorScheme.primary,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
error: (error, stack) => Container(
|
||||
height: 48,
|
||||
@@ -867,9 +855,12 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const SizedBox(
|
||||
loading: () => SizedBox(
|
||||
height: 48,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: CustomLoadingIndicator(
|
||||
color: colorScheme.primary,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
error: (error, stack) => Container(
|
||||
height: 48,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
|
||||
/// Splash Page
|
||||
@@ -61,10 +62,7 @@ class SplashPage extends StatelessWidget {
|
||||
const SizedBox(height: 48.0),
|
||||
|
||||
// Loading Indicator
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(colorScheme.primary),
|
||||
strokeWidth: 3.0,
|
||||
),
|
||||
const CustomLoadingIndicator(),
|
||||
|
||||
const SizedBox(height: 16.0),
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
/// Uses Riverpod 3.0 with code generation for type-safe state management.
|
||||
library;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/core/constants/api_constants.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/core/services/frappe_auth_service.dart';
|
||||
import 'package:worker/features/auth/data/datasources/auth_local_datasource.dart';
|
||||
import 'package:worker/features/auth/data/datasources/auth_remote_datasource.dart';
|
||||
import 'package:worker/features/auth/data/models/auth_session_model.dart';
|
||||
import 'package:worker/features/auth/domain/entities/user.dart';
|
||||
|
||||
part 'auth_provider.g.dart';
|
||||
@@ -80,10 +78,6 @@ class Auth extends _$Auth {
|
||||
Future<FrappeAuthService> get _frappeAuthService async =>
|
||||
await ref.read(frappeAuthServiceProvider.future);
|
||||
|
||||
/// Get auth remote data source
|
||||
Future<AuthRemoteDataSource> get _remoteDataSource async =>
|
||||
await ref.read(authRemoteDataSourceProvider.future);
|
||||
|
||||
/// Initialize with saved session if available
|
||||
@override
|
||||
Future<User?> build() async {
|
||||
@@ -170,7 +164,6 @@ class Auth extends _$Auth {
|
||||
}
|
||||
|
||||
final frappeService = await _frappeAuthService;
|
||||
final remoteDataSource = await _remoteDataSource;
|
||||
|
||||
// Get current session (should exist from app startup)
|
||||
final currentSession = await frappeService.getStoredSession();
|
||||
@@ -183,22 +176,8 @@ class Auth extends _$Auth {
|
||||
}
|
||||
}
|
||||
|
||||
// Get stored session again
|
||||
final session = await frappeService.getStoredSession();
|
||||
if (session == null) {
|
||||
throw Exception('Session not available');
|
||||
}
|
||||
|
||||
// Call login API with current session
|
||||
final loginResponse = await remoteDataSource.login(
|
||||
phone: phoneNumber,
|
||||
csrfToken: session['csrfToken']!,
|
||||
sid: session['sid']!,
|
||||
password: password, // Reserved for future use
|
||||
);
|
||||
|
||||
// Update FlutterSecureStorage with new authenticated session
|
||||
await frappeService.login(phoneNumber, password: password);
|
||||
// Call login API and store session
|
||||
final loginResponse = await frappeService.login(phoneNumber, password: password);
|
||||
|
||||
// Save rememberMe preference
|
||||
await _localDataSource.saveRememberMe(rememberMe);
|
||||
|
||||
@@ -272,7 +272,7 @@ final class AuthProvider extends $AsyncNotifierProvider<Auth, User?> {
|
||||
Auth create() => Auth();
|
||||
}
|
||||
|
||||
String _$authHash() => r'f0438cf6eb9eb17c0afc6b23055acd09926b21ae';
|
||||
String _$authHash() => r'd851980cad7a624f00eba69e19d8a4fee22008e7';
|
||||
|
||||
/// Authentication Provider
|
||||
///
|
||||
|
||||
@@ -16,8 +16,8 @@ abstract class CartRemoteDataSource {
|
||||
/// Add items to cart
|
||||
///
|
||||
/// [items] - List of items with item_id, quantity, and amount
|
||||
/// Returns list of cart items from API
|
||||
Future<List<CartItemModel>> addToCart({
|
||||
/// Returns true if successful
|
||||
Future<bool> addToCart({
|
||||
required List<Map<String, dynamic>> items,
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ class CartRemoteDataSourceImpl implements CartRemoteDataSource {
|
||||
final DioClient _dioClient;
|
||||
|
||||
@override
|
||||
Future<List<CartItemModel>> addToCart({
|
||||
Future<bool> addToCart({
|
||||
required List<Map<String, dynamic>> items,
|
||||
}) async {
|
||||
try {
|
||||
@@ -78,8 +78,7 @@ class CartRemoteDataSourceImpl implements CartRemoteDataSource {
|
||||
throw const ParseException('Invalid response format from add to cart API');
|
||||
}
|
||||
|
||||
// After adding, fetch updated cart
|
||||
return await getUserCart();
|
||||
return true;
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} catch (e) {
|
||||
|
||||
@@ -32,7 +32,7 @@ class CartRepositoryImpl implements CartRepository {
|
||||
final CartLocalDataSource _localDataSource;
|
||||
|
||||
@override
|
||||
Future<List<CartItem>> addToCart({
|
||||
Future<bool> addToCart({
|
||||
required List<String> itemIds,
|
||||
required List<double> quantities,
|
||||
required List<double> prices,
|
||||
@@ -57,17 +57,24 @@ class CartRepositoryImpl implements CartRepository {
|
||||
|
||||
// Try API first
|
||||
try {
|
||||
final cartItemModels = await _remoteDataSource.addToCart(items: items);
|
||||
final success = await _remoteDataSource.addToCart(items: items);
|
||||
|
||||
// Sync to local storage
|
||||
await _localDataSource.saveCartItems(cartItemModels);
|
||||
// Also save to local storage for offline access
|
||||
if (success) {
|
||||
for (int i = 0; i < itemIds.length; i++) {
|
||||
final cartItemModel = _createCartItemModel(
|
||||
productId: itemIds[i],
|
||||
quantity: quantities[i],
|
||||
unitPrice: prices[i],
|
||||
);
|
||||
await _localDataSource.addCartItem(cartItemModel);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to domain entities
|
||||
return cartItemModels.map(_modelToEntity).toList();
|
||||
return success;
|
||||
} on NetworkException catch (e) {
|
||||
// If no internet, add to local cart only
|
||||
if (e is NoInternetException || e is TimeoutException) {
|
||||
// Add items to local cart
|
||||
for (int i = 0; i < itemIds.length; i++) {
|
||||
final cartItemModel = _createCartItemModel(
|
||||
productId: itemIds[i],
|
||||
@@ -79,9 +86,7 @@ class CartRepositoryImpl implements CartRepository {
|
||||
|
||||
// TODO: Queue for sync when online
|
||||
|
||||
// Return local cart items
|
||||
final localItems = await _localDataSource.getCartItems();
|
||||
return localItems.map(_modelToEntity).toList();
|
||||
return true;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
@@ -167,7 +172,7 @@ class CartRepositoryImpl implements CartRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<CartItem>> updateQuantity({
|
||||
Future<bool> updateQuantity({
|
||||
required String itemId,
|
||||
required double quantity,
|
||||
required double price,
|
||||
|
||||
@@ -22,14 +22,13 @@ import 'package:worker/features/cart/domain/entities/cart_item.dart';
|
||||
abstract class CartRepository {
|
||||
/// Add items to cart
|
||||
///
|
||||
/// [items] - List of cart items to add
|
||||
/// [itemIds] - Product ERPNext item codes
|
||||
/// [quantities] - Quantities for each item
|
||||
/// [prices] - Unit prices for each item
|
||||
///
|
||||
/// Returns list of cart items on success.
|
||||
/// Returns true if successful.
|
||||
/// Throws exceptions on failure.
|
||||
Future<List<CartItem>> addToCart({
|
||||
Future<bool> addToCart({
|
||||
required List<String> itemIds,
|
||||
required List<double> quantities,
|
||||
required List<double> prices,
|
||||
@@ -57,9 +56,9 @@ abstract class CartRepository {
|
||||
/// [quantity] - New quantity
|
||||
/// [price] - Unit price
|
||||
///
|
||||
/// Returns updated cart item list.
|
||||
/// Returns true if successful.
|
||||
/// Throws exceptions on failure.
|
||||
Future<List<CartItem>> updateQuantity({
|
||||
Future<bool> updateQuantity({
|
||||
required String itemId,
|
||||
required double quantity,
|
||||
required double price,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
@@ -102,7 +103,7 @@ class _CartPageState extends ConsumerState<CartPage> {
|
||||
],
|
||||
),
|
||||
body: cartState.isLoading && cartState.isEmpty
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const CustomLoadingIndicator()
|
||||
: cartState.errorMessage != null && cartState.isEmpty
|
||||
? _buildErrorState(context, cartState.errorMessage!)
|
||||
: cartState.isEmpty
|
||||
@@ -132,9 +133,7 @@ class _CartPageState extends ConsumerState<CartPage> {
|
||||
if (cartState.isLoading)
|
||||
Container(
|
||||
color: colorScheme.onSurface.withValues(alpha: 0.1),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -315,14 +314,9 @@ class _CartPageState extends ConsumerState<CartPage> {
|
||||
elevation: 0,
|
||||
),
|
||||
child: _isSyncing
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(colorScheme.surface),
|
||||
),
|
||||
? CustomLoadingIndicator(
|
||||
color: colorScheme.surface,
|
||||
size: 20,
|
||||
)
|
||||
: Text(
|
||||
'Tiến hành đặt hàng',
|
||||
@@ -425,7 +419,7 @@ class _CartPageState extends ConsumerState<CartPage> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => context.go(RouteNames.products),
|
||||
onPressed: () => context.push(RouteNames.products),
|
||||
icon: const FaIcon(FontAwesomeIcons.bagShopping, size: 20),
|
||||
label: const Text('Xem sản phẩm'),
|
||||
),
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
@@ -177,9 +178,7 @@ class CheckoutPage extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
error: (error, stack) => Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/theme/typography.dart';
|
||||
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
||||
import 'package:worker/features/cart/presentation/providers/cart_state.dart';
|
||||
@@ -128,8 +129,8 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: Center(
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
|
||||
@@ -6,6 +6,7 @@ library;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
@@ -93,8 +94,8 @@ class CheckoutSubmitButton extends HookConsumerWidget {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
builder: (context) => Center(
|
||||
child: CustomLoadingIndicator(color: Theme.of(context).colorScheme.primary, size: 40),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ class FavoritesPage extends ConsumerWidget {
|
||||
return ProductCard(productId: productId);
|
||||
},
|
||||
),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class FavoritesPage extends ConsumerWidget {
|
||||
return ProductTile(productId: productId);
|
||||
},
|
||||
),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => ErrorWidget(error),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,11 +204,11 @@ class FavoritesPage extends ConsumerWidget {
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const Center(child: const CustomLoadingIndicator()),
|
||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const Center(child: const CustomLoadingIndicator()),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -368,7 +368,7 @@ class FavoriteProductsList extends ConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
loading: () => const CircularProgressIndicator(),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
}
|
||||
@@ -417,7 +417,7 @@ class FavoritesPageWithRefresh extends ConsumerWidget {
|
||||
return ListTile(title: Text('Product: $productId'));
|
||||
},
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const Center(child: const CustomLoadingIndicator()),
|
||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||
),
|
||||
),
|
||||
@@ -466,7 +466,7 @@ class FavoriteButtonWithLoadingState extends ConsumerWidget {
|
||||
loading: () => const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: CustomLoadingIndicator(strokeWidth: 2),
|
||||
),
|
||||
error: (error, stack) => IconButton(
|
||||
icon: const Icon(Icons.error, color: Colors.grey),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -302,14 +303,14 @@ class FavoritesPage extends HookConsumerWidget {
|
||||
},
|
||||
child: _FavoritesGrid(products: previousValue),
|
||||
),
|
||||
const Positioned(
|
||||
Positioned(
|
||||
top: 16,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
@@ -319,12 +320,10 @@ class FavoritesPage extends HookConsumerWidget {
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text('Đang tải...'),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Đang tải...'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
||||
@@ -81,7 +83,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
error: (error, stack) => Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
@@ -133,10 +135,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
loading: () => const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
loading: () => _buildPromotionsShimmer(colorScheme),
|
||||
error: (error, stack) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
@@ -241,4 +240,93 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build shimmer loading for promotions section
|
||||
Widget _buildPromotionsShimmer(ColorScheme colorScheme) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title shimmer
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Chương trình ưu đãi',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Cards shimmer
|
||||
SizedBox(
|
||||
height: 210,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: colorScheme.surfaceContainerHighest,
|
||||
highlightColor: colorScheme.surface,
|
||||
child: Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Image placeholder
|
||||
Container(
|
||||
height: 140,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Text placeholders
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 200,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: 140,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ part 'member_card_provider.g.dart';
|
||||
///
|
||||
/// memberCardAsync.when(
|
||||
/// data: (memberCard) => MemberCardWidget(memberCard: memberCard),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
/// Provider: Promotions Provider
|
||||
///
|
||||
/// Manages the state of promotions data using Riverpod.
|
||||
/// Provides access to active promotions throughout the app.
|
||||
/// Uses the same data source as news articles (single API call).
|
||||
///
|
||||
/// Uses AsyncNotifierProvider for automatic loading, error, and data states.
|
||||
library;
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/features/home/data/datasources/home_local_datasource.dart';
|
||||
import 'package:worker/features/home/data/repositories/home_repository_impl.dart';
|
||||
import 'package:worker/features/home/domain/entities/promotion.dart';
|
||||
import 'package:worker/features/home/domain/usecases/get_promotions.dart';
|
||||
import 'package:worker/features/news/presentation/providers/news_provider.dart';
|
||||
|
||||
part 'promotions_provider.g.dart';
|
||||
|
||||
/// Max number of promotions to display on home page
|
||||
const int _maxPromotions = 5;
|
||||
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -26,38 +28,27 @@ part 'promotions_provider.g.dart';
|
||||
///
|
||||
/// promotionsAsync.when(
|
||||
/// data: (promotions) => PromotionSlider(promotions: promotions),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@riverpod
|
||||
class PromotionsNotifier extends _$PromotionsNotifier {
|
||||
@override
|
||||
Future<List<Promotion>> build() async {
|
||||
// Initialize dependencies
|
||||
final localDataSource = const HomeLocalDataSourceImpl();
|
||||
final repository = HomeRepositoryImpl(localDataSource: localDataSource);
|
||||
final useCase = GetPromotions(repository);
|
||||
Future<List<Promotion>> promotions(Ref ref) async {
|
||||
// Use newsArticles provider (same API call, no duplicate request)
|
||||
final articles = await ref.watch(newsArticlesProvider.future);
|
||||
|
||||
// Fetch promotions (only active ones)
|
||||
return await useCase();
|
||||
}
|
||||
// Take max 5 articles and convert to Promotion
|
||||
final limitedArticles = articles.take(_maxPromotions).toList();
|
||||
|
||||
/// Refresh promotions data
|
||||
///
|
||||
/// Forces a refresh from the server (when API is available).
|
||||
/// Updates the cached state with fresh data.
|
||||
Future<void> refresh() async {
|
||||
// Set loading state
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
// Fetch fresh data
|
||||
state = await AsyncValue.guard(() async {
|
||||
final localDataSource = const HomeLocalDataSourceImpl();
|
||||
final repository = HomeRepositoryImpl(localDataSource: localDataSource);
|
||||
final useCase = GetPromotions(repository);
|
||||
|
||||
return await useCase.refresh();
|
||||
});
|
||||
}
|
||||
return limitedArticles.map((article) {
|
||||
final now = DateTime.now();
|
||||
return Promotion(
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
imageUrl: article.imageUrl,
|
||||
startDate: article.publishedDate,
|
||||
endDate: now.add(const Duration(days: 365)), // Always active
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ part of 'promotions_provider.dart';
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -25,13 +26,14 @@ part of 'promotions_provider.dart';
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@ProviderFor(PromotionsNotifier)
|
||||
const promotionsProvider = PromotionsNotifierProvider._();
|
||||
@ProviderFor(promotions)
|
||||
const promotionsProvider = PromotionsProvider._();
|
||||
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -44,12 +46,20 @@ const promotionsProvider = PromotionsNotifierProvider._();
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
final class PromotionsNotifierProvider
|
||||
extends $AsyncNotifierProvider<PromotionsNotifier, List<Promotion>> {
|
||||
|
||||
final class PromotionsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<Promotion>>,
|
||||
List<Promotion>,
|
||||
FutureOr<List<Promotion>>
|
||||
>
|
||||
with $FutureModifier<List<Promotion>>, $FutureProvider<List<Promotion>> {
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -62,7 +72,7 @@ final class PromotionsNotifierProvider
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
const PromotionsNotifierProvider._()
|
||||
const PromotionsProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
@@ -74,48 +84,18 @@ final class PromotionsNotifierProvider
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$promotionsNotifierHash();
|
||||
String debugGetCreateSourceHash() => _$promotionsHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
PromotionsNotifier create() => PromotionsNotifier();
|
||||
}
|
||||
$FutureProviderElement<List<Promotion>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
String _$promotionsNotifierHash() =>
|
||||
r'3cd866c74ba11c6519e9b63521e1757ef117c7a9';
|
||||
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// // In a ConsumerWidget
|
||||
/// final promotionsAsync = ref.watch(promotionsProvider);
|
||||
///
|
||||
/// promotionsAsync.when(
|
||||
/// data: (promotions) => PromotionSlider(promotions: promotions),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
abstract class _$PromotionsNotifier extends $AsyncNotifier<List<Promotion>> {
|
||||
FutureOr<List<Promotion>> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<AsyncValue<List<Promotion>>, List<Promotion>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<List<Promotion>>, List<Promotion>>,
|
||||
AsyncValue<List<Promotion>>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
FutureOr<List<Promotion>> create(Ref ref) {
|
||||
return promotions(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$promotionsHash() => r'2eac0298d2b84ad5cc50faa6b8a015dbf7b7a1d3';
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/features/home/domain/entities/promotion.dart';
|
||||
|
||||
/// Promotion Slider Widget
|
||||
@@ -126,7 +127,7 @@ class _PromotionCard extends StatelessWidget {
|
||||
placeholder: (context, url) => Container(
|
||||
height: 140,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
height: 140,
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
@@ -66,7 +67,7 @@ class InvoiceDetailPage extends ConsumerWidget {
|
||||
),
|
||||
body: invoiceAsync.when(
|
||||
data: (invoice) => _buildContent(context, invoice),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => _buildErrorState(context, ref, error),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
@@ -74,7 +75,7 @@ class InvoicesPage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => _buildErrorState(context, ref, error),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/database/models/enums.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
@@ -74,7 +75,7 @@ class PointsHistoryPage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => _buildErrorState(error, colorScheme),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/loyalty/domain/entities/points_record.dart';
|
||||
@@ -183,9 +184,7 @@ class PointsRecordsPage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await ref.read(allPointsRecordsProvider.notifier).refresh();
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/features/loyalty/domain/entities/gift_catalog.dart';
|
||||
import 'package:worker/features/loyalty/presentation/providers/loyalty_points_provider.dart';
|
||||
|
||||
@@ -150,8 +151,8 @@ class RewardCard extends ConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: Center(
|
||||
child: CustomLoadingIndicator(color: Theme.of(context).colorScheme.primary, size: 40),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -60,7 +61,7 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
|
||||
}
|
||||
return _buildContent(context, article);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => _buildErrorState(context, error.toString()),
|
||||
),
|
||||
);
|
||||
@@ -126,7 +127,7 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
|
||||
placeholder: (context, url) => Container(
|
||||
height: 250,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
height: 250,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -98,7 +99,7 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
|
||||
loading: () => const SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
),
|
||||
error: (error, stack) =>
|
||||
@@ -158,7 +159,7 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
|
||||
);
|
||||
},
|
||||
loading: () => const SliverFillRemaining(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
error: (error, stack) => SliverFillRemaining(
|
||||
child: _buildErrorState(error.toString()),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
@@ -65,7 +66,7 @@ class FeaturedNewsCard extends StatelessWidget {
|
||||
placeholder: (context, url) => Container(
|
||||
height: 200,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
height: 200,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
@@ -57,11 +58,11 @@ class NewsCard extends StatelessWidget {
|
||||
width: 80,
|
||||
height: 80,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
@@ -56,11 +57,11 @@ class RelatedArticleCard extends StatelessWidget {
|
||||
width: 60,
|
||||
height: 60,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart' hide Notification;
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -52,7 +53,7 @@ class NotificationsPage extends HookConsumerWidget {
|
||||
notifications,
|
||||
selectedCategory,
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) =>
|
||||
_buildErrorState(ref, selectedCategory),
|
||||
),
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:worker/core/enums/status_color.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/features/account/domain/entities/address.dart';
|
||||
import 'package:worker/features/orders/domain/entities/order_detail.dart';
|
||||
import 'package:worker/features/orders/presentation/providers/orders_provider.dart';
|
||||
@@ -135,7 +136,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -1001,7 +1002,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
height: 60,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: CustomLoadingIndicator(size: 20),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
@@ -1524,13 +1525,9 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.onPrimary),
|
||||
),
|
||||
CustomLoadingIndicator(
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text('Đang hủy đơn hàng...'),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -351,7 +352,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
/// Build loading state
|
||||
Widget _buildLoadingState() {
|
||||
return const SliverFillRemaining(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -192,7 +193,7 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -225,9 +226,7 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
border: Border.all(color: colorScheme.outlineVariant),
|
||||
),
|
||||
child: isLoading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
? const CustomLoadingIndicator()
|
||||
: qrCodeData != null && qrCodeData.isNotEmpty
|
||||
? QrImageView(
|
||||
data: qrCodeData,
|
||||
@@ -580,10 +579,7 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
? SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(colorScheme.surface),
|
||||
),
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
)
|
||||
: const FaIcon(FontAwesomeIcons.camera, size: 18),
|
||||
label: Text(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -71,7 +72,7 @@ class PaymentsPage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => _buildErrorState(context, ref, error),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -164,7 +165,7 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -81,7 +81,7 @@ UI components and state management.
|
||||
|
||||
### UI/UX Features
|
||||
- Pull-to-refresh support
|
||||
- Loading states with CircularProgressIndicator
|
||||
- Loading states with CustomLoadingIndicator
|
||||
- Error states with retry button
|
||||
- Empty states with helpful messages
|
||||
- Vietnamese localization
|
||||
|
||||
@@ -7,8 +7,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/services/analytics_service.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
||||
import 'package:worker/features/favorites/presentation/providers/favorites_provider.dart';
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
import 'package:worker/features/products/presentation/providers/products_provider.dart';
|
||||
@@ -153,8 +156,25 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
void _addToCart(Product product) {
|
||||
// TODO: Add to cart logic
|
||||
void _addToCart(Product product) async {
|
||||
// Add to cart via provider
|
||||
await ref.read(cartProvider.notifier).addToCart(
|
||||
product,
|
||||
quantity: _quantity.toDouble(),
|
||||
);
|
||||
|
||||
// Log analytics event
|
||||
await AnalyticsService.logAddToCart(
|
||||
productId: product.productId,
|
||||
productName: product.name,
|
||||
price: product.basePrice,
|
||||
quantity: _quantity,
|
||||
brand: product.itemGroupName,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
@@ -164,7 +184,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
action: SnackBarAction(
|
||||
label: 'Xem giỏ hàng',
|
||||
onPressed: () {
|
||||
// TODO: Navigate to cart
|
||||
context.push('/cart');
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -244,27 +264,20 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
),
|
||||
|
||||
// Sticky Action Bar
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: StickyActionBar(
|
||||
quantity: _quantity,
|
||||
unit: 'm²',
|
||||
conversionOfSm: product.conversionOfSm,
|
||||
uomFromIntroAttributes: product.getIntroAttribute('UOM'),
|
||||
onIncrease: _increaseQuantity,
|
||||
onDecrease: _decreaseQuantity,
|
||||
onQuantityChanged: _updateQuantity,
|
||||
onAddToCart: () => _addToCart(product),
|
||||
),
|
||||
StickyActionBar(
|
||||
quantity: _quantity,
|
||||
unit: 'm²',
|
||||
conversionOfSm: product.conversionOfSm,
|
||||
uomFromIntroAttributes: product.getIntroAttribute('UOM'),
|
||||
onIncrease: _increaseQuantity,
|
||||
onDecrease: _decreaseQuantity,
|
||||
onQuantityChanged: _updateQuantity,
|
||||
onAddToCart: () => _addToCart(product),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => Center(
|
||||
child: CircularProgressIndicator(color: colorScheme.primary),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.xl),
|
||||
|
||||
@@ -7,11 +7,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/services/analytics_service.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
||||
import 'package:worker/features/products/presentation/providers/product_filter_options_provider.dart';
|
||||
import 'package:worker/features/products/presentation/providers/products_provider.dart';
|
||||
import 'package:worker/features/products/presentation/widgets/brand_filter_chips.dart';
|
||||
import 'package:worker/features/products/presentation/widgets/product_filter_drawer.dart';
|
||||
@@ -36,8 +37,7 @@ class ProductsPage extends ConsumerWidget {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final productsAsync = ref.watch(productsProvider);
|
||||
|
||||
// Preload filter options for better UX when opening filter drawer
|
||||
ref.watch(productFilterOptionsProvider);
|
||||
// Filter options loaded lazily when filter drawer is opened (not here)
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colorScheme.surfaceContainerLowest,
|
||||
@@ -45,7 +45,13 @@ class ProductsPage extends ConsumerWidget {
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () {
|
||||
if (context.canPop()) {
|
||||
context.pop();
|
||||
} else {
|
||||
context.go(RouteNames.home);
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text('Sản phẩm', style: TextStyle(color: colorScheme.onSurface)),
|
||||
elevation: AppBarSpecs.elevation,
|
||||
@@ -105,8 +111,10 @@ class ProductsPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
// Brand Filter Chips
|
||||
const BrandFilterChips(),
|
||||
// Brand Filter Chips - only show after products are loaded
|
||||
productsAsync.hasValue
|
||||
? const BrandFilterChips()
|
||||
: const SizedBox(height: 48.0),
|
||||
|
||||
// Products Grid
|
||||
Expanded(
|
||||
@@ -134,6 +142,14 @@ class ProductsPage extends ConsumerWidget {
|
||||
// Add to cart
|
||||
ref.read(cartProvider.notifier).addToCart(product);
|
||||
|
||||
AnalyticsService.logAddToCart(
|
||||
productId: product.productId,
|
||||
productName: product.name,
|
||||
price: product.basePrice,
|
||||
quantity: 1,
|
||||
brand: product.itemGroupName,
|
||||
);
|
||||
|
||||
// Show SnackBar with manual dismissal
|
||||
final messenger = ScaffoldMessenger.of(context)
|
||||
..clearSnackBars();
|
||||
@@ -192,7 +208,7 @@ class ProductsPage extends ConsumerWidget {
|
||||
|
||||
/// Build loading state
|
||||
Widget _buildLoadingState(ColorScheme colorScheme) {
|
||||
return Center(child: CircularProgressIndicator(color: colorScheme.primary));
|
||||
return const CustomLoadingIndicator();
|
||||
}
|
||||
|
||||
/// Build error state
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
@@ -186,7 +187,7 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
|
||||
|
||||
body: productAsync.when(
|
||||
data: (product) => _buildForm(product),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -249,8 +250,8 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
|
||||
width: 80,
|
||||
height: 80,
|
||||
color: colorScheme.surfaceContainerLowest,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: Center(
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
@@ -510,15 +511,7 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
|
||||
elevation: 0,
|
||||
),
|
||||
child: _isSubmitting
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(colorScheme.onPrimary),
|
||||
),
|
||||
)
|
||||
? CustomLoadingIndicator(color: colorScheme.onPrimary, size: 20)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
@@ -99,7 +99,7 @@ class FilterOption {
|
||||
///
|
||||
/// filterOptionsAsync.when(
|
||||
/// data: (options) => ProductFilterDrawer(options: options),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -59,7 +59,7 @@ Future<ProductsRepository> productsRepository(Ref ref) async {
|
||||
///
|
||||
/// productsAsync.when(
|
||||
/// data: (products) => ProductGrid(products: products),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -86,49 +86,56 @@ class Products extends _$Products {
|
||||
// Get repository with injected data sources
|
||||
final repository = await ref.watch(productsRepositoryProvider.future);
|
||||
|
||||
// Fetch first page of products using unified API
|
||||
// Fetch first page of products
|
||||
List<Product> products;
|
||||
|
||||
// Build filter parameters from filter drawer
|
||||
final List<String>? itemGroups = filters.productLines.isNotEmpty
|
||||
? filters.productLines.toList()
|
||||
: null;
|
||||
// Check if any filters or search are active
|
||||
final hasFilters = filters.hasActiveFilters;
|
||||
final hasSearch = searchQuery.isNotEmpty;
|
||||
|
||||
// Use brands from productFiltersProvider (shared by chips and drawer)
|
||||
final List<String>? brands = filters.brands.isNotEmpty
|
||||
? filters.brands.toList()
|
||||
: null;
|
||||
if (!hasFilters && !hasSearch) {
|
||||
// No filters/search: Use simple getAllProducts for faster initial load
|
||||
products = await repository.getAllProducts(
|
||||
limitStart: 0,
|
||||
limitPageLength: pageSize,
|
||||
);
|
||||
} else {
|
||||
// Filters/search active: Use getProductsWithFilters
|
||||
final List<String>? itemGroups = filters.productLines.isNotEmpty
|
||||
? filters.productLines.toList()
|
||||
: null;
|
||||
|
||||
// Build item attributes from filter drawer (sizes, surfaces, colors)
|
||||
final List<Map<String, String>> itemAttributes = [];
|
||||
final List<String>? brands = filters.brands.isNotEmpty
|
||||
? filters.brands.toList()
|
||||
: null;
|
||||
|
||||
// Add size attributes
|
||||
for (final size in filters.sizes) {
|
||||
itemAttributes.add({'attribute': 'Kích thước', 'attribute_value': size});
|
||||
// Build item attributes from filter drawer (sizes, surfaces, colors)
|
||||
final List<Map<String, String>> itemAttributes = [];
|
||||
|
||||
for (final size in filters.sizes) {
|
||||
itemAttributes.add({'attribute': 'Kích thước', 'attribute_value': size});
|
||||
}
|
||||
|
||||
for (final surface in filters.surfaces) {
|
||||
itemAttributes.add({'attribute': 'Bề mặt', 'attribute_value': surface});
|
||||
}
|
||||
|
||||
for (final color in filters.colors) {
|
||||
itemAttributes.add({'attribute': 'Màu sắc', 'attribute_value': color});
|
||||
}
|
||||
|
||||
final String? keyword = hasSearch ? searchQuery : null;
|
||||
|
||||
products = await repository.getProductsWithFilters(
|
||||
limitStart: 0,
|
||||
limitPageLength: pageSize,
|
||||
itemGroups: itemGroups,
|
||||
brands: brands,
|
||||
itemAttributes: itemAttributes.isNotEmpty ? itemAttributes : null,
|
||||
searchKeyword: keyword,
|
||||
);
|
||||
}
|
||||
|
||||
// Add surface attributes
|
||||
for (final surface in filters.surfaces) {
|
||||
itemAttributes.add({'attribute': 'Bề mặt', 'attribute_value': surface});
|
||||
}
|
||||
|
||||
// Add color attributes
|
||||
for (final color in filters.colors) {
|
||||
itemAttributes.add({'attribute': 'Màu sắc', 'attribute_value': color});
|
||||
}
|
||||
|
||||
final String? keyword = searchQuery.isNotEmpty ? searchQuery : null;
|
||||
|
||||
// Use the comprehensive getProductsWithFilters method
|
||||
products = await repository.getProductsWithFilters(
|
||||
limitStart: 0,
|
||||
limitPageLength: pageSize,
|
||||
itemGroups: itemGroups,
|
||||
brands: brands,
|
||||
itemAttributes: itemAttributes.isNotEmpty ? itemAttributes : null,
|
||||
searchKeyword: keyword,
|
||||
);
|
||||
|
||||
// If we got less than pageSize, there are no more products
|
||||
_hasMore = products.length >= pageSize;
|
||||
_currentPage = 1;
|
||||
@@ -149,46 +156,54 @@ class Products extends _$Products {
|
||||
// Calculate pagination parameters
|
||||
final limitStart = _currentPage * pageSize;
|
||||
|
||||
// Build filter parameters (same logic as build() method)
|
||||
final List<String>? itemGroups = filters.productLines.isNotEmpty
|
||||
? filters.productLines.toList()
|
||||
: null;
|
||||
// Check if any filters or search are active
|
||||
final hasFilters = filters.hasActiveFilters;
|
||||
final hasSearch = searchQuery.isNotEmpty;
|
||||
|
||||
// Use brands from productFiltersProvider (shared by chips and drawer)
|
||||
final List<String>? brands = filters.brands.isNotEmpty
|
||||
? filters.brands.toList()
|
||||
: null;
|
||||
List<Product> newProducts;
|
||||
|
||||
// Build item attributes from filter drawer (sizes, surfaces, colors)
|
||||
final List<Map<String, String>> itemAttributes = [];
|
||||
if (!hasFilters && !hasSearch) {
|
||||
// No filters/search: Use simple getAllProducts
|
||||
newProducts = await repository.getAllProducts(
|
||||
limitStart: limitStart,
|
||||
limitPageLength: pageSize,
|
||||
);
|
||||
} else {
|
||||
// Filters/search active: Use getProductsWithFilters
|
||||
final List<String>? itemGroups = filters.productLines.isNotEmpty
|
||||
? filters.productLines.toList()
|
||||
: null;
|
||||
|
||||
// Add size attributes
|
||||
for (final size in filters.sizes) {
|
||||
itemAttributes.add({'attribute': 'Kích thước', 'attribute_value': size});
|
||||
final List<String>? brands = filters.brands.isNotEmpty
|
||||
? filters.brands.toList()
|
||||
: null;
|
||||
|
||||
final List<Map<String, String>> itemAttributes = [];
|
||||
|
||||
for (final size in filters.sizes) {
|
||||
itemAttributes.add({'attribute': 'Kích thước', 'attribute_value': size});
|
||||
}
|
||||
|
||||
for (final surface in filters.surfaces) {
|
||||
itemAttributes.add({'attribute': 'Bề mặt', 'attribute_value': surface});
|
||||
}
|
||||
|
||||
for (final color in filters.colors) {
|
||||
itemAttributes.add({'attribute': 'Màu sắc', 'attribute_value': color});
|
||||
}
|
||||
|
||||
final String? keyword = hasSearch ? searchQuery : null;
|
||||
|
||||
newProducts = await repository.getProductsWithFilters(
|
||||
limitStart: limitStart,
|
||||
limitPageLength: pageSize,
|
||||
itemGroups: itemGroups,
|
||||
brands: brands,
|
||||
itemAttributes: itemAttributes.isNotEmpty ? itemAttributes : null,
|
||||
searchKeyword: keyword,
|
||||
);
|
||||
}
|
||||
|
||||
// Add surface attributes
|
||||
for (final surface in filters.surfaces) {
|
||||
itemAttributes.add({'attribute': 'Bề mặt', 'attribute_value': surface});
|
||||
}
|
||||
|
||||
// Add color attributes
|
||||
for (final color in filters.colors) {
|
||||
itemAttributes.add({'attribute': 'Màu sắc', 'attribute_value': color});
|
||||
}
|
||||
|
||||
final String? keyword = searchQuery.isNotEmpty ? searchQuery : null;
|
||||
|
||||
// Fetch next page using unified API
|
||||
final newProducts = await repository.getProductsWithFilters(
|
||||
limitStart: limitStart,
|
||||
limitPageLength: pageSize,
|
||||
itemGroups: itemGroups,
|
||||
brands: brands,
|
||||
itemAttributes: itemAttributes.isNotEmpty ? itemAttributes : null,
|
||||
searchKeyword: keyword,
|
||||
);
|
||||
|
||||
// If we got less than pageSize, there are no more products
|
||||
_hasMore = newProducts.length >= pageSize;
|
||||
|
||||
@@ -236,7 +251,7 @@ Future<List<Product>> allProducts(Ref ref) async {
|
||||
///
|
||||
/// productAsync.when(
|
||||
/// data: (product) => ProductDetailView(product: product),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -228,7 +228,7 @@ final class ProductsProvider
|
||||
Products create() => Products();
|
||||
}
|
||||
|
||||
String _$productsHash() => r'6c55b22e75b912281feff3a68f84e488ccb7ab79';
|
||||
String _$productsHash() => r'a4f416712cdbf2e633622c65b1fdc95686e31fa4';
|
||||
|
||||
/// Products Provider
|
||||
///
|
||||
|
||||
@@ -5,6 +5,7 @@ library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/features/products/presentation/providers/product_filter_options_provider.dart';
|
||||
import 'package:worker/features/products/presentation/providers/product_filters_provider.dart';
|
||||
@@ -105,10 +106,10 @@ class BrandFilterChips extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const SizedBox(
|
||||
loading: () => SizedBox(
|
||||
height: 48.0,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2.0),
|
||||
child: CustomLoadingIndicator(color: Theme.of(context).colorScheme.primary, size: 40),
|
||||
),
|
||||
),
|
||||
error: (error, stack) => const SizedBox.shrink(),
|
||||
|
||||
@@ -6,6 +6,7 @@ library;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
@@ -469,9 +470,7 @@ class _ReviewsTab extends ConsumerWidget {
|
||||
loading: () => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40),
|
||||
child: CircularProgressIndicator(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
),
|
||||
error: (error, stack) => _buildErrorState(colorScheme, error.toString()),
|
||||
|
||||
@@ -6,6 +6,7 @@ library;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/products/presentation/providers/product_filters_provider.dart';
|
||||
@@ -209,11 +210,7 @@ class ProductFilterDrawer extends ConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
loading: () => Center(
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(colorScheme.primary),
|
||||
),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
import 'package:worker/features/products/presentation/widgets/product_card.dart';
|
||||
@@ -80,9 +81,7 @@ class _ProductGridState extends State<ProductGrid> {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
child: CircularProgressIndicator(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -168,7 +169,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const CustomLoadingIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Đang tải thông tin dự án...',
|
||||
@@ -638,10 +639,10 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text('Đang tải...', style: TextStyle(color: colorScheme.onSurfaceVariant)),
|
||||
@@ -787,10 +788,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
child: CustomLoadingIndicator(color: Colors.white, size: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -897,11 +895,11 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
|
||||
width: 48,
|
||||
height: 48,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -931,10 +929,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
child: CustomLoadingIndicator(color: Colors.white, size: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1150,10 +1145,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
child: CustomLoadingIndicator(color: Colors.white, size: 20),
|
||||
)
|
||||
: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -179,9 +180,7 @@ class SubmissionsPage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await ref.read(allSubmissionsProvider.notifier).refresh();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
@@ -69,7 +70,7 @@ class _PromotionDetailPageState extends ConsumerState<PromotionDetailPage> {
|
||||
foregroundColor: AppColors.grey900,
|
||||
centerTitle: false,
|
||||
),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
body: const CustomLoadingIndicator(),
|
||||
),
|
||||
error: (error, stack) => Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -201,7 +202,7 @@ class _PromotionDetailPageState extends ConsumerState<PromotionDetailPage> {
|
||||
placeholder: (context, url) => Container(
|
||||
height: 200,
|
||||
color: AppColors.grey100,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
height: 200,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -43,7 +44,7 @@ class PromotionsPage extends ConsumerWidget {
|
||||
child: promotionsAsync.when(
|
||||
data: (promotions) =>
|
||||
_buildPromotionsContent(context, promotions),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -57,7 +58,7 @@ class PromotionCard extends StatelessWidget {
|
||||
placeholder: (context, url) => Container(
|
||||
height: 150,
|
||||
color: AppColors.grey100,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
height: 150,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
@@ -450,7 +451,7 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
/// Build loading state
|
||||
Widget _buildLoadingState() {
|
||||
return const SliverFillRemaining(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -810,10 +811,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: colorScheme.surface,
|
||||
),
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -491,9 +492,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40),
|
||||
@@ -559,8 +558,8 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: Center(
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
@@ -85,9 +86,7 @@ class ModelHouseDetailPage extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40),
|
||||
@@ -409,8 +408,8 @@ class ModelHouseDetailPage extends ConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: Center(
|
||||
child: CustomLoadingIndicator(color: colorScheme.primary, size: 20),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
@@ -522,7 +521,7 @@ class _ImageViewerDialogState extends State<_ImageViewerDialog> {
|
||||
imageUrl: widget.images[index].fileUrl,
|
||||
fit: BoxFit.contain,
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
child: CustomLoadingIndicator(color: Colors.white, size: 40),
|
||||
),
|
||||
errorWidget: (context, url, error) => const Icon(
|
||||
Icons.error,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -214,9 +215,7 @@ class _LibraryTab extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40),
|
||||
@@ -288,7 +287,7 @@ class _LibraryCard extends StatelessWidget {
|
||||
placeholder: (context, url) => Container(
|
||||
height: 200,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
child: const CustomLoadingIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
height: 200,
|
||||
@@ -425,9 +424,7 @@ class _DesignRequestsTab extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const CustomLoadingIndicator(),
|
||||
error: (error, stack) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
library;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:worker/core/widgets/loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:worker/features/showrooms/domain/entities/sample_project.dart';
|
||||
|
||||
@@ -70,7 +71,7 @@ class _ImageViewerDialogState extends State<ImageViewerDialog> {
|
||||
imageUrl: widget.images[index].fileUrl,
|
||||
fit: BoxFit.contain,
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
child: CustomLoadingIndicator(color: Colors.white, size: 40),
|
||||
),
|
||||
errorWidget: (context, url, error) => const Icon(
|
||||
Icons.error,
|
||||
|
||||
68
lib/firebase_options.dart
Normal file
68
lib/firebase_options.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyA60iGPuHOQMJUA0m5aSimzevPAiiaB4pE',
|
||||
appId: '1:147309310656:android:86613d8ffc85576fdc7325',
|
||||
messagingSenderId: '147309310656',
|
||||
projectId: 'dbiz-partner',
|
||||
storageBucket: 'dbiz-partner.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAMgNFpkK0ss_uzNl51OqGyQHd0vFc9SxQ',
|
||||
appId: '1:147309310656:ios:aa59724d2c6b4620dc7325',
|
||||
messagingSenderId: '147309310656',
|
||||
projectId: 'dbiz-partner',
|
||||
storageBucket: 'dbiz-partner.firebasestorage.app',
|
||||
iosBundleId: 'com.dbiz.partner',
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:worker/app.dart';
|
||||
import 'package:worker/core/database/app_settings_box.dart';
|
||||
import 'package:worker/core/database/hive_initializer.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'firebase_options.dart';
|
||||
|
||||
/// Main entry point of the Worker Mobile App
|
||||
///
|
||||
@@ -23,6 +25,10 @@ void main() async {
|
||||
// Ensure Flutter is initialized before async operations
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
|
||||
// Set preferred device orientations
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
|
||||
92
pubspec.lock
92
pubspec.lock
@@ -9,6 +9,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "91.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: "8a1f5f3020ef2a74fb93f7ab3ef127a8feea33a7a2276279113660784ee7516a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.64"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -441,6 +449,78 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+4"
|
||||
firebase_analytics:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_analytics
|
||||
sha256: bfb80d92eee10a6585ebd5a7e60de5caf0f2c06329e5676c0578130aea1bfe85
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.4"
|
||||
firebase_analytics_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_analytics_platform_interface
|
||||
sha256: "3b803077907def997044774f6c022d8e9204e9c0f5e205e3572d887c93dafd72"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.4"
|
||||
firebase_analytics_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_analytics_web
|
||||
sha256: "0dbd96dbe77b51185319000c0078477fdcffb4abb0018c362dd9afb9845c1e06"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: "22086f857d2340f5d973776cfd542d3fb30cf98e1c643c3aa4a7520bb12745bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.0.4"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: a59920cbf2eb7c83d34a5f354331210ffec116b216dc72d864d8b8eb983ca398
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.4"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: "1183e40e6fd2a279a628951cc3b639fcf5ffe7589902632db645011eb70ebefb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -876,6 +956,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
loading_animation_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: loading_animation_widget
|
||||
sha256: "9fe23381f3096e902f39e87e487648ff7f74925e86234353fa885bb9f6c98004"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -928,10 +1016,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760
|
||||
sha256: "023a71afb4d7cfb5529d0f2636aa8b43db66257905b9486d702085989769c5f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.3"
|
||||
version: "7.1.3"
|
||||
mockito:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user