Compare commits

..

20 Commits

Author SHA1 Message Date
6a29c75630 Merge remote-tracking branch 'origin/fusion-app' into feature/contacts
# Conflicts:
#	packages/sf_localizations/assets/l10n/en.json
#	packages/sf_localizations/assets/l10n/es.json
#	packages/sf_localizations/lib/src/generated/i18n.dart
2026-03-26 19:11:03 +01:00
1810dc6e2a fix 2026-03-25 18:53:21 +01:00
4d2d25f47b fix 2026-03-25 16:39:47 +01:00
c79cbeffcc bump build to 6 2026-03-25 14:43:27 +01:00
b7614a39f1 feat: sync device settings after updates and improve remote connection features
- Add DeviceSettingsSync extension on Ref to centralize device provider
    updates after settings changes (sound, volume, language, timezone,
    battery, disable functions, alerts, pedometer, heart rate freq,
    location freq, background image)
  - Add photo capture countdown with Lottie animation in remote camera
  - Replace Image.network with Image.memory for photo display
  - Fix commands datasource to handle text/html responses (post<dynamic>)
  - Add typed error/success events to RemoteConnectionViewModel
  - Add background image active indicator and backgroundImageId to device settings
  - Change photos endpoint from /devices/identificator/:id/photos/files to /photos/files
  - Remove dead deviceId param from getBackgroundImage chain
  - Relax PictureEntity required fields to @Default for API compatibility
  - Fix LocationViewModel rebuild crash (ref.watch → ref.read)
  - Use .select() in RemoteCameraScreen for optimized rebuilds
  - Increase health measure countdown to 60s
2026-03-25 13:51:48 +01:00
429b67a536 Merge branch 'refs/heads/fusion-app' into feature/contacts
# Conflicts:
#	modules/legacy/packages/legacy_shared/lib/legacy_shared.dart
#	packages/sf_localizations/assets/l10n/en.json
#	packages/sf_localizations/assets/l10n/es.json
#	packages/sf_localizations/lib/src/generated/i18n.dart
2026-03-25 09:22:11 +01:00
a05c167f30 fix 2026-03-25 06:11:48 +01:00
c1c903ac93 feat: add country code picker to call watch dialog 2026-03-25 06:06:33 +01:00
a0a782c91b clean 2026-03-25 05:46:49 +01:00
c140daa7ae refactor: extract timezone data, remove duplicate i18n keys 2026-03-25 05:45:05 +01:00
6d30a59651 feat: implement alerts, disable functions, and battery night mode settings
- Rename sms_alert feature to alerts with toggle list from device capabilities
  - Implement disable functions (keyboard, GPS) with device settings update
  - Implement battery night mode with dedicated view model
  - Add keyboard, gps, nightMode fields to DeviceSettingsEntity/Model
  - Fix photos endpoint to use /photos/files for file content
2026-03-25 05:03:40 +01:00
8d453dc980 feature/background-image into fusion-app with fixes
- Fix upload flow: capture photo ID from POST /photos response
  - Fix endpoint: use /devices/identificator/{id}/photos/files for listing
  - Fix setBackgroundImage: use device.id (UUID) instead of identificator
  - Redesign screen as photo gallery with grid view
  - Add image compression on pick (maxWidth: 800, quality: 80%)
  - Fix multipart upload: remove content-type header for FormData auto-detection
  - Replace hardcoded Spanish text with i18n in 6 languages
  - Add typed error/success enums (BackgroundImageErrorEvent, BackgroundImageSuccessEvent)
  - Revert initialLocation to splash
  - Add missing translations for contacts and background-image features
2026-03-25 03:52:24 +01:00
0a50de3d70 Merge remote-tracking branch 'origin/feature/contacts' into fusion-app 2026-03-25 02:30:09 +01:00
02053182db Merge remote-tracking branch 'origin/feature/linked-devices' into fusion-app 2026-03-25 02:22:48 +01:00
33f3dfa252 feat: type device settings and capabilities, centralize device updates, and improve error handling
- Type device.settings and device.capabilities from untyped maps to Freezed models
  - Centralize all device settings updates through shared DeviceSettingsUpdateDatasource
  - Add frequency selectors for location and heart rate, pedometer toggle, and health measurement countdown with Lottie animation
  - Replace raw backend error messages with typed i18n error events across location, health, and activity meter
  - Fix silent error swallowing in commands datasource and stuck dialog in locate device
2026-03-25 02:15:25 +01:00
cb70973d3b feat: change background image 2026-03-24 17:31:49 +01:00
75df4736e2 change block phones behaviour 2026-03-24 13:43:26 +01:00
1ffeea8b77 background image screen and state 2026-03-24 11:25:45 +01:00
5f484036f8 feat: contacts upper and lower limits 2026-03-23 15:38:47 +01:00
73927557ca fix: show activation code dialog with qr scan 2026-03-23 13:58:59 +01:00
196 changed files with 13949 additions and 3167 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,8 @@ PODS:
- Flutter
- flutter_treezor_entrust_sdk_bridge (0.0.1):
- Flutter
- image_picker_ios (0.0.1):
- Flutter
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
@@ -14,6 +16,8 @@ PODS:
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -28,9 +32,11 @@ DEPENDENCIES:
- flutter_contacts (from `.symlinks/plugins/flutter_contacts/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_treezor_entrust_sdk_bridge (from `.symlinks/plugins/flutter_treezor_entrust_sdk_bridge/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
@@ -44,12 +50,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_treezor_entrust_sdk_bridge:
:path: ".symlinks/plugins/flutter_treezor_entrust_sdk_bridge/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/darwin"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
@@ -62,9 +72,11 @@ SPEC CHECKSUMS:
flutter_contacts: edb1c5ce76aa433e20e6cb14c615f4c0b66e0983
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_treezor_entrust_sdk_bridge: 4c2c94fb74ab57576e8d49f5f2a4b214e41141fe
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4

View File

@@ -30,7 +30,9 @@ Future<void> initApp(EnvironmentEnum env) async {
await configureDependencies(
QuestiaEnvConfig(),
log: env.isDevelopment || kDebugMode,
onTokenExpired: () => appRouter.go(AppRoutes.scaTreezor),
onTokenExpired: () => appRouter.go(
AppRoutes.legacyLogin,
), //change to payments app to AppRoutes.scaTreezor
onUnauthorized: () async {
final currentLocation =
appRouter.routerDelegate.currentConfiguration.uri.path;
@@ -39,7 +41,7 @@ Future<void> initApp(EnvironmentEnum env) async {
await GetIt.I<TreezorWalletConnectionService>().logout();
} catch (_) {}
await clearSessionData();
appRouter.go(AppRoutes.login);
appRouter.go(AppRoutes.legacyLogin);
},
);

View File

@@ -21,25 +21,6 @@ import 'package:splash/splash.dart';
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
final _legacyControlPanelNavKey =
GlobalKey<NavigatorState>(debugLabel: 'legacyControlPanel');
final _legacyDeviceMgmtNavKey =
GlobalKey<NavigatorState>(debugLabel: 'legacyDeviceMgmt');
final _legacyLocationNavKey =
GlobalKey<NavigatorState>(debugLabel: 'legacyLocation');
final _legacyChatNavKey = GlobalKey<NavigatorState>(debugLabel: 'legacyChat');
final _legacySettingsNavKey =
GlobalKey<NavigatorState>(debugLabel: 'legacySettings');
final _dashboardHomeNavKey =
GlobalKey<NavigatorState>(debugLabel: 'dashboardHome');
final _dashboardActivityNavKey =
GlobalKey<NavigatorState>(debugLabel: 'dashboardActivity');
final _dashboardNotificationsNavKey =
GlobalKey<NavigatorState>(debugLabel: 'dashboardNotifications');
final _dashboardProfileNavKey =
GlobalKey<NavigatorState>(debugLabel: 'dashboardProfile');
late final GoRouter appRouter;
void configureAppRouter() {
@@ -59,7 +40,6 @@ void configureAppRouter() {
},
branches: [
StatefulShellBranch(
navigatorKey: _legacyControlPanelNavKey,
routes: [
GoRoute(
path: AppRoutes.controlPanel,
@@ -108,7 +88,6 @@ void configureAppRouter() {
],
),
StatefulShellBranch(
navigatorKey: _legacyDeviceMgmtNavKey,
routes: [
GoRoute(
path: AppRoutes.deviceManagement,
@@ -172,12 +151,16 @@ void configureAppRouter() {
name: 'call_history',
pageBuilder: const CallHistoryBuilder().buildPage,
),
GoRoute(
path: 'background_image',
name: 'background_image',
pageBuilder: const BackgroundImageBuilder().buildPage,
),
],
),
],
),
StatefulShellBranch(
navigatorKey: _legacyLocationNavKey,
routes: [
GoRoute(
path: AppRoutes.legacyLocation,
@@ -188,7 +171,6 @@ void configureAppRouter() {
),
// TODO: Añadir branch para Chat (tab 4)
StatefulShellBranch(
navigatorKey: _legacyChatNavKey,
routes: [
GoRoute(
path: '${AppRoutes.legacyDashboard}/chat',
@@ -203,7 +185,6 @@ void configureAppRouter() {
],
),
StatefulShellBranch(
navigatorKey: _legacySettingsNavKey,
routes: [
GoRoute(
path: AppRoutes.settings,
@@ -271,9 +252,9 @@ void configureAppRouter() {
pageBuilder: RemoteOnOffBuilder().buildPage,
),
GoRoute(
path: 'sms_alert',
name: 'sms_alert',
pageBuilder: SmsAlertBuilder().buildPage,
path: 'alerts',
name: 'alerts',
pageBuilder: AlertsBuilder().buildPage,
),
GoRoute(
path: 'timezone',
@@ -379,7 +360,6 @@ void configureAppRouter() {
},
branches: [
StatefulShellBranch(
navigatorKey: _dashboardHomeNavKey,
routes: [
GoRoute(
path: AppRoutes.dashboardHome,
@@ -427,24 +407,6 @@ void configureAppRouter() {
pageBuilder:
const EditChildProfileBuilder().buildPage,
),
GoRoute(
path: 'set-pin',
name: 'home_set_card_pin',
pageBuilder:
const SetCardPinBuilder().buildPage,
),
GoRoute(
path: 'change-pin',
name: 'home_change_card_pin',
pageBuilder:
const ChangeCardPinBuilder().buildPage,
),
GoRoute(
path: 'renew-card',
name: 'home_renew_card',
pageBuilder:
const RenewCardBuilder().buildPage,
),
],
),
],
@@ -452,7 +414,6 @@ void configureAppRouter() {
],
),
StatefulShellBranch(
navigatorKey: _dashboardActivityNavKey,
routes: [
GoRoute(
path: AppRoutes.dashboardActivity,
@@ -462,7 +423,6 @@ void configureAppRouter() {
],
),
StatefulShellBranch(
navigatorKey: _dashboardNotificationsNavKey,
routes: [
GoRoute(
path: AppRoutes.dashboardNotifications,
@@ -472,7 +432,6 @@ void configureAppRouter() {
],
),
StatefulShellBranch(
navigatorKey: _dashboardProfileNavKey,
routes: [
GoRoute(
path: AppRoutes.dashboardProfile,

View File

@@ -34,13 +34,15 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
walletHeartbeat = WalletHeartbeatService(
repository: ref.read(treezorRepositoryProvider),
sessionLocal: SessionLocalDatasourceImpl(),
onError: () => appRouter.go(AppRoutes.scaTreezor),
onError: () => appRouter.go(
AppRoutes.legacyLogin,
), //change to payments app to AppRoutes.scaTreezor
);
legacyHeartbeat = LegacyHeartbeatService(
repository: GetIt.I<QuestiaRepository>(),
onUnauthorized: () {
clearSessionData();
appRouter.go(AppRoutes.login);
appRouter.go(AppRoutes.legacyLogin);
},
);
onBeforeSessionCleared = () {

View File

@@ -0,0 +1 @@
/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.4/

View File

@@ -0,0 +1 @@
/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.2/

View File

@@ -6,9 +6,13 @@
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
url_launcher_linux
)

View File

@@ -393,6 +393,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
url: "https://pub.dev"
source: hosted
version: "0.9.5"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
fixnum:
dependency: transitive
description:
@@ -459,6 +491,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.7"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
url: "https://pub.dev"
source: hosted
version: "2.0.33"
flutter_riverpod:
dependency: "direct main"
description:
@@ -610,6 +650,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.8.0"
image_picker:
dependency: transitive
description:
name: image_picker
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156
url: "https://pub.dev"
source: hosted
version: "0.8.13+14"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad"
url: "https://pub.dev"
source: hosted
version: "0.8.13+3"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
url: "https://pub.dev"
source: hosted
version: "0.2.2+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
url: "https://pub.dev"
source: hosted
version: "2.11.1"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
url: "https://pub.dev"
source: hosted
version: "0.2.2"
intl:
dependency: transitive
description:

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+5
version: 1.0.0+6
environment:
sdk: ^3.9.2
@@ -123,6 +123,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/shared/images/
- assets/shared/animations/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images

View File

@@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_treezor_entrust_sdk_bridge","path":"/Users/juliandalcalaf/Desktop/save-family-app/sf-app-platform/packages/flutter_treezor_entrust_sdk_bridge/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.5.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_treezor_entrust_sdk_bridge","path":"/Users/juliandalcalaf/Desktop/save-family-app/sf-app-platform/packages/flutter_treezor_entrust_sdk_bridge/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.22/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.5.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"shared_preferences_web","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_treezor_entrust_sdk_bridge","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2026-03-18 14:45:38.408085","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_treezor_entrust_sdk_bridge","path":"/Users/juliandalcalaf/Desktop/save-family-app/sf-app-platform/packages/flutter_treezor_entrust_sdk_bridge/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.5.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_treezor_entrust_sdk_bridge","path":"/Users/juliandalcalaf/Desktop/save-family-app/sf-app-platform/packages/flutter_treezor_entrust_sdk_bridge/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.22/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.5.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"shared_preferences_web","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_treezor_entrust_sdk_bridge","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2026-03-25 02:38:09.414683","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}}

View File

@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@@ -32,7 +32,7 @@ class OnboardingScreen extends ConsumerWidget {
void goToNext() {
if (isLast) {
navigationContract.goTo(AppRoutes.login);
navigationContract.goTo(AppRoutes.legacyLogin);
} else {
pageController.nextPage(
duration: const Duration(milliseconds: 400),
@@ -111,7 +111,7 @@ class OnboardingScreen extends ConsumerWidget {
? const SizedBox.shrink()
: TextButton(
onPressed: () =>
navigationContract.goTo(AppRoutes.login),
navigationContract.goTo(AppRoutes.legacyLogin),
child: Text(
context.translate(I18n.skip),
style: AppFonts.stolzlStyle(

View File

@@ -15,7 +15,6 @@ class ScaPinView extends StatelessWidget {
final VoidCallback onSubmit;
final String clearPinText;
final String? errorMessage;
final int pinLength;
const ScaPinView({
super.key,
@@ -31,7 +30,6 @@ class ScaPinView extends StatelessWidget {
required this.onSubmit,
required this.clearPinText,
this.errorMessage,
this.pinLength = 6,
});
@override
@@ -51,7 +49,7 @@ class ScaPinView extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(width: 44),
_PinDots(length: pin.length, max: pinLength),
_PinDots(length: pin.length, max: 6),
const SizedBox(width: 12),
SizedBox(
width: 44,

View File

@@ -495,6 +495,7 @@ class SignUpViewModel extends Notifier<SignUpViewState> {
String _emailErrorFor(String value) {
final email = value.trim();
if (email.isEmpty) return I18n.errorEmailRequired;
if (email.contains('+')) return I18n.errorEmailPlusNotAllowed;
if (!_isValidEmail(email)) return I18n.errorEmailInvalid;
return '';
}

View File

@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@@ -8,6 +8,3 @@ export 'src/features/limits/limits_builder.dart';
export 'src/features/goals/goals_builder.dart';
export 'src/features/extract/extract_builder.dart';
export 'src/features/edit_child_profile/edit_child_profile_builder.dart';
export 'src/features/card_pin/card_pin_builder.dart';
export 'src/features/card_pin/card_pin_view_state.dart';
export 'src/features/renew_card/renew_card_builder.dart';

View File

@@ -1,41 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:navigation/navigation.dart';
import 'card_pin_screen.dart';
import 'card_pin_view_state.dart';
class SetCardPinBuilder {
const SetCardPinBuilder();
Page<void> buildPage(BuildContext context, GoRouterState state) {
final childWalletId = state.pathParameters['childWalletId'] ?? '';
final navigationContract = GetIt.I<NavigationContract>();
return MaterialPage(
key: state.pageKey,
child: CardPinScreen(
childId: childWalletId,
navigation: navigationContract,
mode: CardPinMode.set,
),
);
}
}
class ChangeCardPinBuilder {
const ChangeCardPinBuilder();
Page<void> buildPage(BuildContext context, GoRouterState state) {
final childWalletId = state.pathParameters['childWalletId'] ?? '';
final navigationContract = GetIt.I<NavigationContract>();
return MaterialPage(
key: state.pageKey,
child: CardPinScreen(
childId: childWalletId,
navigation: navigationContract,
mode: CardPinMode.change,
),
);
}
}

View File

@@ -1,136 +0,0 @@
import 'package:auth/auth.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'card_pin_view_model.dart';
import 'card_pin_view_state.dart';
class CardPinScreen extends ConsumerWidget {
final String childId;
final NavigationContract navigation;
final CardPinMode mode;
const CardPinScreen({
super.key,
required this.childId,
required this.navigation,
required this.mode,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final arg = (childId, mode);
final viewState = ref.watch(cardPinViewModelProvider(arg));
final viewModel = ref.read(cardPinViewModelProvider(arg).notifier);
ref.listen(cardPinViewModelProvider(arg), (prev, next) {
if (next.success && !(prev?.success ?? false)) {
showTopSnackbar(
context,
message: context.translate(I18n.cardPinSuccess),
type: MessageType.success,
);
navigation.goBack();
return;
}
if (next.errorMessage.isNotEmpty &&
next.errorMessage != (prev?.errorMessage ?? '')) {
if (next.errorMessage == 'cardPinMismatch') {
showTopSnackbar(
context,
message: context.translate(I18n.cardPinMismatch),
type: MessageType.error,
);
} else {
showTopSnackbar(
context,
message: context.translate(I18n.cardPinError),
type: MessageType.error,
);
}
}
});
if (viewState.isLoading) {
return const Scaffold(body: Center(child: AppLoadingIndicator()));
}
if (viewState.errorMessage.isNotEmpty && viewState.childProfile == null) {
return Scaffold(
body: Center(child: Text('Error: ${viewState.errorMessage}')),
);
}
final pinLength = viewState.step == CardPinStep.scaPin ? 6 : 4;
final stepTitle = _stepTitle(context, viewState.step);
final currentPin = switch (viewState.step) {
CardPinStep.currentPin => viewState.currentPin,
CardPinStep.newPin => viewState.newPin,
CardPinStep.confirmPin => viewState.confirmPin,
CardPinStep.scaPin => viewState.scaPin,
};
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
appBar: AppBar(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: theme.getColorFor(ThemeCode.textPrimary),
),
onPressed: () {
if (viewModel.isFirstStep) {
navigation.goBack();
} else {
viewModel.goBack();
}
},
),
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: ScaPinView(
title: stepTitle,
pin: currentPin,
pinLength: pinLength,
isProcessing: viewState.isSigning || viewState.isSubmitting,
processingText: context.translate(I18n.scaSigning),
canSubmit: viewModel.canSubmitStep,
submitText: viewState.step == CardPinStep.scaPin
? context.translate(I18n.scaConnect)
: context.translate(I18n.cardPinNext),
clearPinText: context.translate(I18n.scaClearPin),
onDigitPressed: viewModel.onDigitPressed,
onBackspacePressed: viewModel.onBackspacePressed,
onClearPin: viewModel.onClearPin,
onSubmit: viewModel.onStepSubmit,
),
),
),
SafeArea(
child: TextButton(
onPressed: () => navigation.goBack(),
child: Text(context.translate(I18n.cancel)),
),
),
],
),
);
}
String _stepTitle(BuildContext context, CardPinStep step) {
return switch (step) {
CardPinStep.currentPin => context.translate(I18n.cardPinCurrentStep),
CardPinStep.newPin => context.translate(I18n.cardPinNewStep),
CardPinStep.confirmPin => context.translate(I18n.cardPinConfirmStep),
CardPinStep.scaPin => context.translate(I18n.cardPinScaStep),
};
}
}

View File

@@ -1,257 +0,0 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';
import 'package:sca_treezor/sca_treezor.dart';
import 'package:sf_shared/sf_shared.dart';
import '../child_wallet/child_data_provider.dart';
import 'card_pin_view_state.dart';
final cardPinViewModelProvider = NotifierProvider.autoDispose
.family<CardPinViewModel, CardPinViewState, (String, CardPinMode)>(
CardPinViewModel.new,
);
class CardPinViewModel extends Notifier<CardPinViewState> {
final (String, CardPinMode) arg;
CardPinViewModel(this.arg);
String get childId => arg.$1;
CardPinMode get mode => arg.$2;
late final TreezorWalletConnectionService _connectionService;
late final TreezorWalletSignatureService _signatureService;
@override
CardPinViewState build() {
debugPrint('[CardPIN] build() mode=$mode childId=$childId');
_connectionService = GetIt.I<TreezorWalletConnectionService>();
_signatureService = GetIt.I<TreezorWalletSignatureService>();
ref.listen(childDataProvider(childId), (prev, next) {
state = state.copyWith(
isLoading: next.isLoading,
childProfile: next.childProfile,
childWallet: next.childWallet,
device: next.device,
errorMessage: next.errorMessage,
);
if (next.childProfile != null &&
prev?.childProfile == null &&
state.cardId.isEmpty) {
_loadCard(next.childProfile!.walletId);
}
});
final data = ref.read(childDataProvider(childId));
final initialStep =
mode == CardPinMode.change ? CardPinStep.currentPin : CardPinStep.newPin;
final initialState = CardPinViewState(
isLoading: data.isLoading,
childProfile: data.childProfile,
childWallet: data.childWallet,
device: data.device,
errorMessage: data.errorMessage,
step: initialStep,
);
if (data.childProfile != null) {
Future.microtask(() => _loadCard(data.childProfile!.walletId));
}
return initialState;
}
Future<void> _loadCard(String walletId) async {
debugPrint('[CardPIN] _loadCard walletId=$walletId');
try {
final card =
await ref.read(treezorRepositoryProvider).getCard(walletId: walletId);
if (!ref.mounted) return;
debugPrint('[CardPIN] _loadCard cardId=${card.cardId} status=${card.status}');
state = state.copyWith(cardId: card.cardId.toString());
} catch (e) {
debugPrint('[CardPIN] _loadCard error: $e');
}
}
String _currentStepPin() => switch (state.step) {
CardPinStep.currentPin => state.currentPin,
CardPinStep.newPin => state.newPin,
CardPinStep.confirmPin => state.confirmPin,
CardPinStep.scaPin => state.scaPin,
};
int get _currentStepMaxLength =>
state.step == CardPinStep.scaPin ? 6 : 4;
void onDigitPressed(String digit) {
final current = _currentStepPin();
if (current.length >= _currentStepMaxLength) return;
final updated = current + digit;
state = _updateStepPin(updated).copyWith(errorMessage: '');
}
void onBackspacePressed() {
final current = _currentStepPin();
if (current.isEmpty) return;
state = _updateStepPin(current.substring(0, current.length - 1));
}
void onClearPin() {
state = _updateStepPin('');
}
CardPinViewState _updateStepPin(String value) => switch (state.step) {
CardPinStep.currentPin => state.copyWith(currentPin: value),
CardPinStep.newPin => state.copyWith(newPin: value),
CardPinStep.confirmPin => state.copyWith(confirmPin: value),
CardPinStep.scaPin => state.copyWith(scaPin: value),
};
bool get canSubmitStep => _currentStepPin().length == _currentStepMaxLength;
void onStepSubmit() {
debugPrint('[CardPIN] onStepSubmit step=${state.step}');
switch (state.step) {
case CardPinStep.currentPin:
debugPrint('[CardPIN] currentPin entered, advancing to newPin');
state = state.copyWith(step: CardPinStep.newPin, errorMessage: '');
case CardPinStep.newPin:
debugPrint('[CardPIN] newPin entered, advancing to confirmPin');
state = state.copyWith(step: CardPinStep.confirmPin, errorMessage: '');
case CardPinStep.confirmPin:
if (state.confirmPin != state.newPin) {
debugPrint('[CardPIN] confirmPin MISMATCH');
state = state.copyWith(
confirmPin: '',
errorMessage: 'cardPinMismatch',
);
return;
}
debugPrint('[CardPIN] confirmPin matches, advancing to scaPin');
state = state.copyWith(step: CardPinStep.scaPin, errorMessage: '');
case CardPinStep.scaPin:
debugPrint('[CardPIN] scaPin entered, submitting...');
_submit();
}
}
void goBack() {
switch (state.step) {
case CardPinStep.currentPin:
break;
case CardPinStep.newPin:
if (mode == CardPinMode.change) {
state = state.copyWith(step: CardPinStep.currentPin, newPin: '');
}
case CardPinStep.confirmPin:
state = state.copyWith(step: CardPinStep.newPin, confirmPin: '');
case CardPinStep.scaPin:
state = state.copyWith(step: CardPinStep.confirmPin, scaPin: '');
}
}
bool get isFirstStep =>
(mode == CardPinMode.set && state.step == CardPinStep.newPin) ||
(mode == CardPinMode.change && state.step == CardPinStep.currentPin);
Future<void> _submit() async {
final cardId = state.cardId;
final walletId = state.childProfile?.walletId;
final userId = state.childProfile?.treezorUserId;
debugPrint('[CardPIN] _submit mode=$mode cardId=$cardId walletId=$walletId userId=$userId');
if (cardId.isEmpty || walletId == null) {
debugPrint('[CardPIN] _submit aborted: cardId or walletId missing');
return;
}
state = state.copyWith(isSigning: true, errorMessage: '');
try {
debugPrint('[CardPIN] connecting with SCA PIN...');
await _connectionService.connectWithPin(loginPin: state.scaPin);
if (!ref.mounted) return;
debugPrint('[CardPIN] SCA connection successful');
final String scaProof;
if (mode == CardPinMode.set) {
final url =
'https://savefamily.preprod.secure.treezor.co/cards/$cardId/setPIN';
final scaInput = jsonEncode({
'url': url,
'body': {
'newPIN': state.newPin,
'confirmPIN': state.confirmPin,
'userId': userId,
},
});
debugPrint('[CardPIN] setPIN scaInput: $scaInput');
scaProof = await _signatureService.generateJwsWithPin(
message: '',
input: scaInput,
pin: state.scaPin,
);
debugPrint('[CardPIN] setPIN scaProof: $scaProof');
} else {
final url =
'https://savefamily.preprod.secure.treezor.co/cards/$cardId/ChangePIN';
final scaInput = jsonEncode({
'url': url,
'body': {
'currentPIN': state.currentPin,
'newPIN': state.newPin,
'confirmPIN': state.confirmPin,
'userId': userId,
},
});
debugPrint('[CardPIN] changePIN scaInput: $scaInput');
scaProof = await _signatureService.generateJwsWithPin(
message: '',
input: scaInput,
pin: state.scaPin,
);
debugPrint('[CardPIN] changePIN scaProof: $scaProof');
}
if (!ref.mounted) return;
state = state.copyWith(isSigning: false, isSubmitting: true, scaPin: '');
if (mode == CardPinMode.set) {
debugPrint('[CardPIN] calling backend setCardPin walletId=$walletId');
await ref.read(treezorRepositoryProvider).setCardPin(
walletId: walletId,
newPin: state.newPin,
confirmPin: state.confirmPin,
scaProof: scaProof,
);
debugPrint('[CardPIN] setCardPin SUCCESS');
} else {
debugPrint('[CardPIN] calling backend changeCardPin walletId=$walletId');
await ref.read(treezorRepositoryProvider).changeCardPin(
walletId: walletId,
currentPin: state.currentPin,
newPin: state.newPin,
confirmPin: state.confirmPin,
scaProof: scaProof,
);
debugPrint('[CardPIN] changeCardPin SUCCESS');
}
if (!ref.mounted) return;
state = state.copyWith(isSubmitting: false, success: true);
} catch (e) {
debugPrint('[CardPIN] _submit ERROR: $e');
if (!ref.mounted) return;
state = state.copyWith(
isSigning: false,
isSubmitting: false,
scaPin: '',
errorMessage: e.toString(),
);
}
}
}

View File

@@ -1,28 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:sf_shared/sf_shared.dart';
part 'card_pin_view_state.freezed.dart';
enum CardPinMode { set, change }
enum CardPinStep { currentPin, newPin, confirmPin, scaPin }
@freezed
abstract class CardPinViewState with _$CardPinViewState {
const factory CardPinViewState({
@Default(true) bool isLoading,
ChildProfileEntity? childProfile,
ChildWalletEntity? childWallet,
DeviceEntity? device,
@Default('') String cardId,
@Default('') String currentPin,
@Default('') String newPin,
@Default('') String confirmPin,
@Default('') String scaPin,
@Default(CardPinStep.newPin) CardPinStep step,
@Default(false) bool isSigning,
@Default(false) bool isSubmitting,
@Default(false) bool success,
@Default('') String errorMessage,
}) = _CardPinViewState;
}

View File

@@ -1,382 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'card_pin_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$CardPinViewState {
bool get isLoading; ChildProfileEntity? get childProfile; ChildWalletEntity? get childWallet; DeviceEntity? get device; String get cardId; String get currentPin; String get newPin; String get confirmPin; String get scaPin; CardPinStep get step; bool get isSigning; bool get isSubmitting; bool get success; String get errorMessage;
/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$CardPinViewStateCopyWith<CardPinViewState> get copyWith => _$CardPinViewStateCopyWithImpl<CardPinViewState>(this as CardPinViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CardPinViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardId, cardId) || other.cardId == cardId)&&(identical(other.currentPin, currentPin) || other.currentPin == currentPin)&&(identical(other.newPin, newPin) || other.newPin == newPin)&&(identical(other.confirmPin, confirmPin) || other.confirmPin == confirmPin)&&(identical(other.scaPin, scaPin) || other.scaPin == scaPin)&&(identical(other.step, step) || other.step == step)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.success, success) || other.success == success)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,childProfile,childWallet,device,cardId,currentPin,newPin,confirmPin,scaPin,step,isSigning,isSubmitting,success,errorMessage);
@override
String toString() {
return 'CardPinViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardId: $cardId, currentPin: $currentPin, newPin: $newPin, confirmPin: $confirmPin, scaPin: $scaPin, step: $step, isSigning: $isSigning, isSubmitting: $isSubmitting, success: $success, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class $CardPinViewStateCopyWith<$Res> {
factory $CardPinViewStateCopyWith(CardPinViewState value, $Res Function(CardPinViewState) _then) = _$CardPinViewStateCopyWithImpl;
@useResult
$Res call({
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String currentPin, String newPin, String confirmPin, String scaPin, CardPinStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage
});
$ChildProfileEntityCopyWith<$Res>? get childProfile;$ChildWalletEntityCopyWith<$Res>? get childWallet;$DeviceEntityCopyWith<$Res>? get device;
}
/// @nodoc
class _$CardPinViewStateCopyWithImpl<$Res>
implements $CardPinViewStateCopyWith<$Res> {
_$CardPinViewStateCopyWithImpl(this._self, this._then);
final CardPinViewState _self;
final $Res Function(CardPinViewState) _then;
/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardId = null,Object? currentPin = null,Object? newPin = null,Object? confirmPin = null,Object? scaPin = null,Object? step = null,Object? isSigning = null,Object? isSubmitting = null,Object? success = null,Object? errorMessage = null,}) {
return _then(_self.copyWith(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,childProfile: freezed == childProfile ? _self.childProfile : childProfile // ignore: cast_nullable_to_non_nullable
as ChildProfileEntity?,childWallet: freezed == childWallet ? _self.childWallet : childWallet // ignore: cast_nullable_to_non_nullable
as ChildWalletEntity?,device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
as DeviceEntity?,cardId: null == cardId ? _self.cardId : cardId // ignore: cast_nullable_to_non_nullable
as String,currentPin: null == currentPin ? _self.currentPin : currentPin // ignore: cast_nullable_to_non_nullable
as String,newPin: null == newPin ? _self.newPin : newPin // ignore: cast_nullable_to_non_nullable
as String,confirmPin: null == confirmPin ? _self.confirmPin : confirmPin // ignore: cast_nullable_to_non_nullable
as String,scaPin: null == scaPin ? _self.scaPin : scaPin // ignore: cast_nullable_to_non_nullable
as String,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
as CardPinStep,isSigning: null == isSigning ? _self.isSigning : isSigning // ignore: cast_nullable_to_non_nullable
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,success: null == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildProfileEntityCopyWith<$Res>? get childProfile {
if (_self.childProfile == null) {
return null;
}
return $ChildProfileEntityCopyWith<$Res>(_self.childProfile!, (value) {
return _then(_self.copyWith(childProfile: value));
});
}/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildWalletEntityCopyWith<$Res>? get childWallet {
if (_self.childWallet == null) {
return null;
}
return $ChildWalletEntityCopyWith<$Res>(_self.childWallet!, (value) {
return _then(_self.copyWith(childWallet: value));
});
}/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceEntityCopyWith<$Res>? get device {
if (_self.device == null) {
return null;
}
return $DeviceEntityCopyWith<$Res>(_self.device!, (value) {
return _then(_self.copyWith(device: value));
});
}
}
/// Adds pattern-matching-related methods to [CardPinViewState].
extension CardPinViewStatePatterns on CardPinViewState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _CardPinViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _CardPinViewState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _CardPinViewState value) $default,){
final _that = this;
switch (_that) {
case _CardPinViewState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _CardPinViewState value)? $default,){
final _that = this;
switch (_that) {
case _CardPinViewState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String currentPin, String newPin, String confirmPin, String scaPin, CardPinStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CardPinViewState() when $default != null:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.currentPin,_that.newPin,_that.confirmPin,_that.scaPin,_that.step,_that.isSigning,_that.isSubmitting,_that.success,_that.errorMessage);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String currentPin, String newPin, String confirmPin, String scaPin, CardPinStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage) $default,) {final _that = this;
switch (_that) {
case _CardPinViewState():
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.currentPin,_that.newPin,_that.confirmPin,_that.scaPin,_that.step,_that.isSigning,_that.isSubmitting,_that.success,_that.errorMessage);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String currentPin, String newPin, String confirmPin, String scaPin, CardPinStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage)? $default,) {final _that = this;
switch (_that) {
case _CardPinViewState() when $default != null:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.currentPin,_that.newPin,_that.confirmPin,_that.scaPin,_that.step,_that.isSigning,_that.isSubmitting,_that.success,_that.errorMessage);case _:
return null;
}
}
}
/// @nodoc
class _CardPinViewState implements CardPinViewState {
const _CardPinViewState({this.isLoading = true, this.childProfile, this.childWallet, this.device, this.cardId = '', this.currentPin = '', this.newPin = '', this.confirmPin = '', this.scaPin = '', this.step = CardPinStep.newPin, this.isSigning = false, this.isSubmitting = false, this.success = false, this.errorMessage = ''});
@override@JsonKey() final bool isLoading;
@override final ChildProfileEntity? childProfile;
@override final ChildWalletEntity? childWallet;
@override final DeviceEntity? device;
@override@JsonKey() final String cardId;
@override@JsonKey() final String currentPin;
@override@JsonKey() final String newPin;
@override@JsonKey() final String confirmPin;
@override@JsonKey() final String scaPin;
@override@JsonKey() final CardPinStep step;
@override@JsonKey() final bool isSigning;
@override@JsonKey() final bool isSubmitting;
@override@JsonKey() final bool success;
@override@JsonKey() final String errorMessage;
/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$CardPinViewStateCopyWith<_CardPinViewState> get copyWith => __$CardPinViewStateCopyWithImpl<_CardPinViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CardPinViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardId, cardId) || other.cardId == cardId)&&(identical(other.currentPin, currentPin) || other.currentPin == currentPin)&&(identical(other.newPin, newPin) || other.newPin == newPin)&&(identical(other.confirmPin, confirmPin) || other.confirmPin == confirmPin)&&(identical(other.scaPin, scaPin) || other.scaPin == scaPin)&&(identical(other.step, step) || other.step == step)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.success, success) || other.success == success)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,childProfile,childWallet,device,cardId,currentPin,newPin,confirmPin,scaPin,step,isSigning,isSubmitting,success,errorMessage);
@override
String toString() {
return 'CardPinViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardId: $cardId, currentPin: $currentPin, newPin: $newPin, confirmPin: $confirmPin, scaPin: $scaPin, step: $step, isSigning: $isSigning, isSubmitting: $isSubmitting, success: $success, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class _$CardPinViewStateCopyWith<$Res> implements $CardPinViewStateCopyWith<$Res> {
factory _$CardPinViewStateCopyWith(_CardPinViewState value, $Res Function(_CardPinViewState) _then) = __$CardPinViewStateCopyWithImpl;
@override @useResult
$Res call({
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String currentPin, String newPin, String confirmPin, String scaPin, CardPinStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage
});
@override $ChildProfileEntityCopyWith<$Res>? get childProfile;@override $ChildWalletEntityCopyWith<$Res>? get childWallet;@override $DeviceEntityCopyWith<$Res>? get device;
}
/// @nodoc
class __$CardPinViewStateCopyWithImpl<$Res>
implements _$CardPinViewStateCopyWith<$Res> {
__$CardPinViewStateCopyWithImpl(this._self, this._then);
final _CardPinViewState _self;
final $Res Function(_CardPinViewState) _then;
/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardId = null,Object? currentPin = null,Object? newPin = null,Object? confirmPin = null,Object? scaPin = null,Object? step = null,Object? isSigning = null,Object? isSubmitting = null,Object? success = null,Object? errorMessage = null,}) {
return _then(_CardPinViewState(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,childProfile: freezed == childProfile ? _self.childProfile : childProfile // ignore: cast_nullable_to_non_nullable
as ChildProfileEntity?,childWallet: freezed == childWallet ? _self.childWallet : childWallet // ignore: cast_nullable_to_non_nullable
as ChildWalletEntity?,device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
as DeviceEntity?,cardId: null == cardId ? _self.cardId : cardId // ignore: cast_nullable_to_non_nullable
as String,currentPin: null == currentPin ? _self.currentPin : currentPin // ignore: cast_nullable_to_non_nullable
as String,newPin: null == newPin ? _self.newPin : newPin // ignore: cast_nullable_to_non_nullable
as String,confirmPin: null == confirmPin ? _self.confirmPin : confirmPin // ignore: cast_nullable_to_non_nullable
as String,scaPin: null == scaPin ? _self.scaPin : scaPin // ignore: cast_nullable_to_non_nullable
as String,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
as CardPinStep,isSigning: null == isSigning ? _self.isSigning : isSigning // ignore: cast_nullable_to_non_nullable
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,success: null == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildProfileEntityCopyWith<$Res>? get childProfile {
if (_self.childProfile == null) {
return null;
}
return $ChildProfileEntityCopyWith<$Res>(_self.childProfile!, (value) {
return _then(_self.copyWith(childProfile: value));
});
}/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildWalletEntityCopyWith<$Res>? get childWallet {
if (_self.childWallet == null) {
return null;
}
return $ChildWalletEntityCopyWith<$Res>(_self.childWallet!, (value) {
return _then(_self.copyWith(childWallet: value));
});
}/// Create a copy of CardPinViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceEntityCopyWith<$Res>? get device {
if (_self.device == null) {
return null;
}
return $DeviceEntityCopyWith<$Res>(_self.device!, (value) {
return _then(_self.copyWith(device: value));
});
}
}
// dart format on

View File

@@ -175,16 +175,6 @@ class _ChildWalletScreenState extends ConsumerState<ChildWalletScreen> {
),
),
),
IconButton(
onPressed: () => widget.navigation.pushTo(
AppRoutes.renewCard(widget.childId),
),
icon: Icon(
Icons.credit_card_outlined,
color: theme.getColorFor(ThemeCode.backgroundPrimary),
size: 24,
),
),
PopupMenuButton<String>(
icon: Icon(
Icons.more_vert,
@@ -197,16 +187,6 @@ class _ChildWalletScreenState extends ConsumerState<ChildWalletScreen> {
);
} else if (value == 'delete') {
_showDeleteConfirmation();
} else if (value == 'set_pin') {
widget.navigation.pushTo(
AppRoutes.setCardPin(widget.childId),
);
} else if (value == 'change_pin') {
widget.navigation.pushTo(
AppRoutes.changeCardPin(widget.childId),
);
} else if (value == 'unblock_pin') {
_showUnblockPinConfirmation();
}
},
itemBuilder: (_) => [
@@ -222,45 +202,6 @@ class _ChildWalletScreenState extends ConsumerState<ChildWalletScreen> {
],
),
),
if (!viewState.isPinBlocked && !(viewState.childProfile?.hasCardPin ?? false))
PopupMenuItem(
value: 'set_pin',
child: Row(
spacing: 8,
children: [
Icon(Icons.pin_outlined),
Text(
context.translate(I18n.cardPinSet),
),
],
),
),
if (!viewState.isPinBlocked && (viewState.childProfile?.hasCardPin ?? false))
PopupMenuItem(
value: 'change_pin',
child: Row(
spacing: 8,
children: [
Icon(Icons.lock_reset_outlined),
Text(
context.translate(I18n.cardPinChange),
),
],
),
),
if (viewState.isPinBlocked)
PopupMenuItem(
value: 'unblock_pin',
child: Row(
spacing: 8,
children: [
Icon(Icons.lock_open_outlined),
Text(
context.translate(I18n.cardPinUnblock),
),
],
),
),
PopupMenuItem(
value: 'delete',
child: Row(
@@ -563,52 +504,6 @@ class _ChildWalletScreenState extends ConsumerState<ChildWalletScreen> {
showTopSnackbar(context, message: e.toString(), type: MessageType.error);
}
}
Future<void> _showUnblockPinConfirmation() async {
final theme = ref.read(themePortProvider);
final confirmTitle = context.translate(I18n.cardPinUnblock);
final confirmMessage = context.translate(I18n.cardPinUnblockConfirm);
final cancelText = context.translate(I18n.cancel);
final confirmText = context.translate(I18n.accept);
final successText = context.translate(I18n.cardPinUnblockSuccess);
final bgColor = theme.getColorFor(ThemeCode.backgroundPrimary);
final shape = RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
);
showDialog(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: bgColor,
shape: shape,
title: Text(confirmTitle),
content: Text(confirmMessage),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text(cancelText),
),
TextButton(
onPressed: () async {
Navigator.of(ctx).pop();
final viewModel = ref.read(
childWalletViewModelProvider(widget.childId).notifier,
);
final success = await viewModel.unblockCardPin();
if (success && mounted) {
showTopSnackbar(
context,
message: successText,
type: MessageType.success,
);
}
},
child: Text(confirmText),
),
],
),
);
}
}
class _CardStatusSheet extends ConsumerStatefulWidget {

View File

@@ -124,7 +124,6 @@ class ChildWalletViewModel extends Notifier<ChildWalletViewState> {
cardId: card.cardId.toString(),
cardStatus: card.status,
locked: CardStatus.fromString(card.status).isLocked,
isPinBlocked: card.isPinBlocked,
);
} catch (_) {}
}
@@ -245,29 +244,6 @@ class ChildWalletViewModel extends Notifier<ChildWalletViewState> {
}
}
Future<bool> unblockCardPin() async {
final walletId = state.childProfile?.walletId;
if (walletId == null) return false;
state = state.copyWith(isUpdatingCard: true, cardStatusError: '');
try {
await ref.read(treezorRepositoryProvider).unblockCardPin(
walletId: walletId,
);
if (!ref.mounted) return false;
state = state.copyWith(isUpdatingCard: false);
return true;
} catch (e) {
if (!ref.mounted) return false;
state = state.copyWith(
isUpdatingCard: false,
cardStatusError: e.toString(),
);
return false;
}
}
static int _cardStatusToInt(String status) {
return CardStatus.fromString(status).apiCode;
}

View File

@@ -13,7 +13,6 @@ abstract class ChildWalletViewState with _$ChildWalletViewState {
@Default('') String cardId,
@Default('') String cardStatus,
@Default(false) bool locked,
@Default(false) bool isPinBlocked,
@Default('') String errorMessage,
@Default(false) bool isUpdatingCard,
@Default('') String cardStatusError,

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ChildWalletViewState {
bool get isLoading; ChildProfileEntity? get childProfile; ChildWalletEntity? get childWallet; DeviceEntity? get device; String get cardId; String get cardStatus; bool get locked; bool get isPinBlocked; String get errorMessage; bool get isUpdatingCard; String get cardStatusError; bool get cardStatusSuccess; bool get showPin; String get selectedStatus; String get pin; bool get isSigning; bool get isLoadingTransactions; List<List<WalletTransactionEntity>> get transactionPages; String? get nextCursor; bool get isLoadingMore; int get currentPage;
bool get isLoading; ChildProfileEntity? get childProfile; ChildWalletEntity? get childWallet; DeviceEntity? get device; String get cardId; String get cardStatus; bool get locked; String get errorMessage; bool get isUpdatingCard; String get cardStatusError; bool get cardStatusSuccess; bool get showPin; String get selectedStatus; String get pin; bool get isSigning; bool get isLoadingTransactions; List<List<WalletTransactionEntity>> get transactionPages; String? get nextCursor; bool get isLoadingMore; int get currentPage;
/// Create a copy of ChildWalletViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $ChildWalletViewStateCopyWith<ChildWalletViewState> get copyWith => _$ChildWalle
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChildWalletViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardId, cardId) || other.cardId == cardId)&&(identical(other.cardStatus, cardStatus) || other.cardStatus == cardStatus)&&(identical(other.locked, locked) || other.locked == locked)&&(identical(other.isPinBlocked, isPinBlocked) || other.isPinBlocked == isPinBlocked)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isUpdatingCard, isUpdatingCard) || other.isUpdatingCard == isUpdatingCard)&&(identical(other.cardStatusError, cardStatusError) || other.cardStatusError == cardStatusError)&&(identical(other.cardStatusSuccess, cardStatusSuccess) || other.cardStatusSuccess == cardStatusSuccess)&&(identical(other.showPin, showPin) || other.showPin == showPin)&&(identical(other.selectedStatus, selectedStatus) || other.selectedStatus == selectedStatus)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isLoadingTransactions, isLoadingTransactions) || other.isLoadingTransactions == isLoadingTransactions)&&const DeepCollectionEquality().equals(other.transactionPages, transactionPages)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChildWalletViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardId, cardId) || other.cardId == cardId)&&(identical(other.cardStatus, cardStatus) || other.cardStatus == cardStatus)&&(identical(other.locked, locked) || other.locked == locked)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isUpdatingCard, isUpdatingCard) || other.isUpdatingCard == isUpdatingCard)&&(identical(other.cardStatusError, cardStatusError) || other.cardStatusError == cardStatusError)&&(identical(other.cardStatusSuccess, cardStatusSuccess) || other.cardStatusSuccess == cardStatusSuccess)&&(identical(other.showPin, showPin) || other.showPin == showPin)&&(identical(other.selectedStatus, selectedStatus) || other.selectedStatus == selectedStatus)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isLoadingTransactions, isLoadingTransactions) || other.isLoadingTransactions == isLoadingTransactions)&&const DeepCollectionEquality().equals(other.transactionPages, transactionPages)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage));
}
@override
int get hashCode => Object.hashAll([runtimeType,isLoading,childProfile,childWallet,device,cardId,cardStatus,locked,isPinBlocked,errorMessage,isUpdatingCard,cardStatusError,cardStatusSuccess,showPin,selectedStatus,pin,isSigning,isLoadingTransactions,const DeepCollectionEquality().hash(transactionPages),nextCursor,isLoadingMore,currentPage]);
int get hashCode => Object.hashAll([runtimeType,isLoading,childProfile,childWallet,device,cardId,cardStatus,locked,errorMessage,isUpdatingCard,cardStatusError,cardStatusSuccess,showPin,selectedStatus,pin,isSigning,isLoadingTransactions,const DeepCollectionEquality().hash(transactionPages),nextCursor,isLoadingMore,currentPage]);
@override
String toString() {
return 'ChildWalletViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardId: $cardId, cardStatus: $cardStatus, locked: $locked, isPinBlocked: $isPinBlocked, errorMessage: $errorMessage, isUpdatingCard: $isUpdatingCard, cardStatusError: $cardStatusError, cardStatusSuccess: $cardStatusSuccess, showPin: $showPin, selectedStatus: $selectedStatus, pin: $pin, isSigning: $isSigning, isLoadingTransactions: $isLoadingTransactions, transactionPages: $transactionPages, nextCursor: $nextCursor, isLoadingMore: $isLoadingMore, currentPage: $currentPage)';
return 'ChildWalletViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardId: $cardId, cardStatus: $cardStatus, locked: $locked, errorMessage: $errorMessage, isUpdatingCard: $isUpdatingCard, cardStatusError: $cardStatusError, cardStatusSuccess: $cardStatusSuccess, showPin: $showPin, selectedStatus: $selectedStatus, pin: $pin, isSigning: $isSigning, isLoadingTransactions: $isLoadingTransactions, transactionPages: $transactionPages, nextCursor: $nextCursor, isLoadingMore: $isLoadingMore, currentPage: $currentPage)';
}
@@ -45,7 +45,7 @@ abstract mixin class $ChildWalletViewStateCopyWith<$Res> {
factory $ChildWalletViewStateCopyWith(ChildWalletViewState value, $Res Function(ChildWalletViewState) _then) = _$ChildWalletViewStateCopyWithImpl;
@useResult
$Res call({
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, bool isPinBlocked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage
});
@@ -62,7 +62,7 @@ class _$ChildWalletViewStateCopyWithImpl<$Res>
/// Create a copy of ChildWalletViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardId = null,Object? cardStatus = null,Object? locked = null,Object? isPinBlocked = null,Object? errorMessage = null,Object? isUpdatingCard = null,Object? cardStatusError = null,Object? cardStatusSuccess = null,Object? showPin = null,Object? selectedStatus = null,Object? pin = null,Object? isSigning = null,Object? isLoadingTransactions = null,Object? transactionPages = null,Object? nextCursor = freezed,Object? isLoadingMore = null,Object? currentPage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardId = null,Object? cardStatus = null,Object? locked = null,Object? errorMessage = null,Object? isUpdatingCard = null,Object? cardStatusError = null,Object? cardStatusSuccess = null,Object? showPin = null,Object? selectedStatus = null,Object? pin = null,Object? isSigning = null,Object? isLoadingTransactions = null,Object? transactionPages = null,Object? nextCursor = freezed,Object? isLoadingMore = null,Object? currentPage = null,}) {
return _then(_self.copyWith(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,childProfile: freezed == childProfile ? _self.childProfile : childProfile // ignore: cast_nullable_to_non_nullable
@@ -71,7 +71,6 @@ as ChildWalletEntity?,device: freezed == device ? _self.device : device // ignor
as DeviceEntity?,cardId: null == cardId ? _self.cardId : cardId // ignore: cast_nullable_to_non_nullable
as String,cardStatus: null == cardStatus ? _self.cardStatus : cardStatus // ignore: cast_nullable_to_non_nullable
as String,locked: null == locked ? _self.locked : locked // ignore: cast_nullable_to_non_nullable
as bool,isPinBlocked: null == isPinBlocked ? _self.isPinBlocked : isPinBlocked // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,isUpdatingCard: null == isUpdatingCard ? _self.isUpdatingCard : isUpdatingCard // ignore: cast_nullable_to_non_nullable
as bool,cardStatusError: null == cardStatusError ? _self.cardStatusError : cardStatusError // ignore: cast_nullable_to_non_nullable
@@ -206,10 +205,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, bool isPinBlocked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ChildWalletViewState() when $default != null:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.cardStatus,_that.locked,_that.isPinBlocked,_that.errorMessage,_that.isUpdatingCard,_that.cardStatusError,_that.cardStatusSuccess,_that.showPin,_that.selectedStatus,_that.pin,_that.isSigning,_that.isLoadingTransactions,_that.transactionPages,_that.nextCursor,_that.isLoadingMore,_that.currentPage);case _:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.cardStatus,_that.locked,_that.errorMessage,_that.isUpdatingCard,_that.cardStatusError,_that.cardStatusSuccess,_that.showPin,_that.selectedStatus,_that.pin,_that.isSigning,_that.isLoadingTransactions,_that.transactionPages,_that.nextCursor,_that.isLoadingMore,_that.currentPage);case _:
return orElse();
}
@@ -227,10 +226,10 @@ return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.devic
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, bool isPinBlocked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage) $default,) {final _that = this;
switch (_that) {
case _ChildWalletViewState():
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.cardStatus,_that.locked,_that.isPinBlocked,_that.errorMessage,_that.isUpdatingCard,_that.cardStatusError,_that.cardStatusSuccess,_that.showPin,_that.selectedStatus,_that.pin,_that.isSigning,_that.isLoadingTransactions,_that.transactionPages,_that.nextCursor,_that.isLoadingMore,_that.currentPage);case _:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.cardStatus,_that.locked,_that.errorMessage,_that.isUpdatingCard,_that.cardStatusError,_that.cardStatusSuccess,_that.showPin,_that.selectedStatus,_that.pin,_that.isSigning,_that.isLoadingTransactions,_that.transactionPages,_that.nextCursor,_that.isLoadingMore,_that.currentPage);case _:
throw StateError('Unexpected subclass');
}
@@ -247,10 +246,10 @@ return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.devic
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, bool isPinBlocked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage)? $default,) {final _that = this;
switch (_that) {
case _ChildWalletViewState() when $default != null:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.cardStatus,_that.locked,_that.isPinBlocked,_that.errorMessage,_that.isUpdatingCard,_that.cardStatusError,_that.cardStatusSuccess,_that.showPin,_that.selectedStatus,_that.pin,_that.isSigning,_that.isLoadingTransactions,_that.transactionPages,_that.nextCursor,_that.isLoadingMore,_that.currentPage);case _:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardId,_that.cardStatus,_that.locked,_that.errorMessage,_that.isUpdatingCard,_that.cardStatusError,_that.cardStatusSuccess,_that.showPin,_that.selectedStatus,_that.pin,_that.isSigning,_that.isLoadingTransactions,_that.transactionPages,_that.nextCursor,_that.isLoadingMore,_that.currentPage);case _:
return null;
}
@@ -262,7 +261,7 @@ return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.devic
class _ChildWalletViewState implements ChildWalletViewState {
const _ChildWalletViewState({this.isLoading = true, this.childProfile, this.childWallet, this.device, this.cardId = '', this.cardStatus = '', this.locked = false, this.isPinBlocked = false, this.errorMessage = '', this.isUpdatingCard = false, this.cardStatusError = '', this.cardStatusSuccess = false, this.showPin = false, this.selectedStatus = '', this.pin = '', this.isSigning = false, this.isLoadingTransactions = false, final List<List<WalletTransactionEntity>> transactionPages = const [], this.nextCursor, this.isLoadingMore = false, this.currentPage = 0}): _transactionPages = transactionPages;
const _ChildWalletViewState({this.isLoading = true, this.childProfile, this.childWallet, this.device, this.cardId = '', this.cardStatus = '', this.locked = false, this.errorMessage = '', this.isUpdatingCard = false, this.cardStatusError = '', this.cardStatusSuccess = false, this.showPin = false, this.selectedStatus = '', this.pin = '', this.isSigning = false, this.isLoadingTransactions = false, final List<List<WalletTransactionEntity>> transactionPages = const [], this.nextCursor, this.isLoadingMore = false, this.currentPage = 0}): _transactionPages = transactionPages;
@override@JsonKey() final bool isLoading;
@@ -272,7 +271,6 @@ class _ChildWalletViewState implements ChildWalletViewState {
@override@JsonKey() final String cardId;
@override@JsonKey() final String cardStatus;
@override@JsonKey() final bool locked;
@override@JsonKey() final bool isPinBlocked;
@override@JsonKey() final String errorMessage;
@override@JsonKey() final bool isUpdatingCard;
@override@JsonKey() final String cardStatusError;
@@ -303,16 +301,16 @@ _$ChildWalletViewStateCopyWith<_ChildWalletViewState> get copyWith => __$ChildWa
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChildWalletViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardId, cardId) || other.cardId == cardId)&&(identical(other.cardStatus, cardStatus) || other.cardStatus == cardStatus)&&(identical(other.locked, locked) || other.locked == locked)&&(identical(other.isPinBlocked, isPinBlocked) || other.isPinBlocked == isPinBlocked)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isUpdatingCard, isUpdatingCard) || other.isUpdatingCard == isUpdatingCard)&&(identical(other.cardStatusError, cardStatusError) || other.cardStatusError == cardStatusError)&&(identical(other.cardStatusSuccess, cardStatusSuccess) || other.cardStatusSuccess == cardStatusSuccess)&&(identical(other.showPin, showPin) || other.showPin == showPin)&&(identical(other.selectedStatus, selectedStatus) || other.selectedStatus == selectedStatus)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isLoadingTransactions, isLoadingTransactions) || other.isLoadingTransactions == isLoadingTransactions)&&const DeepCollectionEquality().equals(other._transactionPages, _transactionPages)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChildWalletViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardId, cardId) || other.cardId == cardId)&&(identical(other.cardStatus, cardStatus) || other.cardStatus == cardStatus)&&(identical(other.locked, locked) || other.locked == locked)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isUpdatingCard, isUpdatingCard) || other.isUpdatingCard == isUpdatingCard)&&(identical(other.cardStatusError, cardStatusError) || other.cardStatusError == cardStatusError)&&(identical(other.cardStatusSuccess, cardStatusSuccess) || other.cardStatusSuccess == cardStatusSuccess)&&(identical(other.showPin, showPin) || other.showPin == showPin)&&(identical(other.selectedStatus, selectedStatus) || other.selectedStatus == selectedStatus)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isLoadingTransactions, isLoadingTransactions) || other.isLoadingTransactions == isLoadingTransactions)&&const DeepCollectionEquality().equals(other._transactionPages, _transactionPages)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage));
}
@override
int get hashCode => Object.hashAll([runtimeType,isLoading,childProfile,childWallet,device,cardId,cardStatus,locked,isPinBlocked,errorMessage,isUpdatingCard,cardStatusError,cardStatusSuccess,showPin,selectedStatus,pin,isSigning,isLoadingTransactions,const DeepCollectionEquality().hash(_transactionPages),nextCursor,isLoadingMore,currentPage]);
int get hashCode => Object.hashAll([runtimeType,isLoading,childProfile,childWallet,device,cardId,cardStatus,locked,errorMessage,isUpdatingCard,cardStatusError,cardStatusSuccess,showPin,selectedStatus,pin,isSigning,isLoadingTransactions,const DeepCollectionEquality().hash(_transactionPages),nextCursor,isLoadingMore,currentPage]);
@override
String toString() {
return 'ChildWalletViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardId: $cardId, cardStatus: $cardStatus, locked: $locked, isPinBlocked: $isPinBlocked, errorMessage: $errorMessage, isUpdatingCard: $isUpdatingCard, cardStatusError: $cardStatusError, cardStatusSuccess: $cardStatusSuccess, showPin: $showPin, selectedStatus: $selectedStatus, pin: $pin, isSigning: $isSigning, isLoadingTransactions: $isLoadingTransactions, transactionPages: $transactionPages, nextCursor: $nextCursor, isLoadingMore: $isLoadingMore, currentPage: $currentPage)';
return 'ChildWalletViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardId: $cardId, cardStatus: $cardStatus, locked: $locked, errorMessage: $errorMessage, isUpdatingCard: $isUpdatingCard, cardStatusError: $cardStatusError, cardStatusSuccess: $cardStatusSuccess, showPin: $showPin, selectedStatus: $selectedStatus, pin: $pin, isSigning: $isSigning, isLoadingTransactions: $isLoadingTransactions, transactionPages: $transactionPages, nextCursor: $nextCursor, isLoadingMore: $isLoadingMore, currentPage: $currentPage)';
}
@@ -323,7 +321,7 @@ abstract mixin class _$ChildWalletViewStateCopyWith<$Res> implements $ChildWalle
factory _$ChildWalletViewStateCopyWith(_ChildWalletViewState value, $Res Function(_ChildWalletViewState) _then) = __$ChildWalletViewStateCopyWithImpl;
@override @useResult
$Res call({
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, bool isPinBlocked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardId, String cardStatus, bool locked, String errorMessage, bool isUpdatingCard, String cardStatusError, bool cardStatusSuccess, bool showPin, String selectedStatus, String pin, bool isSigning, bool isLoadingTransactions, List<List<WalletTransactionEntity>> transactionPages, String? nextCursor, bool isLoadingMore, int currentPage
});
@@ -340,7 +338,7 @@ class __$ChildWalletViewStateCopyWithImpl<$Res>
/// Create a copy of ChildWalletViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardId = null,Object? cardStatus = null,Object? locked = null,Object? isPinBlocked = null,Object? errorMessage = null,Object? isUpdatingCard = null,Object? cardStatusError = null,Object? cardStatusSuccess = null,Object? showPin = null,Object? selectedStatus = null,Object? pin = null,Object? isSigning = null,Object? isLoadingTransactions = null,Object? transactionPages = null,Object? nextCursor = freezed,Object? isLoadingMore = null,Object? currentPage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardId = null,Object? cardStatus = null,Object? locked = null,Object? errorMessage = null,Object? isUpdatingCard = null,Object? cardStatusError = null,Object? cardStatusSuccess = null,Object? showPin = null,Object? selectedStatus = null,Object? pin = null,Object? isSigning = null,Object? isLoadingTransactions = null,Object? transactionPages = null,Object? nextCursor = freezed,Object? isLoadingMore = null,Object? currentPage = null,}) {
return _then(_ChildWalletViewState(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,childProfile: freezed == childProfile ? _self.childProfile : childProfile // ignore: cast_nullable_to_non_nullable
@@ -349,7 +347,6 @@ as ChildWalletEntity?,device: freezed == device ? _self.device : device // ignor
as DeviceEntity?,cardId: null == cardId ? _self.cardId : cardId // ignore: cast_nullable_to_non_nullable
as String,cardStatus: null == cardStatus ? _self.cardStatus : cardStatus // ignore: cast_nullable_to_non_nullable
as String,locked: null == locked ? _self.locked : locked // ignore: cast_nullable_to_non_nullable
as bool,isPinBlocked: null == isPinBlocked ? _self.isPinBlocked : isPinBlocked // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,isUpdatingCard: null == isUpdatingCard ? _self.isUpdatingCard : isUpdatingCard // ignore: cast_nullable_to_non_nullable
as bool,cardStatusError: null == cardStatusError ? _self.cardStatusError : cardStatusError // ignore: cast_nullable_to_non_nullable

View File

@@ -1,22 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:navigation/navigation.dart';
import 'renew_card_screen.dart';
class RenewCardBuilder {
const RenewCardBuilder();
Page<void> buildPage(BuildContext context, GoRouterState state) {
final childWalletId = state.pathParameters['childWalletId'] ?? '';
final navigationContract = GetIt.I<NavigationContract>();
return MaterialPage(
key: state.pageKey,
child: RenewCardScreen(
childId: childWalletId,
navigation: navigationContract,
),
);
}
}

View File

@@ -1,162 +0,0 @@
import 'package:auth/auth.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'renew_card_view_model.dart';
import 'renew_card_view_state.dart';
class RenewCardScreen extends ConsumerWidget {
final String childId;
final NavigationContract navigation;
const RenewCardScreen({
super.key,
required this.childId,
required this.navigation,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final viewState = ref.watch(renewCardViewModelProvider(childId));
final viewModel = ref.read(renewCardViewModelProvider(childId).notifier);
ref.listen(renewCardViewModelProvider(childId), (prev, next) {
if (next.success && !(prev?.success ?? false)) {
showTopSnackbar(
context,
message: context.translate(I18n.renewCardSuccess),
type: MessageType.success,
);
navigation.goBack();
return;
}
if (next.errorMessage.isNotEmpty &&
next.errorMessage != (prev?.errorMessage ?? '')) {
showTopSnackbar(
context,
message: context.translate(I18n.renewCardError),
type: MessageType.error,
);
}
});
if (viewState.isLoading) {
return const Scaffold(body: Center(child: AppLoadingIndicator()));
}
if (viewState.errorMessage.isNotEmpty && viewState.childProfile == null) {
return Scaffold(
body: Center(child: Text('Error: ${viewState.errorMessage}')),
);
}
if (viewState.step == RenewCardStep.scaPin) {
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
appBar: AppBar(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: theme.getColorFor(ThemeCode.textPrimary),
),
onPressed: viewModel.goBackToToken,
),
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: ScaPinView(
title: context.translate(I18n.renewCardPinTitle),
pin: viewState.pin,
isProcessing:
viewState.isSigning || viewState.isSubmitting,
processingText: context.translate(I18n.scaSigning),
canSubmit: viewModel.canSubmitPin,
submitText: context.translate(I18n.scaConnect),
clearPinText: context.translate(I18n.scaClearPin),
onDigitPressed: viewModel.onDigitPressed,
onBackspacePressed: viewModel.onBackspacePressed,
onClearPin: viewModel.onClearPin,
onSubmit: () => viewModel.onPinSubmit(),
),
),
),
TextButton(
onPressed: () => navigation.goBack(),
child: Text(context.translate(I18n.cancel)),
),
],
),
),
);
}
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
appBar: AppBar(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: theme.getColorFor(ThemeCode.textPrimary),
),
onPressed: () => navigation.goBack(),
),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
Text(
context.translate(I18n.renewCardTitle),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
context.translate(I18n.renewCardTokenHint),
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 20),
CustomTextField(
controller: viewModel.cardTokenController,
hint: 'XXXXXXXXXX',
),
const Spacer(),
SizedBox(
width: double.infinity,
child: PrimaryButton(
onPressed: viewState.cardToken.isEmpty
? null
: viewModel.onCardTokenSubmit,
text: context.translate(I18n.cardPinNext),
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
),
const SizedBox(height: 8),
Center(
child: TextButton(
onPressed: () => navigation.goBack(),
child: Text(context.translate(I18n.cancel)),
),
),
const SizedBox(height: 16),
],
),
),
),
);
}
}

View File

@@ -1,141 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';
import 'package:sca_treezor/sca_treezor.dart';
import 'package:sf_shared/sf_shared.dart';
import '../child_wallet/child_data_provider.dart';
import 'renew_card_view_state.dart';
final renewCardViewModelProvider = NotifierProvider.autoDispose
.family<RenewCardViewModel, RenewCardViewState, String>(
RenewCardViewModel.new,
);
class RenewCardViewModel extends Notifier<RenewCardViewState> {
final String childId;
RenewCardViewModel(this.childId);
late final TextEditingController cardTokenController;
late final TreezorWalletConnectionService _connectionService;
late final TreezorWalletSignatureService _signatureService;
@override
RenewCardViewState build() {
_connectionService = GetIt.I<TreezorWalletConnectionService>();
_signatureService = GetIt.I<TreezorWalletSignatureService>();
cardTokenController = TextEditingController();
cardTokenController.addListener(_onTokenChanged);
ref.onDispose(_disposeControllers);
ref.listen(childDataProvider(childId), (prev, next) {
state = state.copyWith(
isLoading: next.isLoading,
childProfile: next.childProfile,
childWallet: next.childWallet,
device: next.device,
errorMessage: next.errorMessage,
);
});
final data = ref.read(childDataProvider(childId));
return RenewCardViewState(
isLoading: data.isLoading,
childProfile: data.childProfile,
childWallet: data.childWallet,
device: data.device,
errorMessage: data.errorMessage,
);
}
void _disposeControllers() {
cardTokenController.removeListener(_onTokenChanged);
cardTokenController.dispose();
}
void _onTokenChanged() {
state = state.copyWith(cardToken: cardTokenController.text.trim());
}
void onCardTokenSubmit() {
if (state.cardToken.isEmpty) return;
state = state.copyWith(step: RenewCardStep.scaPin, pin: '', errorMessage: '');
}
void goBackToToken() {
state = state.copyWith(step: RenewCardStep.cardToken, pin: '');
}
void onDigitPressed(String digit) {
if (state.pin.length >= 6) return;
state = state.copyWith(pin: state.pin + digit, errorMessage: '');
}
void onBackspacePressed() {
if (state.pin.isEmpty) return;
state = state.copyWith(pin: state.pin.substring(0, state.pin.length - 1));
}
void onClearPin() {
state = state.copyWith(pin: '');
}
bool get canSubmitPin => state.pin.length == 6;
Future<void> onPinSubmit() async {
final childProfile = state.childProfile;
final cardToken = state.cardToken;
if (childProfile == null || cardToken.isEmpty) return;
state = state.copyWith(isSigning: true, errorMessage: '');
try {
debugPrint('[RenewCard] connecting with SCA PIN...');
await _connectionService.connectWithPin(loginPin: state.pin);
if (!ref.mounted) return;
debugPrint('[RenewCard] SCA connection successful');
final url =
'https://savefamily.sandbox.treezor.co/v1/cards/$cardToken/public-token-activation';
final scaInput = jsonEncode({
'url': url,
'body': <String, dynamic>{},
});
debugPrint('[RenewCard] scaInput: $scaInput');
final scaProof = await _signatureService.generateJwsWithPin(
message: '',
input: scaInput,
pin: state.pin,
);
debugPrint('[RenewCard] scaProof: $scaProof');
if (!ref.mounted) return;
state = state.copyWith(isSigning: false, isSubmitting: true, pin: '');
debugPrint('[RenewCard] calling backend renewCard childProfileId=${childProfile.id} publicToken=$cardToken');
await ref.read(treezorRepositoryProvider).renewCard(
childProfileId: childProfile.id,
publicToken: cardToken,
scaProof: scaProof,
);
debugPrint('[RenewCard] renewCard SUCCESS');
if (!ref.mounted) return;
ref.read(walletRefreshProvider.notifier).refresh();
state = state.copyWith(isSubmitting: false, success: true);
} catch (e) {
debugPrint('[RenewCard] ERROR: $e');
if (!ref.mounted) return;
state = state.copyWith(
isSigning: false,
isSubmitting: false,
pin: '',
errorMessage: e.toString(),
);
}
}
}

View File

@@ -1,23 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:sf_shared/sf_shared.dart';
part 'renew_card_view_state.freezed.dart';
enum RenewCardStep { cardToken, scaPin }
@freezed
abstract class RenewCardViewState with _$RenewCardViewState {
const factory RenewCardViewState({
@Default(true) bool isLoading,
ChildProfileEntity? childProfile,
ChildWalletEntity? childWallet,
DeviceEntity? device,
@Default('') String cardToken,
@Default('') String pin,
@Default(RenewCardStep.cardToken) RenewCardStep step,
@Default(false) bool isSigning,
@Default(false) bool isSubmitting,
@Default(false) bool success,
@Default('') String errorMessage,
}) = _RenewCardViewState;
}

View File

@@ -1,373 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'renew_card_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$RenewCardViewState {
bool get isLoading; ChildProfileEntity? get childProfile; ChildWalletEntity? get childWallet; DeviceEntity? get device; String get cardToken; String get pin; RenewCardStep get step; bool get isSigning; bool get isSubmitting; bool get success; String get errorMessage;
/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$RenewCardViewStateCopyWith<RenewCardViewState> get copyWith => _$RenewCardViewStateCopyWithImpl<RenewCardViewState>(this as RenewCardViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is RenewCardViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardToken, cardToken) || other.cardToken == cardToken)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.step, step) || other.step == step)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.success, success) || other.success == success)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,childProfile,childWallet,device,cardToken,pin,step,isSigning,isSubmitting,success,errorMessage);
@override
String toString() {
return 'RenewCardViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardToken: $cardToken, pin: $pin, step: $step, isSigning: $isSigning, isSubmitting: $isSubmitting, success: $success, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class $RenewCardViewStateCopyWith<$Res> {
factory $RenewCardViewStateCopyWith(RenewCardViewState value, $Res Function(RenewCardViewState) _then) = _$RenewCardViewStateCopyWithImpl;
@useResult
$Res call({
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardToken, String pin, RenewCardStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage
});
$ChildProfileEntityCopyWith<$Res>? get childProfile;$ChildWalletEntityCopyWith<$Res>? get childWallet;$DeviceEntityCopyWith<$Res>? get device;
}
/// @nodoc
class _$RenewCardViewStateCopyWithImpl<$Res>
implements $RenewCardViewStateCopyWith<$Res> {
_$RenewCardViewStateCopyWithImpl(this._self, this._then);
final RenewCardViewState _self;
final $Res Function(RenewCardViewState) _then;
/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardToken = null,Object? pin = null,Object? step = null,Object? isSigning = null,Object? isSubmitting = null,Object? success = null,Object? errorMessage = null,}) {
return _then(_self.copyWith(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,childProfile: freezed == childProfile ? _self.childProfile : childProfile // ignore: cast_nullable_to_non_nullable
as ChildProfileEntity?,childWallet: freezed == childWallet ? _self.childWallet : childWallet // ignore: cast_nullable_to_non_nullable
as ChildWalletEntity?,device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
as DeviceEntity?,cardToken: null == cardToken ? _self.cardToken : cardToken // ignore: cast_nullable_to_non_nullable
as String,pin: null == pin ? _self.pin : pin // ignore: cast_nullable_to_non_nullable
as String,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
as RenewCardStep,isSigning: null == isSigning ? _self.isSigning : isSigning // ignore: cast_nullable_to_non_nullable
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,success: null == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildProfileEntityCopyWith<$Res>? get childProfile {
if (_self.childProfile == null) {
return null;
}
return $ChildProfileEntityCopyWith<$Res>(_self.childProfile!, (value) {
return _then(_self.copyWith(childProfile: value));
});
}/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildWalletEntityCopyWith<$Res>? get childWallet {
if (_self.childWallet == null) {
return null;
}
return $ChildWalletEntityCopyWith<$Res>(_self.childWallet!, (value) {
return _then(_self.copyWith(childWallet: value));
});
}/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceEntityCopyWith<$Res>? get device {
if (_self.device == null) {
return null;
}
return $DeviceEntityCopyWith<$Res>(_self.device!, (value) {
return _then(_self.copyWith(device: value));
});
}
}
/// Adds pattern-matching-related methods to [RenewCardViewState].
extension RenewCardViewStatePatterns on RenewCardViewState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _RenewCardViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _RenewCardViewState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _RenewCardViewState value) $default,){
final _that = this;
switch (_that) {
case _RenewCardViewState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _RenewCardViewState value)? $default,){
final _that = this;
switch (_that) {
case _RenewCardViewState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardToken, String pin, RenewCardStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _RenewCardViewState() when $default != null:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardToken,_that.pin,_that.step,_that.isSigning,_that.isSubmitting,_that.success,_that.errorMessage);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardToken, String pin, RenewCardStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage) $default,) {final _that = this;
switch (_that) {
case _RenewCardViewState():
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardToken,_that.pin,_that.step,_that.isSigning,_that.isSubmitting,_that.success,_that.errorMessage);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardToken, String pin, RenewCardStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage)? $default,) {final _that = this;
switch (_that) {
case _RenewCardViewState() when $default != null:
return $default(_that.isLoading,_that.childProfile,_that.childWallet,_that.device,_that.cardToken,_that.pin,_that.step,_that.isSigning,_that.isSubmitting,_that.success,_that.errorMessage);case _:
return null;
}
}
}
/// @nodoc
class _RenewCardViewState implements RenewCardViewState {
const _RenewCardViewState({this.isLoading = true, this.childProfile, this.childWallet, this.device, this.cardToken = '', this.pin = '', this.step = RenewCardStep.cardToken, this.isSigning = false, this.isSubmitting = false, this.success = false, this.errorMessage = ''});
@override@JsonKey() final bool isLoading;
@override final ChildProfileEntity? childProfile;
@override final ChildWalletEntity? childWallet;
@override final DeviceEntity? device;
@override@JsonKey() final String cardToken;
@override@JsonKey() final String pin;
@override@JsonKey() final RenewCardStep step;
@override@JsonKey() final bool isSigning;
@override@JsonKey() final bool isSubmitting;
@override@JsonKey() final bool success;
@override@JsonKey() final String errorMessage;
/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$RenewCardViewStateCopyWith<_RenewCardViewState> get copyWith => __$RenewCardViewStateCopyWithImpl<_RenewCardViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RenewCardViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.childProfile, childProfile) || other.childProfile == childProfile)&&(identical(other.childWallet, childWallet) || other.childWallet == childWallet)&&(identical(other.device, device) || other.device == device)&&(identical(other.cardToken, cardToken) || other.cardToken == cardToken)&&(identical(other.pin, pin) || other.pin == pin)&&(identical(other.step, step) || other.step == step)&&(identical(other.isSigning, isSigning) || other.isSigning == isSigning)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.success, success) || other.success == success)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,childProfile,childWallet,device,cardToken,pin,step,isSigning,isSubmitting,success,errorMessage);
@override
String toString() {
return 'RenewCardViewState(isLoading: $isLoading, childProfile: $childProfile, childWallet: $childWallet, device: $device, cardToken: $cardToken, pin: $pin, step: $step, isSigning: $isSigning, isSubmitting: $isSubmitting, success: $success, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class _$RenewCardViewStateCopyWith<$Res> implements $RenewCardViewStateCopyWith<$Res> {
factory _$RenewCardViewStateCopyWith(_RenewCardViewState value, $Res Function(_RenewCardViewState) _then) = __$RenewCardViewStateCopyWithImpl;
@override @useResult
$Res call({
bool isLoading, ChildProfileEntity? childProfile, ChildWalletEntity? childWallet, DeviceEntity? device, String cardToken, String pin, RenewCardStep step, bool isSigning, bool isSubmitting, bool success, String errorMessage
});
@override $ChildProfileEntityCopyWith<$Res>? get childProfile;@override $ChildWalletEntityCopyWith<$Res>? get childWallet;@override $DeviceEntityCopyWith<$Res>? get device;
}
/// @nodoc
class __$RenewCardViewStateCopyWithImpl<$Res>
implements _$RenewCardViewStateCopyWith<$Res> {
__$RenewCardViewStateCopyWithImpl(this._self, this._then);
final _RenewCardViewState _self;
final $Res Function(_RenewCardViewState) _then;
/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? childProfile = freezed,Object? childWallet = freezed,Object? device = freezed,Object? cardToken = null,Object? pin = null,Object? step = null,Object? isSigning = null,Object? isSubmitting = null,Object? success = null,Object? errorMessage = null,}) {
return _then(_RenewCardViewState(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,childProfile: freezed == childProfile ? _self.childProfile : childProfile // ignore: cast_nullable_to_non_nullable
as ChildProfileEntity?,childWallet: freezed == childWallet ? _self.childWallet : childWallet // ignore: cast_nullable_to_non_nullable
as ChildWalletEntity?,device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
as DeviceEntity?,cardToken: null == cardToken ? _self.cardToken : cardToken // ignore: cast_nullable_to_non_nullable
as String,pin: null == pin ? _self.pin : pin // ignore: cast_nullable_to_non_nullable
as String,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable
as RenewCardStep,isSigning: null == isSigning ? _self.isSigning : isSigning // ignore: cast_nullable_to_non_nullable
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,success: null == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildProfileEntityCopyWith<$Res>? get childProfile {
if (_self.childProfile == null) {
return null;
}
return $ChildProfileEntityCopyWith<$Res>(_self.childProfile!, (value) {
return _then(_self.copyWith(childProfile: value));
});
}/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChildWalletEntityCopyWith<$Res>? get childWallet {
if (_self.childWallet == null) {
return null;
}
return $ChildWalletEntityCopyWith<$Res>(_self.childWallet!, (value) {
return _then(_self.copyWith(childWallet: value));
});
}/// Create a copy of RenewCardViewState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceEntityCopyWith<$Res>? get device {
if (_self.device == null) {
return null;
}
return $DeviceEntityCopyWith<$Res>(_self.device!, (value) {
return _then(_self.copyWith(device: value));
});
}
}
// dart format on

View File

@@ -93,6 +93,7 @@ class ControlPanelViewModel extends Notifier<ControlPanelViewState> {
Future<void> refreshPositions() async {
if (state.devices.isEmpty) return;
state = state.copyWith(errorMessage: '');
try {
final positionLists = await Future.wait(
state.devices.map(

View File

@@ -11,4 +11,5 @@ export 'src/features/rewards/rewards_builder.dart';
export 'src/features/activity_meter/activity_meter_builder.dart';
export 'src/features/apps_use/apps_use_builder.dart';
export 'src/features/volume_control/volume_control_builder.dart';
export 'src/features/call_history/call_history_builder.dart';
export 'src/features/call_history/call_history_builder.dart';
export 'src/features/background_image/background_image_builder.dart';

View File

@@ -0,0 +1,14 @@
import '../models/get_background_image_response_model.dart';
abstract class BackgroundImageRemoteDatasource {
Future<GetBackgroundImageResponseModel> getBackgroundImage();
Future<String> uploadImage({
required String path,
});
Future<void> setBackgroundImage({
required String deviceId,
required String photoId,
});
}

View File

@@ -0,0 +1,73 @@
import 'package:dio/dio.dart';
import 'package:get_it/get_it.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_infrastructure/configure_dependencies.dart';
import '../models/get_background_image_response_model.dart';
import 'background_image_remote_datasource.dart';
class BackgroundImageRemoteDatasourceImpl
implements BackgroundImageRemoteDatasource {
BackgroundImageRemoteDatasourceImpl(this._repository);
final QuestiaRepository _repository;
@override
Future<GetBackgroundImageResponseModel> getBackgroundImage() async {
final response = await safeCall(
() => _repository.get<dynamic>('/photos/files'),
'Error getting background image',
);
final data = response.data;
if (data == null || (data is Map && data.isEmpty)) {
return const GetBackgroundImageResponseModel(items: []);
}
return GetBackgroundImageResponseModel.fromJson(
data is Map<String, dynamic> ? data : data as Map<String, dynamic>,
);
}
@override
Future<String> uploadImage({required String path}) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(path, filename: 'photo.jpg'),
});
final dio = GetIt.I<Dio>();
dio.options.headers.remove('content-type');
try {
final response = await dio.post<dynamic>('/photos', data: formData);
final data = response.data;
if (data == null) {
throw Exception('Empty response from upload');
}
final map = data is Map<String, dynamic> ? data : <String, dynamic>{};
final id = map['id'] as String?;
if (id == null) {
throw Exception('No photo ID in upload response');
}
return id;
} on DioException catch (e) {
throw mapDioError(e, defaultMessage: 'Error uploading image');
} finally {
dio.options.headers['content-type'] = 'application/json';
}
}
@override
Future<void> setBackgroundImage({
required String deviceId,
required String photoId,
}) async {
await safeCall(
() => _repository.put<dynamic>(
'/devices/$deviceId/background-image',
body: {'photoId': photoId},
),
'Error setting background image',
);
}
}

View File

@@ -14,7 +14,7 @@ class PicturesRemoteDatasourceImpl implements PicturesRemoteDatasource {
Future<List<PictureEntity>> getPictures({required String deviceId}) async {
try {
final response = await _repository.get<Map<String, dynamic>>(
'/devices/identificator/$deviceId/photos',
'/devices/identificator/$deviceId/photos/files',
);
final data = response.data;

View File

@@ -0,0 +1,38 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'get_pictures_response_model.dart';
part 'get_background_image_response_model.freezed.dart';
part 'get_background_image_response_model.g.dart';
@freezed
abstract class GetBackgroundImageResponseModel with _$GetBackgroundImageResponseModel {
const factory GetBackgroundImageResponseModel({
@Default([]) List<GetPicturesItemResponseModel> items,
@Default(0) int total,
@Default(1) int page,
@Default(1) int pages,
}) = _GetBackgroundImageResponseModel;
factory GetBackgroundImageResponseModel.fromJson(Map<String, dynamic> json) =>
_$GetBackgroundImageResponseModelFromJson(json);
}
extension GetBackgroundImageResponseModelMapper on GetBackgroundImageResponseModel {
List<PictureEntity> toEntities() {
return items
.map((item) => PictureEntity(
id: item.id,
deviceIdentificator: item.deviceIdentificator,
imgType: item.imgType,
timestamp: item.timestamp,
fileId: item.fileId,
fileName: item.fileName,
contentType: item.contentType,
createdAt: item.createdAt,
fileBytes: extractFileBytes(item.file),
))
.toList();
}
}

View File

@@ -0,0 +1,292 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'get_background_image_response_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$GetBackgroundImageResponseModel {
List<GetPicturesItemResponseModel> get items; int get total; int get page; int get pages;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$GetBackgroundImageResponseModelCopyWith<GetBackgroundImageResponseModel> get copyWith => _$GetBackgroundImageResponseModelCopyWithImpl<GetBackgroundImageResponseModel>(this as GetBackgroundImageResponseModel, _$identity);
/// Serializes this GetBackgroundImageResponseModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetBackgroundImageResponseModel&&const DeepCollectionEquality().equals(other.items, items)&&(identical(other.total, total) || other.total == total)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(items),total,page,pages);
@override
String toString() {
return 'GetBackgroundImageResponseModel(items: $items, total: $total, page: $page, pages: $pages)';
}
}
/// @nodoc
abstract mixin class $GetBackgroundImageResponseModelCopyWith<$Res> {
factory $GetBackgroundImageResponseModelCopyWith(GetBackgroundImageResponseModel value, $Res Function(GetBackgroundImageResponseModel) _then) = _$GetBackgroundImageResponseModelCopyWithImpl;
@useResult
$Res call({
List<GetPicturesItemResponseModel> items, int total, int page, int pages
});
}
/// @nodoc
class _$GetBackgroundImageResponseModelCopyWithImpl<$Res>
implements $GetBackgroundImageResponseModelCopyWith<$Res> {
_$GetBackgroundImageResponseModelCopyWithImpl(this._self, this._then);
final GetBackgroundImageResponseModel _self;
final $Res Function(GetBackgroundImageResponseModel) _then;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? total = null,Object? page = null,Object? pages = null,}) {
return _then(_self.copyWith(
items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
as List<GetPicturesItemResponseModel>,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
as int,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
as int,pages: null == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [GetBackgroundImageResponseModel].
extension GetBackgroundImageResponseModelPatterns on GetBackgroundImageResponseModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _GetBackgroundImageResponseModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _GetBackgroundImageResponseModel value) $default,){
final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _GetBackgroundImageResponseModel value)? $default,){
final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<GetPicturesItemResponseModel> items, int total, int page, int pages)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() when $default != null:
return $default(_that.items,_that.total,_that.page,_that.pages);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<GetPicturesItemResponseModel> items, int total, int page, int pages) $default,) {final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel():
return $default(_that.items,_that.total,_that.page,_that.pages);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<GetPicturesItemResponseModel> items, int total, int page, int pages)? $default,) {final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() when $default != null:
return $default(_that.items,_that.total,_that.page,_that.pages);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _GetBackgroundImageResponseModel implements GetBackgroundImageResponseModel {
const _GetBackgroundImageResponseModel({final List<GetPicturesItemResponseModel> items = const [], this.total = 0, this.page = 1, this.pages = 1}): _items = items;
factory _GetBackgroundImageResponseModel.fromJson(Map<String, dynamic> json) => _$GetBackgroundImageResponseModelFromJson(json);
final List<GetPicturesItemResponseModel> _items;
@override@JsonKey() List<GetPicturesItemResponseModel> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
@override@JsonKey() final int total;
@override@JsonKey() final int page;
@override@JsonKey() final int pages;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$GetBackgroundImageResponseModelCopyWith<_GetBackgroundImageResponseModel> get copyWith => __$GetBackgroundImageResponseModelCopyWithImpl<_GetBackgroundImageResponseModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$GetBackgroundImageResponseModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetBackgroundImageResponseModel&&const DeepCollectionEquality().equals(other._items, _items)&&(identical(other.total, total) || other.total == total)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_items),total,page,pages);
@override
String toString() {
return 'GetBackgroundImageResponseModel(items: $items, total: $total, page: $page, pages: $pages)';
}
}
/// @nodoc
abstract mixin class _$GetBackgroundImageResponseModelCopyWith<$Res> implements $GetBackgroundImageResponseModelCopyWith<$Res> {
factory _$GetBackgroundImageResponseModelCopyWith(_GetBackgroundImageResponseModel value, $Res Function(_GetBackgroundImageResponseModel) _then) = __$GetBackgroundImageResponseModelCopyWithImpl;
@override @useResult
$Res call({
List<GetPicturesItemResponseModel> items, int total, int page, int pages
});
}
/// @nodoc
class __$GetBackgroundImageResponseModelCopyWithImpl<$Res>
implements _$GetBackgroundImageResponseModelCopyWith<$Res> {
__$GetBackgroundImageResponseModelCopyWithImpl(this._self, this._then);
final _GetBackgroundImageResponseModel _self;
final $Res Function(_GetBackgroundImageResponseModel) _then;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? total = null,Object? page = null,Object? pages = null,}) {
return _then(_GetBackgroundImageResponseModel(
items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
as List<GetPicturesItemResponseModel>,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
as int,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
as int,pages: null == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

View File

@@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'get_background_image_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_GetBackgroundImageResponseModel _$GetBackgroundImageResponseModelFromJson(
Map<String, dynamic> json,
) => _GetBackgroundImageResponseModel(
items:
(json['items'] as List<dynamic>?)
?.map(
(e) => GetPicturesItemResponseModel.fromJson(
e as Map<String, dynamic>,
),
)
.toList() ??
const [],
total: (json['total'] as num?)?.toInt() ?? 0,
page: (json['page'] as num?)?.toInt() ?? 1,
pages: (json['pages'] as num?)?.toInt() ?? 1,
);
Map<String, dynamic> _$GetBackgroundImageResponseModelToJson(
_GetBackgroundImageResponseModel instance,
) => <String, dynamic>{
'items': instance.items,
'total': instance.total,
'page': instance.page,
'pages': instance.pages,
};

View File

@@ -1,3 +1,6 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -19,19 +22,39 @@ abstract class GetPicturesItemResponseModel
with _$GetPicturesItemResponseModel {
const factory GetPicturesItemResponseModel({
required String id,
required String deviceIdentificator,
@Default('') String deviceIdentificator,
String? imgType,
String? timestamp,
required String fileId,
@Default('') String fileId,
String? fileName,
String? contentType,
required int createdAt,
@Default(0) int createdAt,
Map<String, dynamic>? file,
}) = _GetPicturesItemResponseModel;
factory GetPicturesItemResponseModel.fromJson(Map<String, dynamic> json) =>
_$GetPicturesItemResponseModelFromJson(json);
}
Uint8List? extractFileBytes(Map<String, dynamic>? file) {
if (file == null) return null;
final fileData = file['file'];
if (fileData is String) {
try {
return base64Decode(fileData);
} catch (_) {
return null;
}
}
if (fileData is Map) {
final data = fileData['data'];
if (data is List) {
return Uint8List.fromList(data.cast<int>());
}
}
return null;
}
extension GetPicturesResponseModelMapper on GetPicturesResponseModel {
List<PictureEntity> toEntity() {
return items
@@ -44,6 +67,7 @@ extension GetPicturesResponseModelMapper on GetPicturesResponseModel {
fileName: item.fileName,
contentType: item.contentType,
createdAt: item.createdAt,
fileBytes: extractFileBytes(item.file),
))
.toList();
}

View File

@@ -284,7 +284,7 @@ as List<GetPicturesItemResponseModel>,
/// @nodoc
mixin _$GetPicturesItemResponseModel {
String get id; String get deviceIdentificator; String? get imgType; String? get timestamp; String get fileId; String? get fileName; String? get contentType; int get createdAt;
String get id; String get deviceIdentificator; String? get imgType; String? get timestamp; String get fileId; String? get fileName; String? get contentType; int get createdAt; Map<String, dynamic>? get file;
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -297,16 +297,16 @@ $GetPicturesItemResponseModelCopyWith<GetPicturesItemResponseModel> get copyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetPicturesItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetPicturesItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&const DeepCollectionEquality().equals(other.file, file));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt,const DeepCollectionEquality().hash(file));
@override
String toString() {
return 'GetPicturesItemResponseModel(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
return 'GetPicturesItemResponseModel(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt, file: $file)';
}
@@ -317,7 +317,7 @@ abstract mixin class $GetPicturesItemResponseModelCopyWith<$Res> {
factory $GetPicturesItemResponseModelCopyWith(GetPicturesItemResponseModel value, $Res Function(GetPicturesItemResponseModel) _then) = _$GetPicturesItemResponseModelCopyWithImpl;
@useResult
$Res call({
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Map<String, dynamic>? file
});
@@ -334,7 +334,7 @@ class _$GetPicturesItemResponseModelCopyWithImpl<$Res>
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,Object? file = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
@@ -344,7 +344,8 @@ as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullab
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,
as int,file: freezed == file ? _self.file : file // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}
@@ -429,10 +430,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Map<String, dynamic>? file)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt,_that.file);case _:
return orElse();
}
@@ -450,10 +451,10 @@ return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Map<String, dynamic>? file) $default,) {final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel():
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt,_that.file);case _:
throw StateError('Unexpected subclass');
}
@@ -470,10 +471,10 @@ return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Map<String, dynamic>? file)? $default,) {final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt,_that.file);case _:
return null;
}
@@ -485,17 +486,26 @@ return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp
@JsonSerializable()
class _GetPicturesItemResponseModel implements GetPicturesItemResponseModel {
const _GetPicturesItemResponseModel({required this.id, required this.deviceIdentificator, this.imgType, this.timestamp, required this.fileId, this.fileName, this.contentType, required this.createdAt});
const _GetPicturesItemResponseModel({required this.id, this.deviceIdentificator = '', this.imgType, this.timestamp, this.fileId = '', this.fileName, this.contentType, this.createdAt = 0, final Map<String, dynamic>? file}): _file = file;
factory _GetPicturesItemResponseModel.fromJson(Map<String, dynamic> json) => _$GetPicturesItemResponseModelFromJson(json);
@override final String id;
@override final String deviceIdentificator;
@override@JsonKey() final String deviceIdentificator;
@override final String? imgType;
@override final String? timestamp;
@override final String fileId;
@override@JsonKey() final String fileId;
@override final String? fileName;
@override final String? contentType;
@override final int createdAt;
@override@JsonKey() final int createdAt;
final Map<String, dynamic>? _file;
@override Map<String, dynamic>? get file {
final value = _file;
if (value == null) return null;
if (_file is EqualUnmodifiableMapView) return _file;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@@ -510,16 +520,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetPicturesItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetPicturesItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&const DeepCollectionEquality().equals(other._file, _file));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt,const DeepCollectionEquality().hash(_file));
@override
String toString() {
return 'GetPicturesItemResponseModel(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
return 'GetPicturesItemResponseModel(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt, file: $file)';
}
@@ -530,7 +540,7 @@ abstract mixin class _$GetPicturesItemResponseModelCopyWith<$Res> implements $Ge
factory _$GetPicturesItemResponseModelCopyWith(_GetPicturesItemResponseModel value, $Res Function(_GetPicturesItemResponseModel) _then) = __$GetPicturesItemResponseModelCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Map<String, dynamic>? file
});
@@ -547,7 +557,7 @@ class __$GetPicturesItemResponseModelCopyWithImpl<$Res>
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,Object? file = freezed,}) {
return _then(_GetPicturesItemResponseModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
@@ -557,7 +567,8 @@ as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullab
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,
as int,file: freezed == file ? _self._file : file // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}

View File

@@ -24,13 +24,14 @@ _GetPicturesItemResponseModel _$GetPicturesItemResponseModelFromJson(
Map<String, dynamic> json,
) => _GetPicturesItemResponseModel(
id: json['id'] as String,
deviceIdentificator: json['deviceIdentificator'] as String,
deviceIdentificator: json['deviceIdentificator'] as String? ?? '',
imgType: json['imgType'] as String?,
timestamp: json['timestamp'] as String?,
fileId: json['fileId'] as String,
fileId: json['fileId'] as String? ?? '',
fileName: json['fileName'] as String?,
contentType: json['contentType'] as String?,
createdAt: (json['createdAt'] as num).toInt(),
createdAt: (json['createdAt'] as num?)?.toInt() ?? 0,
file: json['file'] as Map<String, dynamic>?,
);
Map<String, dynamic> _$GetPicturesItemResponseModelToJson(
@@ -44,4 +45,5 @@ Map<String, dynamic> _$GetPicturesItemResponseModelToJson(
'fileName': instance.fileName,
'contentType': instance.contentType,
'createdAt': instance.createdAt,
'file': instance.file,
};

View File

@@ -0,0 +1,28 @@
import 'package:device_management/src/core/data/models/get_background_image_response_model.dart';
import '../../../features/remote_connection/domain/entities/picture_entity.dart';
import '../../domain/repositories/background_image_repository.dart';
import '../datasources/background_image_remote_datasource.dart';
class BackgroundImageRepositoryImpl implements BackgroundImageRepository {
const BackgroundImageRepositoryImpl(this._remote);
final BackgroundImageRemoteDatasource _remote;
@override
Future<List<PictureEntity>> getPhotos() async {
final model = await _remote.getBackgroundImage();
return model.toEntities();
}
@override
Future<String> uploadImage({required String path}) {
return _remote.uploadImage(path: path);
}
@override
Future<void> setBackgroundImage({required String deviceId, required String photoId}) {
return _remote.setBackgroundImage(deviceId: deviceId, photoId: photoId);
}
}

View File

@@ -0,0 +1,8 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class BackgroundImageRepository {
Future<List<PictureEntity>> getPhotos();
Future<String> uploadImage({required String path});
Future<void> setBackgroundImage({required String deviceId, required String photoId});
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import '../data/datasources/background_image_remote_datasource.dart';
import '../data/datasources/background_image_remote_datasource_impl.dart';
final backgroundImageRemoteDatasourceProvider =
Provider<BackgroundImageRemoteDatasource>((ref) {
final questiaRepository = getIt<QuestiaRepository>();
return BackgroundImageRemoteDatasourceImpl(questiaRepository);
});

View File

@@ -0,0 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/repositories/background_image_repository_impl.dart';
import '../domain/repositories/background_image_repository.dart';
import 'background_image_remote_datasource_provider.dart';
final backgroundImageRepositoryProvider =
Provider<BackgroundImageRepository>((ref) {
final remote = ref.read(backgroundImageRemoteDatasourceProvider);
return BackgroundImageRepositoryImpl(remote);
});

View File

@@ -7,6 +7,7 @@ import 'package:utils/utils.dart';
import '../../../core/presentation/widgets/time_range_selector.dart';
import 'state/activity_meter_view_model.dart';
import 'state/activity_meter_view_state.dart';
import 'widgets/steps_bar_chart.dart';
import 'widgets/steps_history_section.dart';
import 'widgets/steps_progress_ring.dart';
@@ -20,12 +21,18 @@ class ActivityMeterScreen extends ConsumerWidget {
final theme = ref.watch(themePortProvider);
final state = ref.watch(activityMeterViewModelProvider);
final vm = ref.read(activityMeterViewModelProvider.notifier);
final device = ref.watch(selectedDeviceProvider);
ref.listen(
activityMeterViewModelProvider.select((s) => s.errorMessage),
activityMeterViewModelProvider.select((s) => s.errorEvent),
(previous, next) {
if (next.isNotEmpty) {
showTopSnackbar(context, message: next, type: MessageType.error);
if (next != null) {
final message = switch (next) {
ActivityMeterErrorEvent.loadData => context.translate(I18n.errorActivityData),
ActivityMeterErrorEvent.loadMore => context.translate(I18n.errorActivityData),
ActivityMeterErrorEvent.pedometer => context.translate(I18n.errorPedometer),
};
showTopSnackbar(context, message: message, type: MessageType.error);
}
},
);
@@ -69,6 +76,43 @@ class ActivityMeterScreen extends ConsumerWidget {
],
),
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
vertical: SizeUtils.getByScreen(small: 4, big: 4),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
context.translate(I18n.activityMeterPedometer),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
fontWeight: FontWeight.w500,
),
),
Switch.adaptive(
value: device?.settings.pedometer ?? false,
activeTrackColor: theme.getColorFor(ThemeCode.legacyPrimary),
onChanged: (value) async {
final success = await vm.togglePedometer(enabled: value);
if (!context.mounted) return;
if (success) {
showTopSnackbar(
context,
message: context.translate(
value
? I18n.activityMeterPedometerEnabled
: I18n.activityMeterPedometerDisabled,
),
type: MessageType.success,
);
}
},
),
],
),
),
StepsProgressRing(
steps: state.todayTotal,
goal: state.dailyGoal,

View File

@@ -79,7 +79,7 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
if (!ref.mounted) return;
state = state.copyWith(
isLoadingMore: false,
errorMessage: _formatError(e),
errorEvent: ActivityMeterErrorEvent.loadMore,
);
}
}
@@ -113,7 +113,7 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
await _loadFilteredData();
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
state = state.copyWith(isLoading: false, errorEvent: ActivityMeterErrorEvent.loadData);
}
}
@@ -157,11 +157,11 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
hasMoreHistory: histSteps.length >= _historyPageSize,
stats: _computeStats(chartDaily),
isLoading: false,
errorMessage: '',
errorEvent: null,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
state = state.copyWith(isLoading: false, errorEvent: ActivityMeterErrorEvent.loadData);
}
}
@@ -243,8 +243,25 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
}
}
String _formatError(Object e) {
final msg = e.toString();
return msg.startsWith('Exception: ') ? msg.substring(11) : msg;
Future<bool> togglePedometer({required bool enabled}) async {
final device = ref.read(selectedDeviceProvider);
if (device == null) return false;
state = state.copyWith(errorEvent: null);
try {
final updatedSettings = device.settings.copyWith(pedometer: enabled);
await ref.read(deviceSettingsUpdateProvider).updateDeviceSettings(
device: device,
updatedSettings: updatedSettings,
);
if (!ref.mounted) return false;
ref.syncDeviceSettings(device, updatedSettings);
return true;
} catch (e) {
if (!ref.mounted) return false;
state = state.copyWith(errorEvent: ActivityMeterErrorEvent.pedometer);
return false;
}
}
}

View File

@@ -4,6 +4,12 @@ import '../../../../core/presentation/time_range.dart';
part 'activity_meter_view_state.freezed.dart';
enum ActivityMeterErrorEvent {
loadData,
loadMore,
pedometer,
}
@freezed
abstract class DailySteps with _$DailySteps {
const factory DailySteps({
@@ -36,6 +42,6 @@ abstract class ActivityMeterViewState with _$ActivityMeterViewState {
DateTime? customEnd,
@Default(true) bool isLoading,
@Default(false) bool isLoadingMore,
@Default('') String errorMessage,
ActivityMeterErrorEvent? errorEvent,
}) = _ActivityMeterViewState;
}

View File

@@ -537,7 +537,7 @@ as int,
/// @nodoc
mixin _$ActivityMeterViewState {
int get todayTotal; int get dailyGoal; List<DailySteps> get chartData; List<DailySteps> get historyData; int get currentHistoryPage; bool get hasMoreHistory; StepsStats get stats; TimeRange get timeRange; DateTime? get customStart; DateTime? get customEnd; bool get isLoading; bool get isLoadingMore; String get errorMessage;
int get todayTotal; int get dailyGoal; List<DailySteps> get chartData; List<DailySteps> get historyData; int get currentHistoryPage; bool get hasMoreHistory; StepsStats get stats; TimeRange get timeRange; DateTime? get customStart; DateTime? get customEnd; bool get isLoading; bool get isLoadingMore; ActivityMeterErrorEvent? get errorEvent;
/// Create a copy of ActivityMeterViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -548,16 +548,16 @@ $ActivityMeterViewStateCopyWith<ActivityMeterViewState> get copyWith => _$Activi
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other.chartData, chartData)&&const DeepCollectionEquality().equals(other.historyData, historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other.chartData, chartData)&&const DeepCollectionEquality().equals(other.historyData, historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
}
@override
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(chartData),const DeepCollectionEquality().hash(historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorMessage);
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(chartData),const DeepCollectionEquality().hash(historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorEvent);
@override
String toString() {
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorMessage: $errorMessage)';
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorEvent: $errorEvent)';
}
@@ -568,7 +568,7 @@ abstract mixin class $ActivityMeterViewStateCopyWith<$Res> {
factory $ActivityMeterViewStateCopyWith(ActivityMeterViewState value, $Res Function(ActivityMeterViewState) _then) = _$ActivityMeterViewStateCopyWithImpl;
@useResult
$Res call({
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent
});
@@ -585,7 +585,7 @@ class _$ActivityMeterViewStateCopyWithImpl<$Res>
/// Create a copy of ActivityMeterViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorEvent = freezed,}) {
return _then(_self.copyWith(
todayTotal: null == todayTotal ? _self.todayTotal : todayTotal // ignore: cast_nullable_to_non_nullable
as int,dailyGoal: null == dailyGoal ? _self.dailyGoal : dailyGoal // ignore: cast_nullable_to_non_nullable
@@ -599,8 +599,8 @@ as TimeRange,customStart: freezed == customStart ? _self.customStart : customSta
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as ActivityMeterErrorEvent?,
));
}
/// Create a copy of ActivityMeterViewState
@@ -694,10 +694,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ActivityMeterViewState() when $default != null:
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorMessage);case _:
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
return orElse();
}
@@ -715,10 +715,10 @@ return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyDa
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent) $default,) {final _that = this;
switch (_that) {
case _ActivityMeterViewState():
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorMessage);case _:
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
throw StateError('Unexpected subclass');
}
@@ -735,10 +735,10 @@ return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyDa
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent)? $default,) {final _that = this;
switch (_that) {
case _ActivityMeterViewState() when $default != null:
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorMessage);case _:
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
return null;
}
@@ -750,7 +750,7 @@ return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyDa
class _ActivityMeterViewState implements ActivityMeterViewState {
const _ActivityMeterViewState({this.todayTotal = 0, this.dailyGoal = 8000, final List<DailySteps> chartData = const [], final List<DailySteps> historyData = const [], this.currentHistoryPage = 1, this.hasMoreHistory = false, this.stats = const StepsStats(), this.timeRange = TimeRange.today, this.customStart, this.customEnd, this.isLoading = true, this.isLoadingMore = false, this.errorMessage = ''}): _chartData = chartData,_historyData = historyData;
const _ActivityMeterViewState({this.todayTotal = 0, this.dailyGoal = 8000, final List<DailySteps> chartData = const [], final List<DailySteps> historyData = const [], this.currentHistoryPage = 1, this.hasMoreHistory = false, this.stats = const StepsStats(), this.timeRange = TimeRange.today, this.customStart, this.customEnd, this.isLoading = true, this.isLoadingMore = false, this.errorEvent}): _chartData = chartData,_historyData = historyData;
@override@JsonKey() final int todayTotal;
@@ -777,7 +777,7 @@ class _ActivityMeterViewState implements ActivityMeterViewState {
@override final DateTime? customEnd;
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isLoadingMore;
@override@JsonKey() final String errorMessage;
@override final ActivityMeterErrorEvent? errorEvent;
/// Create a copy of ActivityMeterViewState
/// with the given fields replaced by the non-null parameter values.
@@ -789,16 +789,16 @@ _$ActivityMeterViewStateCopyWith<_ActivityMeterViewState> get copyWith => __$Act
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other._chartData, _chartData)&&const DeepCollectionEquality().equals(other._historyData, _historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other._chartData, _chartData)&&const DeepCollectionEquality().equals(other._historyData, _historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
}
@override
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(_chartData),const DeepCollectionEquality().hash(_historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorMessage);
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(_chartData),const DeepCollectionEquality().hash(_historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorEvent);
@override
String toString() {
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorMessage: $errorMessage)';
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorEvent: $errorEvent)';
}
@@ -809,7 +809,7 @@ abstract mixin class _$ActivityMeterViewStateCopyWith<$Res> implements $Activity
factory _$ActivityMeterViewStateCopyWith(_ActivityMeterViewState value, $Res Function(_ActivityMeterViewState) _then) = __$ActivityMeterViewStateCopyWithImpl;
@override @useResult
$Res call({
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent
});
@@ -826,7 +826,7 @@ class __$ActivityMeterViewStateCopyWithImpl<$Res>
/// Create a copy of ActivityMeterViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorEvent = freezed,}) {
return _then(_ActivityMeterViewState(
todayTotal: null == todayTotal ? _self.todayTotal : todayTotal // ignore: cast_nullable_to_non_nullable
as int,dailyGoal: null == dailyGoal ? _self.dailyGoal : dailyGoal // ignore: cast_nullable_to_non_nullable
@@ -840,8 +840,8 @@ as TimeRange,customStart: freezed == customStart ? _self.customStart : customSta
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as ActivityMeterErrorEvent?,
));
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:navigation/navigation.dart';
import 'presentation/background_image_screen.dart';
class BackgroundImageBuilder {
const BackgroundImageBuilder();
Page<void> buildPage(BuildContext context, GoRouterState state) {
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
return MaterialPage<void>(
key: state.pageKey,
child: BackgroundImageScreen(navigationContract: navigationContract),
);
}
}

View File

@@ -0,0 +1,250 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
import 'state/background_image_view_model.dart';
import 'state/background_image_view_state.dart';
class BackgroundImageScreen extends ConsumerWidget {
final NavigationContract navigationContract;
const BackgroundImageScreen({super.key, required this.navigationContract});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.read(themePortProvider);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
final state = ref.watch(backgroundImageViewModelProvider);
final vm = ref.read(backgroundImageViewModelProvider.notifier);
ref.listen(
backgroundImageViewModelProvider.select((s) => s.errorEvent),
(previous, next) {
if (next != null) {
final message = switch (next) {
BackgroundImageErrorEvent.load =>
context.translate(I18n.errorBackgroundImageLoad),
BackgroundImageErrorEvent.upload =>
context.translate(I18n.errorBackgroundImageUpload),
BackgroundImageErrorEvent.set =>
context.translate(I18n.errorBackgroundImageSet),
};
showTopSnackbar(context, message: message, type: MessageType.error);
}
},
);
ref.listen(
backgroundImageViewModelProvider.select((s) => s.successEvent),
(previous, next) {
if (next != null) {
final message = switch (next) {
BackgroundImageSuccessEvent.uploaded =>
context.translate(I18n.backgroundImageUploaded),
BackgroundImageSuccessEvent.backgroundSet =>
context.translate(I18n.backgroundImageSet),
};
showTopSnackbar(context, message: message, type: MessageType.success);
}
},
);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
elevation: 0,
centerTitle: true,
automaticallyImplyLeading: false,
leading: IconButton(
onPressed: () => navigationContract.goBack(),
icon: Icon(
Icons.adaptive.arrow_back,
color: primaryColor,
size: SizeUtils.getByScreen(small: 32, big: 28),
),
),
title: Text(
context.translate(I18n.customBackground).toUpperCase(),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
fontWeight: FontWeight.w500,
letterSpacing: 0,
color: primaryColor,
),
),
actions: [
Padding(
padding: EdgeInsets.only(
right: SizeUtils.getByScreen(small: 16, big: 14),
),
child: DecoratedBox(
decoration: BoxDecoration(
color: primaryColor,
shape: BoxShape.circle,
),
child: IconButton(
onPressed: state.isSaving ? null : vm.uploadPhoto,
icon: Icon(
Icons.add_photo_alternate_outlined,
color: Colors.white,
size: SizeUtils.getByScreen(small: 24, big: 22),
),
),
),
),
],
),
body: SafeArea(
top: false,
child: state.isLoading
? const Center(child: CircularProgressIndicator())
: state.isSaving
? const Center(child: CircularProgressIndicator())
: state.photos.isEmpty
? _EmptyState(
onUpload: vm.uploadPhoto,
primaryColor: primaryColor,
)
: _PhotoGrid(
state: state,
onPhotoTap: vm.setAsBackground,
primaryColor: primaryColor,
),
),
);
}
}
class _EmptyState extends StatelessWidget {
final VoidCallback onUpload;
final Color primaryColor;
const _EmptyState({required this.onUpload, required this.primaryColor});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.add_photo_alternate_outlined,
size: 100,
color: Colors.grey.shade400,
),
const SizedBox(height: 24),
Text(
context.translate(I18n.backgroundImageDescription),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
PrimaryButton(
onPressed: onUpload,
text: context.translate(I18n.backgroundImageTapToSelect),
color: primaryColor,
),
],
),
),
);
}
}
class _PhotoGrid extends StatelessWidget {
final BackgroundImageViewState state;
final ValueChanged<String> onPhotoTap;
final Color primaryColor;
const _PhotoGrid({
required this.state,
required this.onPhotoTap,
required this.primaryColor,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
context.translate(I18n.backgroundImageTapToChange),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 14),
),
),
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(12),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: state.photos.length,
itemBuilder: (context, index) {
final photo = state.photos[index];
final isActive = state.currentBackgroundId == photo.id;
return GestureDetector(
onTap: () => onPhotoTap(photo.id),
child: Stack(
children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isActive ? primaryColor : Colors.grey.shade300,
width: isActive ? 3 : 1,
),
),
clipBehavior: Clip.antiAlias,
child: photo.fileBytes != null
? Image.memory(
photo.fileBytes!,
fit: BoxFit.cover,
)
: Center(
child: Icon(
Icons.image_outlined,
size: 48,
color: Colors.grey.shade400,
),
),
),
),
if (isActive)
Positioned(
top: 8,
right: 8,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: primaryColor,
shape: BoxShape.circle,
),
child: const Icon(
Icons.check,
color: Colors.white,
size: 16,
),
),
),
],
),
);
},
),
),
],
);
}
}

View File

@@ -0,0 +1,148 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_shared/sf_shared.dart';
import '../../../../core/domain/repositories/background_image_repository.dart';
import '../../../../core/providers/background_image_repository_provider.dart';
import 'background_image_view_state.dart';
final backgroundImageViewModelProvider =
NotifierProvider.autoDispose<
BackgroundImageViewModel,
BackgroundImageViewState
>(BackgroundImageViewModel.new);
class BackgroundImageViewModel extends Notifier<BackgroundImageViewState> {
late final BackgroundImageRepository _repository;
late final SharedDevicesRepository _devicesRepository;
@override
BackgroundImageViewState build() {
_repository = ref.read(backgroundImageRepositoryProvider);
_devicesRepository = ref.read(sharedDevicesRepositoryProvider);
Future.microtask(_load);
return const BackgroundImageViewState();
}
Future<void> _load() async {
try {
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
final photos = await _repository.getPhotos();
if (!ref.mounted) return;
state = state.copyWith(
photos: photos,
currentBackgroundId: device.backgroundImageId,
isLoading: false,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isLoading: false,
errorEvent: BackgroundImageErrorEvent.load,
);
}
}
Future<void> reload() async {
state = state.copyWith(isLoading: true, errorEvent: null);
await _load();
}
Future<void> uploadPhoto() async {
if (state.isSaving) return;
final picker = ImagePicker();
final image = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 800,
imageQuality: 80,
);
if (image == null) return;
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
state = state.copyWith(
isSaving: true,
errorEvent: null,
successEvent: null,
);
try {
final photoId = await _repository.uploadImage(path: image.path);
if (!ref.mounted) return;
await _repository.setBackgroundImage(
deviceId: device.id,
photoId: photoId,
);
if (!ref.mounted) return;
await _refreshDevice(device);
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
successEvent: BackgroundImageSuccessEvent.uploaded,
);
await reload();
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
errorEvent: BackgroundImageErrorEvent.upload,
);
}
}
Future<void> setAsBackground(String photoId) async {
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
state = state.copyWith(
isSaving: true,
errorEvent: null,
successEvent: null,
);
try {
await _repository.setBackgroundImage(
deviceId: device.id,
photoId: photoId,
);
if (!ref.mounted) return;
await _refreshDevice(device);
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
successEvent: BackgroundImageSuccessEvent.backgroundSet,
);
await reload();
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
errorEvent: BackgroundImageErrorEvent.set,
);
}
}
Future<void> _refreshDevice(DeviceEntity currentDevice) async {
final devices = await _devicesRepository.getDevices();
if (!ref.mounted) return;
final updated = devices.firstWhere(
(d) => d.identificator == currentDevice.identificator,
orElse: () => currentDevice,
);
ref.read(selectedDeviceProvider.notifier).setSelectedDevice(updated);
}
}

View File

@@ -0,0 +1,19 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'background_image_view_state.freezed.dart';
enum BackgroundImageErrorEvent { load, upload, set }
enum BackgroundImageSuccessEvent { uploaded, backgroundSet }
@freezed
abstract class BackgroundImageViewState with _$BackgroundImageViewState {
const factory BackgroundImageViewState({
@Default([]) List<PictureEntity> photos,
@Default(true) bool isLoading,
@Default(false) bool isSaving,
String? currentBackgroundId,
BackgroundImageSuccessEvent? successEvent,
BackgroundImageErrorEvent? errorEvent,
}) = _BackgroundImageViewState;
}

View File

@@ -0,0 +1,292 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'background_image_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$BackgroundImageViewState {
List<PictureEntity> get photos; bool get isLoading; bool get isSaving; String? get currentBackgroundId; BackgroundImageSuccessEvent? get successEvent; BackgroundImageErrorEvent? get errorEvent;
/// Create a copy of BackgroundImageViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$BackgroundImageViewStateCopyWith<BackgroundImageViewState> get copyWith => _$BackgroundImageViewStateCopyWithImpl<BackgroundImageViewState>(this as BackgroundImageViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is BackgroundImageViewState&&const DeepCollectionEquality().equals(other.photos, photos)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.currentBackgroundId, currentBackgroundId) || other.currentBackgroundId == currentBackgroundId)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(photos),isLoading,isSaving,currentBackgroundId,successEvent,errorEvent);
@override
String toString() {
return 'BackgroundImageViewState(photos: $photos, isLoading: $isLoading, isSaving: $isSaving, currentBackgroundId: $currentBackgroundId, successEvent: $successEvent, errorEvent: $errorEvent)';
}
}
/// @nodoc
abstract mixin class $BackgroundImageViewStateCopyWith<$Res> {
factory $BackgroundImageViewStateCopyWith(BackgroundImageViewState value, $Res Function(BackgroundImageViewState) _then) = _$BackgroundImageViewStateCopyWithImpl;
@useResult
$Res call({
List<PictureEntity> photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent
});
}
/// @nodoc
class _$BackgroundImageViewStateCopyWithImpl<$Res>
implements $BackgroundImageViewStateCopyWith<$Res> {
_$BackgroundImageViewStateCopyWithImpl(this._self, this._then);
final BackgroundImageViewState _self;
final $Res Function(BackgroundImageViewState) _then;
/// Create a copy of BackgroundImageViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? photos = null,Object? isLoading = null,Object? isSaving = null,Object? currentBackgroundId = freezed,Object? successEvent = freezed,Object? errorEvent = freezed,}) {
return _then(_self.copyWith(
photos: null == photos ? _self.photos : photos // ignore: cast_nullable_to_non_nullable
as List<PictureEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
as bool,currentBackgroundId: freezed == currentBackgroundId ? _self.currentBackgroundId : currentBackgroundId // ignore: cast_nullable_to_non_nullable
as String?,successEvent: freezed == successEvent ? _self.successEvent : successEvent // ignore: cast_nullable_to_non_nullable
as BackgroundImageSuccessEvent?,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as BackgroundImageErrorEvent?,
));
}
}
/// Adds pattern-matching-related methods to [BackgroundImageViewState].
extension BackgroundImageViewStatePatterns on BackgroundImageViewState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _BackgroundImageViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _BackgroundImageViewState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _BackgroundImageViewState value) $default,){
final _that = this;
switch (_that) {
case _BackgroundImageViewState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _BackgroundImageViewState value)? $default,){
final _that = this;
switch (_that) {
case _BackgroundImageViewState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<PictureEntity> photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _BackgroundImageViewState() when $default != null:
return $default(_that.photos,_that.isLoading,_that.isSaving,_that.currentBackgroundId,_that.successEvent,_that.errorEvent);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<PictureEntity> photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent) $default,) {final _that = this;
switch (_that) {
case _BackgroundImageViewState():
return $default(_that.photos,_that.isLoading,_that.isSaving,_that.currentBackgroundId,_that.successEvent,_that.errorEvent);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<PictureEntity> photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent)? $default,) {final _that = this;
switch (_that) {
case _BackgroundImageViewState() when $default != null:
return $default(_that.photos,_that.isLoading,_that.isSaving,_that.currentBackgroundId,_that.successEvent,_that.errorEvent);case _:
return null;
}
}
}
/// @nodoc
class _BackgroundImageViewState implements BackgroundImageViewState {
const _BackgroundImageViewState({final List<PictureEntity> photos = const [], this.isLoading = true, this.isSaving = false, this.currentBackgroundId, this.successEvent, this.errorEvent}): _photos = photos;
final List<PictureEntity> _photos;
@override@JsonKey() List<PictureEntity> get photos {
if (_photos is EqualUnmodifiableListView) return _photos;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_photos);
}
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isSaving;
@override final String? currentBackgroundId;
@override final BackgroundImageSuccessEvent? successEvent;
@override final BackgroundImageErrorEvent? errorEvent;
/// Create a copy of BackgroundImageViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$BackgroundImageViewStateCopyWith<_BackgroundImageViewState> get copyWith => __$BackgroundImageViewStateCopyWithImpl<_BackgroundImageViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BackgroundImageViewState&&const DeepCollectionEquality().equals(other._photos, _photos)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.currentBackgroundId, currentBackgroundId) || other.currentBackgroundId == currentBackgroundId)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_photos),isLoading,isSaving,currentBackgroundId,successEvent,errorEvent);
@override
String toString() {
return 'BackgroundImageViewState(photos: $photos, isLoading: $isLoading, isSaving: $isSaving, currentBackgroundId: $currentBackgroundId, successEvent: $successEvent, errorEvent: $errorEvent)';
}
}
/// @nodoc
abstract mixin class _$BackgroundImageViewStateCopyWith<$Res> implements $BackgroundImageViewStateCopyWith<$Res> {
factory _$BackgroundImageViewStateCopyWith(_BackgroundImageViewState value, $Res Function(_BackgroundImageViewState) _then) = __$BackgroundImageViewStateCopyWithImpl;
@override @useResult
$Res call({
List<PictureEntity> photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent
});
}
/// @nodoc
class __$BackgroundImageViewStateCopyWithImpl<$Res>
implements _$BackgroundImageViewStateCopyWith<$Res> {
__$BackgroundImageViewStateCopyWithImpl(this._self, this._then);
final _BackgroundImageViewState _self;
final $Res Function(_BackgroundImageViewState) _then;
/// Create a copy of BackgroundImageViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? photos = null,Object? isLoading = null,Object? isSaving = null,Object? currentBackgroundId = freezed,Object? successEvent = freezed,Object? errorEvent = freezed,}) {
return _then(_BackgroundImageViewState(
photos: null == photos ? _self._photos : photos // ignore: cast_nullable_to_non_nullable
as List<PictureEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
as bool,currentBackgroundId: freezed == currentBackgroundId ? _self.currentBackgroundId : currentBackgroundId // ignore: cast_nullable_to_non_nullable
as String?,successEvent: freezed == successEvent ? _self.successEvent : successEvent // ignore: cast_nullable_to_non_nullable
as BackgroundImageSuccessEvent?,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as BackgroundImageErrorEvent?,
));
}
}
// dart format on

View File

@@ -2,7 +2,6 @@ import 'package:dio/dio.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import 'call_history_entity.dart';
import 'call_history_response_model.dart';
class CallHistoryDatasource {

View File

@@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_localizations/sf_localizations.dart';
import '../data/call_history_entity.dart';
import 'state/call_history_view_model.dart';
import 'state/call_history_view_state.dart';

View File

@@ -3,7 +3,6 @@ import 'package:legacy_shared/legacy_shared.dart';
import '../../data/call_history_datasource.dart';
import '../../data/call_history_datasource_provider.dart';
import '../../data/call_history_entity.dart';
import 'call_history_view_state.dart';
final callHistoryViewModelProvider =

View File

@@ -1,6 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../data/call_history_entity.dart';
import 'package:legacy_shared/legacy_shared.dart';
part 'call_history_view_state.freezed.dart';

View File

@@ -25,7 +25,7 @@ class ContactsScreen extends ConsumerWidget {
contactsViewModelProvider.select((s) => s.errorMessage),
(previous, next) {
if (next.isNotEmpty) {
showTopSnackbar(context, message: next, type: MessageType.error);
showTopSnackbar(context, message: context.translate(next), type: MessageType.error);
}
},
);
@@ -58,14 +58,21 @@ class ContactsScreen extends ConsumerWidget {
shape: const CircleBorder(),
child: InkWell(
customBorder: const CircleBorder(),
onTap: () => showDialog(
context: context,
builder: (_) => const Dialog(
backgroundColor: Colors.transparent,
child: NewContactDialog(),
),
),
child: SizedBox(
onTap: () {
if (state.contacts.length == state.maxLimit) {
showTopSnackbar(context, message: context.translate(I18n.errorContactsMax));
return;
}
showDialog(
context: context,
builder: (_) => const Dialog(
backgroundColor: Colors.transparent,
child: NewContactDialog(),
),
);
},
child: SizedBox(
width: SizeUtils.getByScreen(small: 48, big: 46),
height: SizeUtils.getByScreen(small: 48, big: 46),
child: Icon(

View File

@@ -134,6 +134,11 @@ class ContactsViewModel extends Notifier<ContactsViewState> {
Future<bool> deleteContact(ContactEntity contact) async {
if (state.isLoading) return false;
if (state.contacts.length == 1){
state = state.copyWith(errorMessage: I18n.errorContactsMin);
return true;
}
try {
state = state.copyWith(isLoading: true, errorMessage: '');

View File

@@ -8,9 +8,10 @@ part 'contacts_view_state.freezed.dart';
abstract class ContactsViewState with _$ContactsViewState {
const factory ContactsViewState({
@Default([]) List<ContactEntity> contacts,
@Default(10) int maxLimit,
@Default('+34') String dialCode,
@Default(true) bool isLoading,
@Default(false) bool isEditing,
@Default('') String errorMessage,
@Default('') String errorMessage
}) = _ContactsViewState;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ContactsViewState {
List<ContactEntity> get contacts; String get dialCode; bool get isLoading; bool get isEditing; String get errorMessage;
List<ContactEntity> get contacts; int get maxLimit; String get dialCode; bool get isLoading; bool get isEditing; String get errorMessage;
/// Create a copy of ContactsViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $ContactsViewStateCopyWith<ContactsViewState> get copyWith => _$ContactsViewStat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ContactsViewState&&const DeepCollectionEquality().equals(other.contacts, contacts)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isEditing, isEditing) || other.isEditing == isEditing)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ContactsViewState&&const DeepCollectionEquality().equals(other.contacts, contacts)&&(identical(other.maxLimit, maxLimit) || other.maxLimit == maxLimit)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isEditing, isEditing) || other.isEditing == isEditing)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(contacts),dialCode,isLoading,isEditing,errorMessage);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(contacts),maxLimit,dialCode,isLoading,isEditing,errorMessage);
@override
String toString() {
return 'ContactsViewState(contacts: $contacts, dialCode: $dialCode, isLoading: $isLoading, isEditing: $isEditing, errorMessage: $errorMessage)';
return 'ContactsViewState(contacts: $contacts, maxLimit: $maxLimit, dialCode: $dialCode, isLoading: $isLoading, isEditing: $isEditing, errorMessage: $errorMessage)';
}
@@ -45,7 +45,7 @@ abstract mixin class $ContactsViewStateCopyWith<$Res> {
factory $ContactsViewStateCopyWith(ContactsViewState value, $Res Function(ContactsViewState) _then) = _$ContactsViewStateCopyWithImpl;
@useResult
$Res call({
List<ContactEntity> contacts, String dialCode, bool isLoading, bool isEditing, String errorMessage
List<ContactEntity> contacts, int maxLimit, String dialCode, bool isLoading, bool isEditing, String errorMessage
});
@@ -62,10 +62,11 @@ class _$ContactsViewStateCopyWithImpl<$Res>
/// Create a copy of ContactsViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? contacts = null,Object? dialCode = null,Object? isLoading = null,Object? isEditing = null,Object? errorMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? contacts = null,Object? maxLimit = null,Object? dialCode = null,Object? isLoading = null,Object? isEditing = null,Object? errorMessage = null,}) {
return _then(_self.copyWith(
contacts: null == contacts ? _self.contacts : contacts // ignore: cast_nullable_to_non_nullable
as List<ContactEntity>,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as List<ContactEntity>,maxLimit: null == maxLimit ? _self.maxLimit : maxLimit // ignore: cast_nullable_to_non_nullable
as int,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isEditing: null == isEditing ? _self.isEditing : isEditing // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
@@ -154,10 +155,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<ContactEntity> contacts, String dialCode, bool isLoading, bool isEditing, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<ContactEntity> contacts, int maxLimit, String dialCode, bool isLoading, bool isEditing, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ContactsViewState() when $default != null:
return $default(_that.contacts,_that.dialCode,_that.isLoading,_that.isEditing,_that.errorMessage);case _:
return $default(_that.contacts,_that.maxLimit,_that.dialCode,_that.isLoading,_that.isEditing,_that.errorMessage);case _:
return orElse();
}
@@ -175,10 +176,10 @@ return $default(_that.contacts,_that.dialCode,_that.isLoading,_that.isEditing,_t
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<ContactEntity> contacts, String dialCode, bool isLoading, bool isEditing, String errorMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<ContactEntity> contacts, int maxLimit, String dialCode, bool isLoading, bool isEditing, String errorMessage) $default,) {final _that = this;
switch (_that) {
case _ContactsViewState():
return $default(_that.contacts,_that.dialCode,_that.isLoading,_that.isEditing,_that.errorMessage);case _:
return $default(_that.contacts,_that.maxLimit,_that.dialCode,_that.isLoading,_that.isEditing,_that.errorMessage);case _:
throw StateError('Unexpected subclass');
}
@@ -195,10 +196,10 @@ return $default(_that.contacts,_that.dialCode,_that.isLoading,_that.isEditing,_t
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<ContactEntity> contacts, String dialCode, bool isLoading, bool isEditing, String errorMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<ContactEntity> contacts, int maxLimit, String dialCode, bool isLoading, bool isEditing, String errorMessage)? $default,) {final _that = this;
switch (_that) {
case _ContactsViewState() when $default != null:
return $default(_that.contacts,_that.dialCode,_that.isLoading,_that.isEditing,_that.errorMessage);case _:
return $default(_that.contacts,_that.maxLimit,_that.dialCode,_that.isLoading,_that.isEditing,_that.errorMessage);case _:
return null;
}
@@ -210,7 +211,7 @@ return $default(_that.contacts,_that.dialCode,_that.isLoading,_that.isEditing,_t
class _ContactsViewState implements ContactsViewState {
const _ContactsViewState({final List<ContactEntity> contacts = const [], this.dialCode = '+34', this.isLoading = true, this.isEditing = false, this.errorMessage = ''}): _contacts = contacts;
const _ContactsViewState({final List<ContactEntity> contacts = const [], this.maxLimit = 10, this.dialCode = '+34', this.isLoading = true, this.isEditing = false, this.errorMessage = ''}): _contacts = contacts;
final List<ContactEntity> _contacts;
@@ -220,6 +221,7 @@ class _ContactsViewState implements ContactsViewState {
return EqualUnmodifiableListView(_contacts);
}
@override@JsonKey() final int maxLimit;
@override@JsonKey() final String dialCode;
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isEditing;
@@ -235,16 +237,16 @@ _$ContactsViewStateCopyWith<_ContactsViewState> get copyWith => __$ContactsViewS
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ContactsViewState&&const DeepCollectionEquality().equals(other._contacts, _contacts)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isEditing, isEditing) || other.isEditing == isEditing)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ContactsViewState&&const DeepCollectionEquality().equals(other._contacts, _contacts)&&(identical(other.maxLimit, maxLimit) || other.maxLimit == maxLimit)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isEditing, isEditing) || other.isEditing == isEditing)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_contacts),dialCode,isLoading,isEditing,errorMessage);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_contacts),maxLimit,dialCode,isLoading,isEditing,errorMessage);
@override
String toString() {
return 'ContactsViewState(contacts: $contacts, dialCode: $dialCode, isLoading: $isLoading, isEditing: $isEditing, errorMessage: $errorMessage)';
return 'ContactsViewState(contacts: $contacts, maxLimit: $maxLimit, dialCode: $dialCode, isLoading: $isLoading, isEditing: $isEditing, errorMessage: $errorMessage)';
}
@@ -255,7 +257,7 @@ abstract mixin class _$ContactsViewStateCopyWith<$Res> implements $ContactsViewS
factory _$ContactsViewStateCopyWith(_ContactsViewState value, $Res Function(_ContactsViewState) _then) = __$ContactsViewStateCopyWithImpl;
@override @useResult
$Res call({
List<ContactEntity> contacts, String dialCode, bool isLoading, bool isEditing, String errorMessage
List<ContactEntity> contacts, int maxLimit, String dialCode, bool isLoading, bool isEditing, String errorMessage
});
@@ -272,10 +274,11 @@ class __$ContactsViewStateCopyWithImpl<$Res>
/// Create a copy of ContactsViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? contacts = null,Object? dialCode = null,Object? isLoading = null,Object? isEditing = null,Object? errorMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? contacts = null,Object? maxLimit = null,Object? dialCode = null,Object? isLoading = null,Object? isEditing = null,Object? errorMessage = null,}) {
return _then(_ContactsViewState(
contacts: null == contacts ? _self._contacts : contacts // ignore: cast_nullable_to_non_nullable
as List<ContactEntity>,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as List<ContactEntity>,maxLimit: null == maxLimit ? _self.maxLimit : maxLimit // ignore: cast_nullable_to_non_nullable
as int,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isEditing: null == isEditing ? _self.isEditing : isEditing // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable

View File

@@ -51,14 +51,14 @@ class DeviceManagementScreen extends ConsumerWidget {
negativeIcon: true,
text: context.translate(I18n.contacts),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () {},
icon: SFIcons.doNotDisturbCircle,
negativeIcon: true,
text: context.translate(I18n.doNotDisturb),
),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () {},
// icon: SFIcons.doNotDisturbCircle,
// negativeIcon: true,
// text: context.translate(I18n.doNotDisturb),
// ),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
@@ -128,21 +128,21 @@ class DeviceManagementScreen extends ConsumerWidget {
icon: SFIcons.screenTime,
text: context.translate(I18n.appsUse),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () {},
icon: Icons.app_registration_sharp,
text: context.translate(I18n.appsSurveillance),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () {},
icon: SFIcons.friendsCircle,
negativeIcon: true,
text: context.translate(I18n.makeFriends),
),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () {},
// icon: Icons.app_registration_sharp,
// text: context.translate(I18n.appsSurveillance),
// ),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () {},
// icon: SFIcons.friendsCircle,
// negativeIcon: true,
// text: context.translate(I18n.makeFriends),
// ),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
@@ -152,6 +152,16 @@ class DeviceManagementScreen extends ConsumerWidget {
negativeIcon: true,
text: context.translate(I18n.locateSF),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () =>
navigationContract.pushTo(AppRoutes.backgroundImage),
icon: Icons.add_photo_alternate_outlined,
iconSize: SizeUtils.getByScreen(small: 32, big: 30),
negativeIcon: false,
text: context.translate(I18n.customBackground),
),
],
),
),

View File

@@ -32,10 +32,14 @@ class CallWatchViewModel extends Notifier<CallWatchViewState> {
state = state.copyWith(phone: text, errorMessage: '');
}
void updateDialCode(String code) {
state = state.copyWith(dialCode: code, errorMessage: '');
}
void call() async {
final phone = state.phone;
if (phone.isEmpty){
if (phone.isEmpty) {
state = state.copyWith(errorMessage: 'errorMessagePhoneIsEmpty');
return;
}
@@ -44,7 +48,8 @@ class CallWatchViewModel extends Notifier<CallWatchViewState> {
return;
}
final url = Uri(scheme: 'tel', path: phone);
final fullNumber = '${state.dialCode}$phone';
final url = Uri(scheme: 'tel', path: fullNumber);
if (await canLaunchUrl(url)) {
launchUrl(url);

View File

@@ -5,6 +5,7 @@ part 'call_watch_view_state.freezed.dart';
@freezed
abstract class CallWatchViewState with _$CallWatchViewState {
const factory CallWatchViewState({
@Default('+34') String dialCode,
@Default('') String phone,
@Default('') String errorMessage,
}) = _CallWatchViewState;

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$CallWatchViewState {
String get phone; String get errorMessage;
String get dialCode; String get phone; String get errorMessage;
/// Create a copy of CallWatchViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $CallWatchViewStateCopyWith<CallWatchViewState> get copyWith => _$CallWatchViewS
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallWatchViewState&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallWatchViewState&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,phone,errorMessage);
int get hashCode => Object.hash(runtimeType,dialCode,phone,errorMessage);
@override
String toString() {
return 'CallWatchViewState(phone: $phone, errorMessage: $errorMessage)';
return 'CallWatchViewState(dialCode: $dialCode, phone: $phone, errorMessage: $errorMessage)';
}
@@ -45,7 +45,7 @@ abstract mixin class $CallWatchViewStateCopyWith<$Res> {
factory $CallWatchViewStateCopyWith(CallWatchViewState value, $Res Function(CallWatchViewState) _then) = _$CallWatchViewStateCopyWithImpl;
@useResult
$Res call({
String phone, String errorMessage
String dialCode, String phone, String errorMessage
});
@@ -62,9 +62,10 @@ class _$CallWatchViewStateCopyWithImpl<$Res>
/// Create a copy of CallWatchViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? phone = null,Object? errorMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? dialCode = null,Object? phone = null,Object? errorMessage = null,}) {
return _then(_self.copyWith(
phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
@@ -151,10 +152,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String phone, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String dialCode, String phone, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CallWatchViewState() when $default != null:
return $default(_that.phone,_that.errorMessage);case _:
return $default(_that.dialCode,_that.phone,_that.errorMessage);case _:
return orElse();
}
@@ -172,10 +173,10 @@ return $default(_that.phone,_that.errorMessage);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String phone, String errorMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String dialCode, String phone, String errorMessage) $default,) {final _that = this;
switch (_that) {
case _CallWatchViewState():
return $default(_that.phone,_that.errorMessage);case _:
return $default(_that.dialCode,_that.phone,_that.errorMessage);case _:
throw StateError('Unexpected subclass');
}
@@ -192,10 +193,10 @@ return $default(_that.phone,_that.errorMessage);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String phone, String errorMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String dialCode, String phone, String errorMessage)? $default,) {final _that = this;
switch (_that) {
case _CallWatchViewState() when $default != null:
return $default(_that.phone,_that.errorMessage);case _:
return $default(_that.dialCode,_that.phone,_that.errorMessage);case _:
return null;
}
@@ -207,9 +208,10 @@ return $default(_that.phone,_that.errorMessage);case _:
class _CallWatchViewState implements CallWatchViewState {
const _CallWatchViewState({this.phone = '', this.errorMessage = ''});
const _CallWatchViewState({this.dialCode = '+34', this.phone = '', this.errorMessage = ''});
@override@JsonKey() final String dialCode;
@override@JsonKey() final String phone;
@override@JsonKey() final String errorMessage;
@@ -223,16 +225,16 @@ _$CallWatchViewStateCopyWith<_CallWatchViewState> get copyWith => __$CallWatchVi
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallWatchViewState&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallWatchViewState&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,phone,errorMessage);
int get hashCode => Object.hash(runtimeType,dialCode,phone,errorMessage);
@override
String toString() {
return 'CallWatchViewState(phone: $phone, errorMessage: $errorMessage)';
return 'CallWatchViewState(dialCode: $dialCode, phone: $phone, errorMessage: $errorMessage)';
}
@@ -243,7 +245,7 @@ abstract mixin class _$CallWatchViewStateCopyWith<$Res> implements $CallWatchVie
factory _$CallWatchViewStateCopyWith(_CallWatchViewState value, $Res Function(_CallWatchViewState) _then) = __$CallWatchViewStateCopyWithImpl;
@override @useResult
$Res call({
String phone, String errorMessage
String dialCode, String phone, String errorMessage
});
@@ -260,9 +262,10 @@ class __$CallWatchViewStateCopyWithImpl<$Res>
/// Create a copy of CallWatchViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? phone = null,Object? errorMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? dialCode = null,Object? phone = null,Object? errorMessage = null,}) {
return _then(_CallWatchViewState(
phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));

View File

@@ -6,6 +6,7 @@ import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
class CallWatchDialog extends ConsumerWidget {
const CallWatchDialog({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -17,7 +18,7 @@ class CallWatchDialog extends ConsumerWidget {
return Container(
padding: SizeUtils.getByScreen(
small: EdgeInsets.symmetric(horizontal: 26, vertical: 20),
big: EdgeInsets.symmetric(horizontal: 24, vertical: 18)
big: EdgeInsets.symmetric(horizontal: 24, vertical: 18),
),
width: SizeUtils.getByScreen(small: 390, big: 380),
height: SizeUtils.getByScreen(small: 250, big: 243),
@@ -25,24 +26,54 @@ class CallWatchDialog extends ConsumerWidget {
children: [
Stack(
children: [
Center(child: Text(context.translate(I18n.callWatch),
textAlign: TextAlign.center,
style: TextStyle(fontSize: SizeUtils.getByScreen(small: 19, big: 18)),
)),
Center(
child: Text(
context.translate(I18n.callWatch),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 19, big: 18),
),
),
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: (){Navigator.pop(context);},
icon: Icon(Icons.close, color: theme.getColorFor(ThemeCode.legacyPrimary)),
)
)
onPressed: () {
Navigator.pop(context);
},
icon: Icon(
Icons.close,
color: theme.getColorFor(ThemeCode.legacyPrimary),
),
),
),
],
),
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 7)),
CustomTextField(
controller: viewModel.phoneController,
hint: context.translate(I18n.mainContactPhoneNumber),
keyboardType: TextInputType.number,
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CountryPrefixPicker(
headerText: context.translate(I18n.selectYourCountry),
initialSelection: viewState.dialCode,
onChanged: (country) {
viewModel.updateDialCode(
country.dialCode ?? viewState.dialCode,
);
},
width: 80,
backgroundColor: Colors.transparent,
borderColor: Colors.black,
),
SizedBox(width: SizeUtils.getByScreen(small: 8, big: 7)),
Expanded(
child: CustomTextField(
controller: viewModel.phoneController,
hint: context.translate(I18n.mainContactPhoneNumber),
keyboardType: TextInputType.phone,
),
),
],
),
if (viewState.errorMessage.isNotEmpty)
Padding(
@@ -68,5 +99,4 @@ class CallWatchDialog extends ConsumerWidget {
),
);
}
}
}

View File

@@ -2,11 +2,13 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:lottie/lottie.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
import 'state/health_view_model.dart';
import 'state/health_view_state.dart';
import 'widgets/blood_pressure_tab.dart';
import 'widgets/health_summary_cards.dart';
import 'widgets/heart_rate_tab.dart';
@@ -55,13 +57,20 @@ class _HealthScreenState extends ConsumerState<HealthScreen>
final theme = ref.watch(themePortProvider);
final state = ref.watch(healthViewModelProvider);
final vm = ref.read(healthViewModelProvider.notifier);
final device = ref.watch(selectedDeviceProvider);
ref.listen(healthViewModelProvider.select((s) => s.errorMessage), (
ref.listen(healthViewModelProvider.select((s) => s.errorEvent), (
previous,
next,
) {
if (next.isNotEmpty) {
showTopSnackbar(context, message: next, type: MessageType.error);
if (next != null) {
final message = switch (next) {
HealthErrorEvent.loadData => context.translate(I18n.errorHealthData),
HealthErrorEvent.loadMore => context.translate(I18n.errorHealthData),
HealthErrorEvent.measure => context.translate(I18n.errorHealthMeasure),
HealthErrorEvent.heartRateFrequency => context.translate(I18n.errorHeartRateFrequency),
};
showTopSnackbar(context, message: message, type: MessageType.error);
}
});
@@ -70,7 +79,12 @@ class _HealthScreenState extends ConsumerState<HealthScreen>
title: context.translate(I18n.health),
body: state.isLoading
? const Center(child: CircularProgressIndicator())
: Column(
: state.isMeasuringCountdown
? _MeasuringOverlay(
remainingSeconds: state.measureRemainingSeconds,
theme: theme,
)
: Column(
children: [
HealthSummaryCards(
heartbeats: state.latestHeartbeats,
@@ -84,6 +98,31 @@ class _HealthScreenState extends ConsumerState<HealthScreen>
onCustomTap: () => _pickCustomRange(vm),
theme: theme,
),
if (device?.capabilities?.heartbeats != null &&
device!.capabilities!.heartbeats!.options.isNotEmpty)
_HeartRateFrequencySelector(
currentFrequency:
device.settings.frequencyHeartRate,
options:
device.capabilities!.heartbeats!.options,
theme: theme,
onChanged: (frequency) async {
final success =
await vm.updateHeartRateFrequency(
frequency: frequency);
if (!context.mounted) return;
if (success) {
showTopSnackbar(
context,
message: context.translate(
I18n.locationFrequencyUpdated,
args: {'minutes': '${frequency ~/ 60}'},
),
type: MessageType.success,
);
}
},
),
TabBar(
controller: _tabController,
labelColor: theme.getColorFor(ThemeCode.legacyPrimary),
@@ -141,18 +180,123 @@ class _SaveSection extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.read(themePortProvider);
final vm = ref.read(healthViewModelProvider.notifier);
final isMeasuring = ref.watch(
healthViewModelProvider.select((s) => s.isMeasuring),
);
final isCountdown = ref.watch(
healthViewModelProvider.select((s) => s.isMeasuringCountdown),
);
if (isCountdown) return const SizedBox.shrink();
return Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: PrimaryButton(
onPressed: () async {
await vm.measure();
},
text: context.translate(I18n.measure),
onPressed: isMeasuring ? null : () => vm.measure(),
text: isMeasuring
? '...'
: context.translate(I18n.measure),
color: theme.getColorFor(ThemeCode.legacyPrimary),
),
);
}
}
class _HeartRateFrequencySelector extends StatelessWidget {
final int currentFrequency;
final List<int> options;
final ThemePort theme;
final ValueChanged<int> onChanged;
const _HeartRateFrequencySelector({
required this.currentFrequency,
required this.options,
required this.theme,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Icon(Icons.timer_outlined, size: 18, color: primaryColor),
const SizedBox(width: 8),
Text(
context.translate(I18n.healthFrequency),
style: TextStyle(fontSize: 13, color: primaryColor),
),
const Spacer(),
...options.map(
(opt) => Padding(
padding: const EdgeInsets.only(left: 6),
child: ChoiceChip(
label: Text('${opt ~/ 60}m'),
selected: opt == currentFrequency,
selectedColor: primaryColor,
labelStyle: TextStyle(
fontSize: 12,
color: opt == currentFrequency ? Colors.white : primaryColor,
),
side: BorderSide(color: primaryColor),
onSelected: (_) => onChanged(opt),
visualDensity: VisualDensity.compact,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
),
],
),
);
}
}
class _MeasuringOverlay extends StatelessWidget {
final int remainingSeconds;
final ThemePort theme;
const _MeasuringOverlay({
required this.remainingSeconds,
required this.theme,
});
@override
Widget build(BuildContext context) {
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Lottie.asset(
'assets/shared/animations/fitness_tracker.json',
width: 200,
height: 200,
),
const SizedBox(height: 24),
Text(
context.translate(I18n.healthMeasuring),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: primaryColor,
),
),
const SizedBox(height: 12),
Text(
'${remainingSeconds}s',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
],
),
);
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
@@ -7,6 +9,18 @@ import '../../../../core/domain/repositories/health_repository.dart';
import '../../../../core/providers/health_repository_provider.dart';
import 'health_view_state.dart';
final _measureEndTimeProvider =
NotifierProvider<_MeasureEndTimeNotifier, DateTime?>(
_MeasureEndTimeNotifier.new,
);
class _MeasureEndTimeNotifier extends Notifier<DateTime?> {
@override
DateTime? build() => null;
void set(DateTime? value) => state = value;
}
final healthViewModelProvider =
NotifierProvider.autoDispose<HealthViewModel, HealthViewState>(
HealthViewModel.new,
@@ -15,24 +29,46 @@ final healthViewModelProvider =
class HealthViewModel extends Notifier<HealthViewState> {
late final HealthRepository _repository;
late final CommandsRepository _commandsRepository;
Timer? _measureTimer;
static const int _historyPageSize = 20;
static const int _measureDurationSeconds = 60;
@override
HealthViewState build() {
_repository = ref.read(healthRepositoryProvider);
_commandsRepository = ref.read(commandsRepositoryProvider);
_init();
_resumeMeasureIfNeeded();
return const HealthViewState();
}
void _resumeMeasureIfNeeded() {
final endTime = ref.read(_measureEndTimeProvider);
if (endTime == null) return;
final remaining = endTime.difference(DateTime.now()).inSeconds;
if (remaining <= 0) {
ref.read(_measureEndTimeProvider.notifier).set(null);
return;
}
Future.microtask(() {
state = state.copyWith(
isMeasuringCountdown: true,
measureRemainingSeconds: remaining,
);
_startCountdownTimer();
});
}
String? get _identificator =>
ref.read(selectedDeviceProvider)?.identificator;
Future<void> selectTimeRange(TimeRange range) async {
if (range == state.timeRange) return;
state = state.copyWith(timeRange: range, isLoading: true);
state = state.copyWith(timeRange: range, isLoading: true, errorEvent: null);
await _loadFilteredData();
}
@@ -42,6 +78,7 @@ class HealthViewModel extends Notifier<HealthViewState> {
customStart: start,
customEnd: end,
isLoading: true,
errorEvent: null,
);
await _loadFilteredData();
}
@@ -91,7 +128,7 @@ class HealthViewModel extends Notifier<HealthViewState> {
if (!ref.mounted) return;
state = state.copyWith(
isLoadingMore: false,
errorMessage: _formatError(e),
errorEvent: HealthErrorEvent.loadMore,
);
}
}
@@ -131,7 +168,7 @@ class HealthViewModel extends Notifier<HealthViewState> {
await _loadFilteredData();
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
state = state.copyWith(isLoading: false, errorEvent: HealthErrorEvent.loadData);
}
}
@@ -201,11 +238,11 @@ class HealthViewModel extends Notifier<HealthViewState> {
chartOxygens.map((e) => e.oxygen).toList(),
),
isLoading: false,
errorMessage: '',
errorEvent: null,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
state = state.copyWith(isLoading: false, errorEvent: HealthErrorEvent.loadData);
}
}
@@ -245,9 +282,26 @@ class HealthViewModel extends Notifier<HealthViewState> {
);
}
String _formatError(Object e) {
final msg = e.toString();
return msg.startsWith('Exception: ') ? msg.substring(11) : msg;
Future<bool> updateHeartRateFrequency({required int frequency}) async {
final device = ref.read(selectedDeviceProvider);
if (device == null) return false;
try {
final updatedSettings = device.settings.copyWith(
frequencyHeartRate: frequency,
);
await ref.read(deviceSettingsUpdateProvider).updateDeviceSettings(
device: device,
updatedSettings: updatedSettings,
);
if (!ref.mounted) return false;
ref.syncDeviceSettings(device, updatedSettings);
return true;
} catch (e) {
if (!ref.mounted) return false;
state = state.copyWith(errorEvent: HealthErrorEvent.heartRateFrequency);
return false;
}
}
Future<void> measure() async {
@@ -255,23 +309,54 @@ class HealthViewModel extends Notifier<HealthViewState> {
if (device == null) return;
try {
state = state.copyWith(isLoading: true);
state = state.copyWith(isMeasuring: true, errorEvent: null);
final request = SendCommandRequestModel(
device: device.identificator,
command: DeviceCommand.requestHeartRate,
);
await _commandsRepository.send(request: request);
if (!ref.mounted) return;
state = state.copyWith(isLoading: false);
ref.read(_measureEndTimeProvider.notifier).set(
DateTime.now().add(const Duration(seconds: _measureDurationSeconds)));
state = state.copyWith(
isMeasuring: false,
isMeasuringCountdown: true,
measureRemainingSeconds: _measureDurationSeconds,
);
_startCountdownTimer();
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isLoading: false,
errorMessage: _formatError(e),
isMeasuring: false,
errorEvent: HealthErrorEvent.measure,
);
}
}
void _startCountdownTimer() {
_measureTimer?.cancel();
_measureTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!ref.mounted) {
timer.cancel();
return;
}
final remaining = state.measureRemainingSeconds - 1;
if (remaining <= 0) {
timer.cancel();
_measureTimer = null;
ref.read(_measureEndTimeProvider.notifier).set(null);
state = state.copyWith(
isMeasuringCountdown: false,
measureRemainingSeconds: 0,
);
_init();
} else {
state = state.copyWith(measureRemainingSeconds: remaining);
}
});
}
}

View File

@@ -6,6 +6,13 @@ import '../../domain/entities/oxygen_entity.dart';
part 'health_view_state.freezed.dart';
enum HealthErrorEvent {
loadData,
loadMore,
measure,
heartRateFrequency,
}
@freezed
abstract class HealthStats with _$HealthStats {
const factory HealthStats({
@@ -33,6 +40,9 @@ abstract class HealthViewState with _$HealthViewState {
@Default(null) DateTime? customEnd,
@Default(true) bool isLoading,
@Default(false) bool isLoadingMore,
@Default('') String errorMessage,
@Default(false) bool isMeasuring,
@Default(false) bool isMeasuringCountdown,
@Default(0) int measureRemainingSeconds,
HealthErrorEvent? errorEvent,
}) = _HealthViewState;
}

View File

@@ -277,7 +277,7 @@ as int,
/// @nodoc
mixin _$HealthViewState {
List<HeartbeatEntity> get latestHeartbeats; List<OxygenEntity> get latestOxygens; List<HeartbeatEntity> get chartHeartbeats; List<OxygenEntity> get chartOxygens; List<HeartbeatEntity> get historyHeartbeats; List<OxygenEntity> get historyOxygens; int get currentHistoryPage; bool get hasMoreHistory; HealthStats get heartRateStats; HealthStats get oxygenStats; TimeRange get timeRange; DateTime? get customStart; DateTime? get customEnd; bool get isLoading; bool get isLoadingMore; String get errorMessage;
List<HeartbeatEntity> get latestHeartbeats; List<OxygenEntity> get latestOxygens; List<HeartbeatEntity> get chartHeartbeats; List<OxygenEntity> get chartOxygens; List<HeartbeatEntity> get historyHeartbeats; List<OxygenEntity> get historyOxygens; int get currentHistoryPage; bool get hasMoreHistory; HealthStats get heartRateStats; HealthStats get oxygenStats; TimeRange get timeRange; DateTime? get customStart; DateTime? get customEnd; bool get isLoading; bool get isLoadingMore; bool get isMeasuring; bool get isMeasuringCountdown; int get measureRemainingSeconds; HealthErrorEvent? get errorEvent;
/// Create a copy of HealthViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -288,16 +288,16 @@ $HealthViewStateCopyWith<HealthViewState> get copyWith => _$HealthViewStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is HealthViewState&&const DeepCollectionEquality().equals(other.latestHeartbeats, latestHeartbeats)&&const DeepCollectionEquality().equals(other.latestOxygens, latestOxygens)&&const DeepCollectionEquality().equals(other.chartHeartbeats, chartHeartbeats)&&const DeepCollectionEquality().equals(other.chartOxygens, chartOxygens)&&const DeepCollectionEquality().equals(other.historyHeartbeats, historyHeartbeats)&&const DeepCollectionEquality().equals(other.historyOxygens, historyOxygens)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.heartRateStats, heartRateStats) || other.heartRateStats == heartRateStats)&&(identical(other.oxygenStats, oxygenStats) || other.oxygenStats == oxygenStats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is HealthViewState&&const DeepCollectionEquality().equals(other.latestHeartbeats, latestHeartbeats)&&const DeepCollectionEquality().equals(other.latestOxygens, latestOxygens)&&const DeepCollectionEquality().equals(other.chartHeartbeats, chartHeartbeats)&&const DeepCollectionEquality().equals(other.chartOxygens, chartOxygens)&&const DeepCollectionEquality().equals(other.historyHeartbeats, historyHeartbeats)&&const DeepCollectionEquality().equals(other.historyOxygens, historyOxygens)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.heartRateStats, heartRateStats) || other.heartRateStats == heartRateStats)&&(identical(other.oxygenStats, oxygenStats) || other.oxygenStats == oxygenStats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.isMeasuring, isMeasuring) || other.isMeasuring == isMeasuring)&&(identical(other.isMeasuringCountdown, isMeasuringCountdown) || other.isMeasuringCountdown == isMeasuringCountdown)&&(identical(other.measureRemainingSeconds, measureRemainingSeconds) || other.measureRemainingSeconds == measureRemainingSeconds)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(latestHeartbeats),const DeepCollectionEquality().hash(latestOxygens),const DeepCollectionEquality().hash(chartHeartbeats),const DeepCollectionEquality().hash(chartOxygens),const DeepCollectionEquality().hash(historyHeartbeats),const DeepCollectionEquality().hash(historyOxygens),currentHistoryPage,hasMoreHistory,heartRateStats,oxygenStats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorMessage);
int get hashCode => Object.hashAll([runtimeType,const DeepCollectionEquality().hash(latestHeartbeats),const DeepCollectionEquality().hash(latestOxygens),const DeepCollectionEquality().hash(chartHeartbeats),const DeepCollectionEquality().hash(chartOxygens),const DeepCollectionEquality().hash(historyHeartbeats),const DeepCollectionEquality().hash(historyOxygens),currentHistoryPage,hasMoreHistory,heartRateStats,oxygenStats,timeRange,customStart,customEnd,isLoading,isLoadingMore,isMeasuring,isMeasuringCountdown,measureRemainingSeconds,errorEvent]);
@override
String toString() {
return 'HealthViewState(latestHeartbeats: $latestHeartbeats, latestOxygens: $latestOxygens, chartHeartbeats: $chartHeartbeats, chartOxygens: $chartOxygens, historyHeartbeats: $historyHeartbeats, historyOxygens: $historyOxygens, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, heartRateStats: $heartRateStats, oxygenStats: $oxygenStats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorMessage: $errorMessage)';
return 'HealthViewState(latestHeartbeats: $latestHeartbeats, latestOxygens: $latestOxygens, chartHeartbeats: $chartHeartbeats, chartOxygens: $chartOxygens, historyHeartbeats: $historyHeartbeats, historyOxygens: $historyOxygens, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, heartRateStats: $heartRateStats, oxygenStats: $oxygenStats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, isMeasuring: $isMeasuring, isMeasuringCountdown: $isMeasuringCountdown, measureRemainingSeconds: $measureRemainingSeconds, errorEvent: $errorEvent)';
}
@@ -308,7 +308,7 @@ abstract mixin class $HealthViewStateCopyWith<$Res> {
factory $HealthViewStateCopyWith(HealthViewState value, $Res Function(HealthViewState) _then) = _$HealthViewStateCopyWithImpl;
@useResult
$Res call({
List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage
List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, bool isMeasuring, bool isMeasuringCountdown, int measureRemainingSeconds, HealthErrorEvent? errorEvent
});
@@ -325,7 +325,7 @@ class _$HealthViewStateCopyWithImpl<$Res>
/// Create a copy of HealthViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? latestHeartbeats = null,Object? latestOxygens = null,Object? chartHeartbeats = null,Object? chartOxygens = null,Object? historyHeartbeats = null,Object? historyOxygens = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? heartRateStats = null,Object? oxygenStats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? latestHeartbeats = null,Object? latestOxygens = null,Object? chartHeartbeats = null,Object? chartOxygens = null,Object? historyHeartbeats = null,Object? historyOxygens = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? heartRateStats = null,Object? oxygenStats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? isMeasuring = null,Object? isMeasuringCountdown = null,Object? measureRemainingSeconds = null,Object? errorEvent = freezed,}) {
return _then(_self.copyWith(
latestHeartbeats: null == latestHeartbeats ? _self.latestHeartbeats : latestHeartbeats // ignore: cast_nullable_to_non_nullable
as List<HeartbeatEntity>,latestOxygens: null == latestOxygens ? _self.latestOxygens : latestOxygens // ignore: cast_nullable_to_non_nullable
@@ -342,8 +342,11 @@ as TimeRange,customStart: freezed == customStart ? _self.customStart : customSta
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,isMeasuring: null == isMeasuring ? _self.isMeasuring : isMeasuring // ignore: cast_nullable_to_non_nullable
as bool,isMeasuringCountdown: null == isMeasuringCountdown ? _self.isMeasuringCountdown : isMeasuringCountdown // ignore: cast_nullable_to_non_nullable
as bool,measureRemainingSeconds: null == measureRemainingSeconds ? _self.measureRemainingSeconds : measureRemainingSeconds // ignore: cast_nullable_to_non_nullable
as int,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as HealthErrorEvent?,
));
}
/// Create a copy of HealthViewState
@@ -446,10 +449,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, bool isMeasuring, bool isMeasuringCountdown, int measureRemainingSeconds, HealthErrorEvent? errorEvent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _HealthViewState() when $default != null:
return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats,_that.chartOxygens,_that.historyHeartbeats,_that.historyOxygens,_that.currentHistoryPage,_that.hasMoreHistory,_that.heartRateStats,_that.oxygenStats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorMessage);case _:
return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats,_that.chartOxygens,_that.historyHeartbeats,_that.historyOxygens,_that.currentHistoryPage,_that.hasMoreHistory,_that.heartRateStats,_that.oxygenStats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.isMeasuring,_that.isMeasuringCountdown,_that.measureRemainingSeconds,_that.errorEvent);case _:
return orElse();
}
@@ -467,10 +470,10 @@ return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, bool isMeasuring, bool isMeasuringCountdown, int measureRemainingSeconds, HealthErrorEvent? errorEvent) $default,) {final _that = this;
switch (_that) {
case _HealthViewState():
return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats,_that.chartOxygens,_that.historyHeartbeats,_that.historyOxygens,_that.currentHistoryPage,_that.hasMoreHistory,_that.heartRateStats,_that.oxygenStats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorMessage);case _:
return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats,_that.chartOxygens,_that.historyHeartbeats,_that.historyOxygens,_that.currentHistoryPage,_that.hasMoreHistory,_that.heartRateStats,_that.oxygenStats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.isMeasuring,_that.isMeasuringCountdown,_that.measureRemainingSeconds,_that.errorEvent);case _:
throw StateError('Unexpected subclass');
}
@@ -487,10 +490,10 @@ return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, bool isMeasuring, bool isMeasuringCountdown, int measureRemainingSeconds, HealthErrorEvent? errorEvent)? $default,) {final _that = this;
switch (_that) {
case _HealthViewState() when $default != null:
return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats,_that.chartOxygens,_that.historyHeartbeats,_that.historyOxygens,_that.currentHistoryPage,_that.hasMoreHistory,_that.heartRateStats,_that.oxygenStats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorMessage);case _:
return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats,_that.chartOxygens,_that.historyHeartbeats,_that.historyOxygens,_that.currentHistoryPage,_that.hasMoreHistory,_that.heartRateStats,_that.oxygenStats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.isMeasuring,_that.isMeasuringCountdown,_that.measureRemainingSeconds,_that.errorEvent);case _:
return null;
}
@@ -502,7 +505,7 @@ return $default(_that.latestHeartbeats,_that.latestOxygens,_that.chartHeartbeats
class _HealthViewState implements HealthViewState {
const _HealthViewState({final List<HeartbeatEntity> latestHeartbeats = const [], final List<OxygenEntity> latestOxygens = const [], final List<HeartbeatEntity> chartHeartbeats = const [], final List<OxygenEntity> chartOxygens = const [], final List<HeartbeatEntity> historyHeartbeats = const [], final List<OxygenEntity> historyOxygens = const [], this.currentHistoryPage = 1, this.hasMoreHistory = false, this.heartRateStats = const HealthStats(), this.oxygenStats = const HealthStats(), this.timeRange = TimeRange.today, this.customStart = null, this.customEnd = null, this.isLoading = true, this.isLoadingMore = false, this.errorMessage = ''}): _latestHeartbeats = latestHeartbeats,_latestOxygens = latestOxygens,_chartHeartbeats = chartHeartbeats,_chartOxygens = chartOxygens,_historyHeartbeats = historyHeartbeats,_historyOxygens = historyOxygens;
const _HealthViewState({final List<HeartbeatEntity> latestHeartbeats = const [], final List<OxygenEntity> latestOxygens = const [], final List<HeartbeatEntity> chartHeartbeats = const [], final List<OxygenEntity> chartOxygens = const [], final List<HeartbeatEntity> historyHeartbeats = const [], final List<OxygenEntity> historyOxygens = const [], this.currentHistoryPage = 1, this.hasMoreHistory = false, this.heartRateStats = const HealthStats(), this.oxygenStats = const HealthStats(), this.timeRange = TimeRange.today, this.customStart = null, this.customEnd = null, this.isLoading = true, this.isLoadingMore = false, this.isMeasuring = false, this.isMeasuringCountdown = false, this.measureRemainingSeconds = 0, this.errorEvent}): _latestHeartbeats = latestHeartbeats,_latestOxygens = latestOxygens,_chartHeartbeats = chartHeartbeats,_chartOxygens = chartOxygens,_historyHeartbeats = historyHeartbeats,_historyOxygens = historyOxygens;
final List<HeartbeatEntity> _latestHeartbeats;
@@ -556,7 +559,10 @@ class _HealthViewState implements HealthViewState {
@override@JsonKey() final DateTime? customEnd;
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isLoadingMore;
@override@JsonKey() final String errorMessage;
@override@JsonKey() final bool isMeasuring;
@override@JsonKey() final bool isMeasuringCountdown;
@override@JsonKey() final int measureRemainingSeconds;
@override final HealthErrorEvent? errorEvent;
/// Create a copy of HealthViewState
/// with the given fields replaced by the non-null parameter values.
@@ -568,16 +574,16 @@ _$HealthViewStateCopyWith<_HealthViewState> get copyWith => __$HealthViewStateCo
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _HealthViewState&&const DeepCollectionEquality().equals(other._latestHeartbeats, _latestHeartbeats)&&const DeepCollectionEquality().equals(other._latestOxygens, _latestOxygens)&&const DeepCollectionEquality().equals(other._chartHeartbeats, _chartHeartbeats)&&const DeepCollectionEquality().equals(other._chartOxygens, _chartOxygens)&&const DeepCollectionEquality().equals(other._historyHeartbeats, _historyHeartbeats)&&const DeepCollectionEquality().equals(other._historyOxygens, _historyOxygens)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.heartRateStats, heartRateStats) || other.heartRateStats == heartRateStats)&&(identical(other.oxygenStats, oxygenStats) || other.oxygenStats == oxygenStats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _HealthViewState&&const DeepCollectionEquality().equals(other._latestHeartbeats, _latestHeartbeats)&&const DeepCollectionEquality().equals(other._latestOxygens, _latestOxygens)&&const DeepCollectionEquality().equals(other._chartHeartbeats, _chartHeartbeats)&&const DeepCollectionEquality().equals(other._chartOxygens, _chartOxygens)&&const DeepCollectionEquality().equals(other._historyHeartbeats, _historyHeartbeats)&&const DeepCollectionEquality().equals(other._historyOxygens, _historyOxygens)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.heartRateStats, heartRateStats) || other.heartRateStats == heartRateStats)&&(identical(other.oxygenStats, oxygenStats) || other.oxygenStats == oxygenStats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.isMeasuring, isMeasuring) || other.isMeasuring == isMeasuring)&&(identical(other.isMeasuringCountdown, isMeasuringCountdown) || other.isMeasuringCountdown == isMeasuringCountdown)&&(identical(other.measureRemainingSeconds, measureRemainingSeconds) || other.measureRemainingSeconds == measureRemainingSeconds)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_latestHeartbeats),const DeepCollectionEquality().hash(_latestOxygens),const DeepCollectionEquality().hash(_chartHeartbeats),const DeepCollectionEquality().hash(_chartOxygens),const DeepCollectionEquality().hash(_historyHeartbeats),const DeepCollectionEquality().hash(_historyOxygens),currentHistoryPage,hasMoreHistory,heartRateStats,oxygenStats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorMessage);
int get hashCode => Object.hashAll([runtimeType,const DeepCollectionEquality().hash(_latestHeartbeats),const DeepCollectionEquality().hash(_latestOxygens),const DeepCollectionEquality().hash(_chartHeartbeats),const DeepCollectionEquality().hash(_chartOxygens),const DeepCollectionEquality().hash(_historyHeartbeats),const DeepCollectionEquality().hash(_historyOxygens),currentHistoryPage,hasMoreHistory,heartRateStats,oxygenStats,timeRange,customStart,customEnd,isLoading,isLoadingMore,isMeasuring,isMeasuringCountdown,measureRemainingSeconds,errorEvent]);
@override
String toString() {
return 'HealthViewState(latestHeartbeats: $latestHeartbeats, latestOxygens: $latestOxygens, chartHeartbeats: $chartHeartbeats, chartOxygens: $chartOxygens, historyHeartbeats: $historyHeartbeats, historyOxygens: $historyOxygens, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, heartRateStats: $heartRateStats, oxygenStats: $oxygenStats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorMessage: $errorMessage)';
return 'HealthViewState(latestHeartbeats: $latestHeartbeats, latestOxygens: $latestOxygens, chartHeartbeats: $chartHeartbeats, chartOxygens: $chartOxygens, historyHeartbeats: $historyHeartbeats, historyOxygens: $historyOxygens, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, heartRateStats: $heartRateStats, oxygenStats: $oxygenStats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, isMeasuring: $isMeasuring, isMeasuringCountdown: $isMeasuringCountdown, measureRemainingSeconds: $measureRemainingSeconds, errorEvent: $errorEvent)';
}
@@ -588,7 +594,7 @@ abstract mixin class _$HealthViewStateCopyWith<$Res> implements $HealthViewState
factory _$HealthViewStateCopyWith(_HealthViewState value, $Res Function(_HealthViewState) _then) = __$HealthViewStateCopyWithImpl;
@override @useResult
$Res call({
List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, String errorMessage
List<HeartbeatEntity> latestHeartbeats, List<OxygenEntity> latestOxygens, List<HeartbeatEntity> chartHeartbeats, List<OxygenEntity> chartOxygens, List<HeartbeatEntity> historyHeartbeats, List<OxygenEntity> historyOxygens, int currentHistoryPage, bool hasMoreHistory, HealthStats heartRateStats, HealthStats oxygenStats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, bool isMeasuring, bool isMeasuringCountdown, int measureRemainingSeconds, HealthErrorEvent? errorEvent
});
@@ -605,7 +611,7 @@ class __$HealthViewStateCopyWithImpl<$Res>
/// Create a copy of HealthViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? latestHeartbeats = null,Object? latestOxygens = null,Object? chartHeartbeats = null,Object? chartOxygens = null,Object? historyHeartbeats = null,Object? historyOxygens = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? heartRateStats = null,Object? oxygenStats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? latestHeartbeats = null,Object? latestOxygens = null,Object? chartHeartbeats = null,Object? chartOxygens = null,Object? historyHeartbeats = null,Object? historyOxygens = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? heartRateStats = null,Object? oxygenStats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? isMeasuring = null,Object? isMeasuringCountdown = null,Object? measureRemainingSeconds = null,Object? errorEvent = freezed,}) {
return _then(_HealthViewState(
latestHeartbeats: null == latestHeartbeats ? _self._latestHeartbeats : latestHeartbeats // ignore: cast_nullable_to_non_nullable
as List<HeartbeatEntity>,latestOxygens: null == latestOxygens ? _self._latestOxygens : latestOxygens // ignore: cast_nullable_to_non_nullable
@@ -622,8 +628,11 @@ as TimeRange,customStart: freezed == customStart ? _self.customStart : customSta
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,isMeasuring: null == isMeasuring ? _self.isMeasuring : isMeasuring // ignore: cast_nullable_to_non_nullable
as bool,isMeasuringCountdown: null == isMeasuringCountdown ? _self.isMeasuringCountdown : isMeasuringCountdown // ignore: cast_nullable_to_non_nullable
as bool,measureRemainingSeconds: null == measureRemainingSeconds ? _self.measureRemainingSeconds : measureRemainingSeconds // ignore: cast_nullable_to_non_nullable
as int,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as HealthErrorEvent?,
));
}

View File

@@ -46,7 +46,11 @@ class LocateDeviceViewModel extends Notifier<LocateDeviceViewState> {
}
}
void resetError() {
state = state.copyWith(errorMessage: '');
void reset() {
state = state.copyWith(
isLoading: false,
isComplete: false,
errorMessage: '',
);
}
}

View File

@@ -15,6 +15,20 @@ class LocateDeviceDialog extends ConsumerWidget {
final state = ref.watch(locateDeviceViewModelProvider);
final vm = ref.read(locateDeviceViewModelProvider.notifier);
ref.listen(
locateDeviceViewModelProvider.select((s) => s.isComplete),
(previous, next) {
if (next && !(previous ?? false)) {
Future.delayed(const Duration(seconds: 1), () {
if (context.mounted) {
Navigator.pop(context);
vm.reset();
}
});
}
},
);
return Container(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 32, big: 30),
@@ -44,7 +58,7 @@ class LocateDeviceDialog extends ConsumerWidget {
PrimaryButton(
onPressed: () {
Navigator.pop(context);
vm.resetError();
vm.reset();
},
text: context.translate(I18n.ok),
color: theme.getColorFor(ThemeCode.legacyPrimary),

View File

@@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'picture_entity.freezed.dart';
@@ -6,12 +8,13 @@ part 'picture_entity.freezed.dart';
abstract class PictureEntity with _$PictureEntity {
const factory PictureEntity({
required String id,
required String deviceIdentificator,
@Default('') String deviceIdentificator,
String? imgType,
String? timestamp,
required String fileId,
@Default('') String fileId,
String? fileName,
String? contentType,
required int createdAt,
@Default(0) int createdAt,
Uint8List? fileBytes,
}) = _PictureEntity;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PictureEntity {
String get id; String get deviceIdentificator; String? get imgType; String? get timestamp; String get fileId; String? get fileName; String? get contentType; int get createdAt;
String get id; String get deviceIdentificator; String? get imgType; String? get timestamp; String get fileId; String? get fileName; String? get contentType; int get createdAt; Uint8List? get fileBytes;
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $PictureEntityCopyWith<PictureEntity> get copyWith => _$PictureEntityCopyWithImp
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&const DeepCollectionEquality().equals(other.fileBytes, fileBytes));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt,const DeepCollectionEquality().hash(fileBytes));
@override
String toString() {
return 'PictureEntity(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
return 'PictureEntity(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt, fileBytes: $fileBytes)';
}
@@ -45,7 +45,7 @@ abstract mixin class $PictureEntityCopyWith<$Res> {
factory $PictureEntityCopyWith(PictureEntity value, $Res Function(PictureEntity) _then) = _$PictureEntityCopyWithImpl;
@useResult
$Res call({
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Uint8List? fileBytes
});
@@ -62,7 +62,7 @@ class _$PictureEntityCopyWithImpl<$Res>
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,Object? fileBytes = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
@@ -72,7 +72,8 @@ as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullab
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,
as int,fileBytes: freezed == fileBytes ? _self.fileBytes : fileBytes // ignore: cast_nullable_to_non_nullable
as Uint8List?,
));
}
@@ -157,10 +158,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Uint8List? fileBytes)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PictureEntity() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt,_that.fileBytes);case _:
return orElse();
}
@@ -178,10 +179,10 @@ return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Uint8List? fileBytes) $default,) {final _that = this;
switch (_that) {
case _PictureEntity():
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt,_that.fileBytes);case _:
throw StateError('Unexpected subclass');
}
@@ -198,10 +199,10 @@ return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Uint8List? fileBytes)? $default,) {final _that = this;
switch (_that) {
case _PictureEntity() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt,_that.fileBytes);case _:
return null;
}
@@ -213,17 +214,18 @@ return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp
class _PictureEntity implements PictureEntity {
const _PictureEntity({required this.id, required this.deviceIdentificator, this.imgType, this.timestamp, required this.fileId, this.fileName, this.contentType, required this.createdAt});
const _PictureEntity({required this.id, this.deviceIdentificator = '', this.imgType, this.timestamp, this.fileId = '', this.fileName, this.contentType, this.createdAt = 0, this.fileBytes});
@override final String id;
@override final String deviceIdentificator;
@override@JsonKey() final String deviceIdentificator;
@override final String? imgType;
@override final String? timestamp;
@override final String fileId;
@override@JsonKey() final String fileId;
@override final String? fileName;
@override final String? contentType;
@override final int createdAt;
@override@JsonKey() final int createdAt;
@override final Uint8List? fileBytes;
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@@ -235,16 +237,16 @@ _$PictureEntityCopyWith<_PictureEntity> get copyWith => __$PictureEntityCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&const DeepCollectionEquality().equals(other.fileBytes, fileBytes));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt,const DeepCollectionEquality().hash(fileBytes));
@override
String toString() {
return 'PictureEntity(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
return 'PictureEntity(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt, fileBytes: $fileBytes)';
}
@@ -255,7 +257,7 @@ abstract mixin class _$PictureEntityCopyWith<$Res> implements $PictureEntityCopy
factory _$PictureEntityCopyWith(_PictureEntity value, $Res Function(_PictureEntity) _then) = __$PictureEntityCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt, Uint8List? fileBytes
});
@@ -272,7 +274,7 @@ class __$PictureEntityCopyWithImpl<$Res>
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,Object? fileBytes = freezed,}) {
return _then(_PictureEntity(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
@@ -282,7 +284,8 @@ as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullab
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,
as int,fileBytes: freezed == fileBytes ? _self.fileBytes : fileBytes // ignore: cast_nullable_to_non_nullable
as Uint8List?,
));
}

View File

@@ -1,7 +1,9 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lottie/lottie.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_model.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_state.dart';
import 'package:device_management/src/features/remote_connection/presentation/widgets/show_picture_dialog.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
@@ -17,10 +19,33 @@ class RemoteCameraScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
ref.listen(
remoteConnectionViewModelProvider.select((s) => s.successMessage),
(_, successMessage) {
if (successMessage.isNotEmpty) {
showTopSnackbar(context, message: context.translate(successMessage), type: MessageType.success);
remoteConnectionViewModelProvider.select((s) => s.errorEvent),
(_, next) {
if (next != null) {
final message = switch (next) {
RemoteConnectionErrorEvent.takePicture =>
context.translate(I18n.errorTakePicture),
RemoteConnectionErrorEvent.fetchPhotos =>
context.translate(I18n.errorFetchPhotos),
RemoteConnectionErrorEvent.call =>
context.translate(I18n.errorCall),
RemoteConnectionErrorEvent.invalidPhone =>
context.translate(I18n.errorMessagePhoneIsInvalid),
};
showTopSnackbar(context, message: message, type: MessageType.error);
}
},
);
ref.listen(
remoteConnectionViewModelProvider.select((s) => s.successEvent),
(_, next) {
if (next != null) {
final message = switch (next) {
RemoteConnectionSuccessEvent.photoTaken =>
context.translate(I18n.photoTaken),
};
showTopSnackbar(context, message: message, type: MessageType.success);
ref.read(remoteConnectionViewModelProvider.notifier).clearSuccess();
}
},
@@ -28,20 +53,38 @@ class RemoteCameraScreen extends ConsumerWidget {
final theme = ref.watch(themePortProvider);
final isLoadingPictures = ref.watch(
remoteConnectionViewModelProvider.select((s)=>s.isLoadingPictures)
final isLoading = ref.watch(
remoteConnectionViewModelProvider.select((s) => s.isLoadingPictures),
);
final isTakingPicture = ref.watch(
remoteConnectionViewModelProvider.select((s)=>s.isTakingPicture)
final isTaking = ref.watch(
remoteConnectionViewModelProvider.select((s) => s.isTakingPicture),
);
final isWaiting = ref.watch(
remoteConnectionViewModelProvider.select((s) => s.isWaitingForPhoto),
);
final countdown = ref.watch(
remoteConnectionViewModelProvider.select((s) => s.photoCountdown),
);
Widget body;
if (isLoading || isTaking) {
body = const Center(child: CircularProgressIndicator());
} else if (isWaiting) {
body = _WaitingForPhotoOverlay(
remainingSeconds: countdown,
theme: theme,
);
} else {
body = const _GallerySection();
}
return LegacyPageLayout(
theme: theme,
title: context.translate(I18n.remoteCamera),
body: isLoadingPictures || isTakingPicture
? const Center(child: CircularProgressIndicator())
: const _GallerySection(),
footer: _TakePictureSection(),
body: body,
footer: isWaiting
? const SizedBox.shrink()
: const _TakePictureSection(),
);
}
}
@@ -87,12 +130,12 @@ class _GallerySection extends ConsumerWidget {
color: theme.getColorFor(ThemeCode.textTertiary)
))
),
child: Image.network(
pictures[index].fileId,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) =>
const Icon(Icons.broken_image, color: Colors.grey),
)
child: pictures[index].fileBytes != null
? Image.memory(
pictures[index].fileBytes!,
fit: BoxFit.cover,
)
: const Icon(Icons.broken_image, color: Colors.grey),
)
)
),
@@ -124,5 +167,53 @@ class _TakePictureSection extends ConsumerWidget {
),
);
}
}
class _WaitingForPhotoOverlay extends StatelessWidget {
static const String _animationAsset =
'assets/shared/animations/shooting_photo.json';
final int remainingSeconds;
final ThemePort theme;
const _WaitingForPhotoOverlay({
required this.remainingSeconds,
required this.theme,
});
@override
Widget build(BuildContext context) {
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Lottie.asset(
_animationAsset,
width: 200,
height: 200,
),
const SizedBox(height: 24),
Text(
context.translate(I18n.takingPhoto),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: primaryColor,
),
),
const SizedBox(height: 12),
Text(
'${remainingSeconds}s',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
],
),
);
}
}

View File

@@ -1,10 +1,11 @@
import 'dart:async';
import 'package:device_management/src/core/providers/pictures_repository_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_state.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_localizations/sf_localizations.dart';
import '../../../../core/domain/repositories/pictures_repository.dart';
@@ -18,8 +19,10 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
late final TextEditingController phoneController;
late final CommandsRepository _commandsRepository;
late final PicturesRepository _picturesRepository;
Timer? _photoTimer;
static final RegExp _phoneRegex = RegExp(r'^\+?\d{6,15}$');
static const int _photoWaitSeconds = 5;
@override
RemoteConnectionViewState build() {
@@ -60,12 +63,12 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
final text = phoneController.text;
if (text == state.phone) return;
state = state.copyWith(phone: text, errorMessage: '');
state = state.copyWith(phone: text, errorEvent: null);
}
void updateDialCode(String value) {
if (value == state.dialCode) return;
state = state.copyWith(dialCode: value, errorMessage: '');
state = state.copyWith(dialCode: value, errorEvent: null);
}
void prevPicture() {
@@ -95,18 +98,17 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
}
void clearSuccess() {
state = state.copyWith(
successMessage: '',
);
state = state.copyWith(successEvent: null);
}
Future<void> takePicture() async {
try {
state = state.copyWith(
isTakingPicture: true,
successMessage: '',
successEvent: null,
errorEvent: null,
);
final request = SendCommandRequestModel(
device: state.deviceId,
command: DeviceCommand.requestPhoto,
@@ -115,21 +117,59 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
await _commandsRepository.send(request: request);
if (!ref.mounted) return;
state = state.copyWith(
isTakingPicture: false,
isWaitingForPhoto: true,
photoCountdown: _photoWaitSeconds,
);
_startPhotoCountdown();
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isTakingPicture: false,
errorEvent: RemoteConnectionErrorEvent.takePicture,
);
}
}
void _startPhotoCountdown() {
_photoTimer?.cancel();
_photoTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!ref.mounted) {
timer.cancel();
return;
}
final remaining = state.photoCountdown - 1;
if (remaining <= 0) {
timer.cancel();
_photoTimer = null;
_fetchPhotosAfterCapture();
} else {
state = state.copyWith(photoCountdown: remaining);
}
});
}
Future<void> _fetchPhotosAfterCapture() async {
try {
final pictures = await _picturesRepository.getPictures(
deviceId: state.deviceId,
);
if (!ref.mounted) return;
state = state.copyWith(
isTakingPicture: false,
isWaitingForPhoto: false,
photoCountdown: 0,
pictures: pictures,
successMessage: I18n.photoTaken,
successEvent: RemoteConnectionSuccessEvent.photoTaken,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isTakingPicture: false,
errorMessage: e.toString(),
isWaitingForPhoto: false,
photoCountdown: 0,
errorEvent: RemoteConnectionErrorEvent.fetchPhotos,
);
}
}
@@ -137,12 +177,8 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
Future<void> call() async {
final phone = phoneController.text;
final dialCode = state.dialCode;
if (phone.isEmpty){
state = state.copyWith(errorMessage: I18n.errorMessagePhoneIsInvalid);
return;
}
if (!_phoneRegex.hasMatch(phone)) {
state = state.copyWith(errorMessage: I18n.errorMessagePhoneIsInvalid);
if (phone.isEmpty || !_phoneRegex.hasMatch(phone)) {
state = state.copyWith(errorEvent: RemoteConnectionErrorEvent.invalidPhone);
return;
}
@@ -163,7 +199,7 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
if (!ref.mounted) return;
state = state.copyWith(
isCalling: false,
errorMessage: e.toString(),
errorEvent: RemoteConnectionErrorEvent.call,
);
}
}

View File

@@ -3,6 +3,9 @@ import 'package:device_management/src/features/remote_connection/domain/entities
part 'remote_connection_view_state.freezed.dart';
enum RemoteConnectionErrorEvent { takePicture, fetchPhotos, call, invalidPhone }
enum RemoteConnectionSuccessEvent { photoTaken }
@freezed
abstract class RemoteConnectionViewState with _$RemoteConnectionViewState {
const factory RemoteConnectionViewState({
@@ -13,8 +16,10 @@ abstract class RemoteConnectionViewState with _$RemoteConnectionViewState {
@Default(0) int pictureIndex,
@Default(true) bool isLoadingPictures,
@Default(false) bool isTakingPicture,
@Default(false) bool isWaitingForPhoto,
@Default(0) int photoCountdown,
@Default(false) bool isCalling,
@Default('') String errorMessage,
@Default('') String successMessage
RemoteConnectionErrorEvent? errorEvent,
RemoteConnectionSuccessEvent? successEvent,
}) = _RemoteConnectionViewState;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$RemoteConnectionViewState {
String get deviceId; String get dialCode; String get phone; List<PictureEntity> get pictures; int get pictureIndex; bool get isLoadingPictures; bool get isTakingPicture; bool get isCalling; String get errorMessage; String get successMessage;
String get deviceId; String get dialCode; String get phone; List<PictureEntity> get pictures; int get pictureIndex; bool get isLoadingPictures; bool get isTakingPicture; bool get isWaitingForPhoto; int get photoCountdown; bool get isCalling; RemoteConnectionErrorEvent? get errorEvent; RemoteConnectionSuccessEvent? get successEvent;
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $RemoteConnectionViewStateCopyWith<RemoteConnectionViewState> get copyWith => _$
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteConnectionViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other.pictures, pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteConnectionViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other.pictures, pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isWaitingForPhoto, isWaitingForPhoto) || other.isWaitingForPhoto == isWaitingForPhoto)&&(identical(other.photoCountdown, photoCountdown) || other.photoCountdown == photoCountdown)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent));
}
@override
int get hashCode => Object.hash(runtimeType,deviceId,dialCode,phone,const DeepCollectionEquality().hash(pictures),pictureIndex,isLoadingPictures,isTakingPicture,isCalling,errorMessage,successMessage);
int get hashCode => Object.hash(runtimeType,deviceId,dialCode,phone,const DeepCollectionEquality().hash(pictures),pictureIndex,isLoadingPictures,isTakingPicture,isWaitingForPhoto,photoCountdown,isCalling,errorEvent,successEvent);
@override
String toString() {
return 'RemoteConnectionViewState(deviceId: $deviceId, dialCode: $dialCode, phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isCalling: $isCalling, errorMessage: $errorMessage, successMessage: $successMessage)';
return 'RemoteConnectionViewState(deviceId: $deviceId, dialCode: $dialCode, phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isWaitingForPhoto: $isWaitingForPhoto, photoCountdown: $photoCountdown, isCalling: $isCalling, errorEvent: $errorEvent, successEvent: $successEvent)';
}
@@ -45,7 +45,7 @@ abstract mixin class $RemoteConnectionViewStateCopyWith<$Res> {
factory $RemoteConnectionViewStateCopyWith(RemoteConnectionViewState value, $Res Function(RemoteConnectionViewState) _then) = _$RemoteConnectionViewStateCopyWithImpl;
@useResult
$Res call({
String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage
String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isWaitingForPhoto, int photoCountdown, bool isCalling, RemoteConnectionErrorEvent? errorEvent, RemoteConnectionSuccessEvent? successEvent
});
@@ -62,7 +62,7 @@ class _$RemoteConnectionViewStateCopyWithImpl<$Res>
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? dialCode = null,Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isCalling = null,Object? errorMessage = null,Object? successMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? dialCode = null,Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isWaitingForPhoto = null,Object? photoCountdown = null,Object? isCalling = null,Object? errorEvent = freezed,Object? successEvent = freezed,}) {
return _then(_self.copyWith(
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
@@ -71,10 +71,12 @@ as String,pictures: null == pictures ? _self.pictures : pictures // ignore: cast
as List<PictureEntity>,pictureIndex: null == pictureIndex ? _self.pictureIndex : pictureIndex // ignore: cast_nullable_to_non_nullable
as int,isLoadingPictures: null == isLoadingPictures ? _self.isLoadingPictures : isLoadingPictures // ignore: cast_nullable_to_non_nullable
as bool,isTakingPicture: null == isTakingPicture ? _self.isTakingPicture : isTakingPicture // ignore: cast_nullable_to_non_nullable
as bool,isCalling: null == isCalling ? _self.isCalling : isCalling // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,isWaitingForPhoto: null == isWaitingForPhoto ? _self.isWaitingForPhoto : isWaitingForPhoto // ignore: cast_nullable_to_non_nullable
as bool,photoCountdown: null == photoCountdown ? _self.photoCountdown : photoCountdown // ignore: cast_nullable_to_non_nullable
as int,isCalling: null == isCalling ? _self.isCalling : isCalling // ignore: cast_nullable_to_non_nullable
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as RemoteConnectionErrorEvent?,successEvent: freezed == successEvent ? _self.successEvent : successEvent // ignore: cast_nullable_to_non_nullable
as RemoteConnectionSuccessEvent?,
));
}
@@ -159,10 +161,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isWaitingForPhoto, int photoCountdown, bool isCalling, RemoteConnectionErrorEvent? errorEvent, RemoteConnectionSuccessEvent? successEvent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _RemoteConnectionViewState() when $default != null:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage,_that.successMessage);case _:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isWaitingForPhoto,_that.photoCountdown,_that.isCalling,_that.errorEvent,_that.successEvent);case _:
return orElse();
}
@@ -180,10 +182,10 @@ return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.p
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isWaitingForPhoto, int photoCountdown, bool isCalling, RemoteConnectionErrorEvent? errorEvent, RemoteConnectionSuccessEvent? successEvent) $default,) {final _that = this;
switch (_that) {
case _RemoteConnectionViewState():
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage,_that.successMessage);case _:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isWaitingForPhoto,_that.photoCountdown,_that.isCalling,_that.errorEvent,_that.successEvent);case _:
throw StateError('Unexpected subclass');
}
@@ -200,10 +202,10 @@ return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.p
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isWaitingForPhoto, int photoCountdown, bool isCalling, RemoteConnectionErrorEvent? errorEvent, RemoteConnectionSuccessEvent? successEvent)? $default,) {final _that = this;
switch (_that) {
case _RemoteConnectionViewState() when $default != null:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage,_that.successMessage);case _:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isWaitingForPhoto,_that.photoCountdown,_that.isCalling,_that.errorEvent,_that.successEvent);case _:
return null;
}
@@ -215,7 +217,7 @@ return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.p
class _RemoteConnectionViewState implements RemoteConnectionViewState {
const _RemoteConnectionViewState({this.deviceId = '', this.dialCode = '+34', this.phone = '', final List<PictureEntity> pictures = const [], this.pictureIndex = 0, this.isLoadingPictures = true, this.isTakingPicture = false, this.isCalling = false, this.errorMessage = '', this.successMessage = ''}): _pictures = pictures;
const _RemoteConnectionViewState({this.deviceId = '', this.dialCode = '+34', this.phone = '', final List<PictureEntity> pictures = const [], this.pictureIndex = 0, this.isLoadingPictures = true, this.isTakingPicture = false, this.isWaitingForPhoto = false, this.photoCountdown = 0, this.isCalling = false, this.errorEvent, this.successEvent}): _pictures = pictures;
@override@JsonKey() final String deviceId;
@@ -231,9 +233,11 @@ class _RemoteConnectionViewState implements RemoteConnectionViewState {
@override@JsonKey() final int pictureIndex;
@override@JsonKey() final bool isLoadingPictures;
@override@JsonKey() final bool isTakingPicture;
@override@JsonKey() final bool isWaitingForPhoto;
@override@JsonKey() final int photoCountdown;
@override@JsonKey() final bool isCalling;
@override@JsonKey() final String errorMessage;
@override@JsonKey() final String successMessage;
@override final RemoteConnectionErrorEvent? errorEvent;
@override final RemoteConnectionSuccessEvent? successEvent;
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@@ -245,16 +249,16 @@ _$RemoteConnectionViewStateCopyWith<_RemoteConnectionViewState> get copyWith =>
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteConnectionViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._pictures, _pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteConnectionViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._pictures, _pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isWaitingForPhoto, isWaitingForPhoto) || other.isWaitingForPhoto == isWaitingForPhoto)&&(identical(other.photoCountdown, photoCountdown) || other.photoCountdown == photoCountdown)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent));
}
@override
int get hashCode => Object.hash(runtimeType,deviceId,dialCode,phone,const DeepCollectionEquality().hash(_pictures),pictureIndex,isLoadingPictures,isTakingPicture,isCalling,errorMessage,successMessage);
int get hashCode => Object.hash(runtimeType,deviceId,dialCode,phone,const DeepCollectionEquality().hash(_pictures),pictureIndex,isLoadingPictures,isTakingPicture,isWaitingForPhoto,photoCountdown,isCalling,errorEvent,successEvent);
@override
String toString() {
return 'RemoteConnectionViewState(deviceId: $deviceId, dialCode: $dialCode, phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isCalling: $isCalling, errorMessage: $errorMessage, successMessage: $successMessage)';
return 'RemoteConnectionViewState(deviceId: $deviceId, dialCode: $dialCode, phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isWaitingForPhoto: $isWaitingForPhoto, photoCountdown: $photoCountdown, isCalling: $isCalling, errorEvent: $errorEvent, successEvent: $successEvent)';
}
@@ -265,7 +269,7 @@ abstract mixin class _$RemoteConnectionViewStateCopyWith<$Res> implements $Remot
factory _$RemoteConnectionViewStateCopyWith(_RemoteConnectionViewState value, $Res Function(_RemoteConnectionViewState) _then) = __$RemoteConnectionViewStateCopyWithImpl;
@override @useResult
$Res call({
String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage
String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isWaitingForPhoto, int photoCountdown, bool isCalling, RemoteConnectionErrorEvent? errorEvent, RemoteConnectionSuccessEvent? successEvent
});
@@ -282,7 +286,7 @@ class __$RemoteConnectionViewStateCopyWithImpl<$Res>
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? dialCode = null,Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isCalling = null,Object? errorMessage = null,Object? successMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? dialCode = null,Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isWaitingForPhoto = null,Object? photoCountdown = null,Object? isCalling = null,Object? errorEvent = freezed,Object? successEvent = freezed,}) {
return _then(_RemoteConnectionViewState(
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
@@ -291,10 +295,12 @@ as String,pictures: null == pictures ? _self._pictures : pictures // ignore: cas
as List<PictureEntity>,pictureIndex: null == pictureIndex ? _self.pictureIndex : pictureIndex // ignore: cast_nullable_to_non_nullable
as int,isLoadingPictures: null == isLoadingPictures ? _self.isLoadingPictures : isLoadingPictures // ignore: cast_nullable_to_non_nullable
as bool,isTakingPicture: null == isTakingPicture ? _self.isTakingPicture : isTakingPicture // ignore: cast_nullable_to_non_nullable
as bool,isCalling: null == isCalling ? _self.isCalling : isCalling // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,isWaitingForPhoto: null == isWaitingForPhoto ? _self.isWaitingForPhoto : isWaitingForPhoto // ignore: cast_nullable_to_non_nullable
as bool,photoCountdown: null == photoCountdown ? _self.photoCountdown : photoCountdown // ignore: cast_nullable_to_non_nullable
as int,isCalling: null == isCalling ? _self.isCalling : isCalling // ignore: cast_nullable_to_non_nullable
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as RemoteConnectionErrorEvent?,successEvent: freezed == successEvent ? _self.successEvent : successEvent // ignore: cast_nullable_to_non_nullable
as RemoteConnectionSuccessEvent?,
));
}

View File

@@ -45,7 +45,7 @@ class ShowPictureDialog extends ConsumerWidget {
child: Column(
children: [
Expanded(
child: _PictureSection(fileId: picture.fileId),
child: _PictureSection(picture: picture),
),
_MetadataSection(picture: picture),
_ControlsSection(
@@ -59,24 +59,22 @@ class ShowPictureDialog extends ConsumerWidget {
}
class _PictureSection extends StatelessWidget {
final String fileId;
final PictureEntity picture;
const _PictureSection({required this.fileId});
const _PictureSection({required this.picture});
@override
Widget build(BuildContext context) {
return Center(
child: Image.network(
fileId,
fit: BoxFit.contain,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.broken_image, size: 64, color: Colors.grey);
},
),
if (picture.fileBytes != null) {
return Center(
child: Image.memory(
picture.fileBytes!,
fit: BoxFit.contain,
),
);
}
return const Center(
child: Icon(Icons.broken_image, size: 64, color: Colors.grey),
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_model.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_state.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -13,26 +14,39 @@ class SpyCallDialog extends ConsumerWidget {
final theme = ref.watch(themePortProvider);
final vm = ref.read(remoteConnectionViewModelProvider.notifier);
ref.listen(remoteConnectionViewModelProvider.select((s) => s.errorMessage),
(_, msg) {
if (msg.isNotEmpty) {
showTopSnackbar(context,
message: context.translate(msg), type: MessageType.error);
}
});
ref.listen(remoteConnectionViewModelProvider.select((s) => s.isCalling),
(prev, isCalling) {
if (prev == true && !isCalling) {
final error = ref.read(remoteConnectionViewModelProvider).errorMessage;
if (error.isEmpty && context.mounted) {
Navigator.pop(context);
showTopSnackbar(context,
message: context.translate(I18n.remoteListening),
type: MessageType.success);
ref.listen(
remoteConnectionViewModelProvider.select((s) => s.errorEvent),
(_, next) {
if (next != null) {
final message = switch (next) {
RemoteConnectionErrorEvent.invalidPhone =>
context.translate(I18n.errorMessagePhoneIsInvalid),
RemoteConnectionErrorEvent.call =>
context.translate(I18n.errorCall),
RemoteConnectionErrorEvent.takePicture =>
context.translate(I18n.errorTakePicture),
RemoteConnectionErrorEvent.fetchPhotos =>
context.translate(I18n.errorFetchPhotos),
};
showTopSnackbar(context, message: message, type: MessageType.error);
}
}
});
},
);
ref.listen(
remoteConnectionViewModelProvider.select((s) => s.isCalling),
(prev, isCalling) {
if (prev == true && !isCalling) {
final error = ref.read(remoteConnectionViewModelProvider).errorEvent;
if (error == null && context.mounted) {
Navigator.pop(context);
showTopSnackbar(context,
message: context.translate(I18n.remoteListening),
type: MessageType.success);
}
}
},
);
return Container(
padding: SizeUtils.getByScreen(

View File

@@ -1,9 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import 'device_update_datasource.dart';
final deviceUpdateDatasourceProvider = Provider<DeviceUpdateDatasource>((ref) {
return DeviceUpdateDatasource(GetIt.I<QuestiaRepository>());
});

View File

@@ -1,8 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_shared/sf_shared.dart';
import '../../data/device_update_datasource.dart';
import '../../data/device_update_datasource_provider.dart';
import 'volume_control_view_state.dart';
final volumeControlViewModelProvider =
@@ -11,11 +10,11 @@ final volumeControlViewModelProvider =
);
class VolumeControlViewModel extends Notifier<VolumeControlViewState> {
late final DeviceUpdateDatasource _datasource;
late final DeviceSettingsUpdateDatasource _datasource;
@override
VolumeControlViewState build() {
_datasource = ref.read(deviceUpdateDatasourceProvider);
_datasource = ref.read(deviceSettingsUpdateProvider);
Future.microtask(() => _load());
return const VolumeControlViewState();
}
@@ -25,14 +24,14 @@ class VolumeControlViewModel extends Notifier<VolumeControlViewState> {
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
final volume = device.settings['volume'] as Map<String, dynamic>? ?? {};
final volume = device.settings.volume;
state = state.copyWith(
isLoading: false,
device: device,
media: (volume['media'] as num?)?.toInt() ?? 50,
ringtone: (volume['ringtone'] as num?)?.toInt() ?? 50,
alarm: (volume['alarm'] as num?)?.toInt() ?? 50,
media: volume.media,
ringtone: volume.ringtone,
alarm: volume.alarm,
);
} catch (e) {
if (!ref.mounted) return;
@@ -51,19 +50,21 @@ class VolumeControlViewModel extends Notifier<VolumeControlViewState> {
try {
state = state.copyWith(isLoading: true, isComplete: false, errorMessage: '');
final settings = Map<String, dynamic>.from(device.settings);
settings['volume'] = {
'media': state.media,
'ringtone': state.ringtone,
'alarm': state.alarm,
};
final updatedSettings = device.settings.copyWith(
volume: DeviceVolumeEntity(
media: state.media,
ringtone: state.ringtone,
alarm: state.alarm,
),
);
await _datasource.updateDeviceSettings(
device: device,
updatedSettings: settings,
updatedSettings: updatedSettings,
);
if (!ref.mounted) return;
ref.syncDeviceSettings(device, updatedSettings);
state = state.copyWith(isLoading: false, isComplete: true);
} catch (e) {
if (!ref.mounted) return;

View File

@@ -215,6 +215,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.15.0"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
url: "https://pub.dev"
source: hosted
version: "0.3.5+2"
crypto:
dependency: transitive
description:
@@ -326,6 +334,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
url: "https://pub.dev"
source: hosted
version: "0.9.5"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
fixnum:
dependency: transitive
description:
@@ -371,6 +411,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.2.2"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
url: "https://pub.dev"
source: hosted
version: "2.0.33"
flutter_riverpod:
dependency: "direct main"
description:
@@ -498,6 +546,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image_picker:
dependency: "direct main"
description:
name: image_picker
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156
url: "https://pub.dev"
source: hosted
version: "0.8.13+14"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad"
url: "https://pub.dev"
source: hosted
version: "0.8.13+3"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
url: "https://pub.dev"
source: hosted
version: "0.2.2+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
url: "https://pub.dev"
source: hosted
version: "2.11.1"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
url: "https://pub.dev"
source: hosted
version: "0.2.2"
intl:
dependency: transitive
description:
@@ -634,7 +746,7 @@ packages:
source: hosted
version: "1.3.0"
lottie:
dependency: transitive
dependency: "direct main"
description:
name: lottie
sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95"

View File

@@ -56,6 +56,8 @@ dependencies:
uuid: ^4.5.1
flutter_contacts: ^1.1.9+2
fl_chart: ^1.1.1
lottie: ^3.3.1
image_picker: ^1.2.1
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
@@ -75,12 +77,6 @@ flutter:
# the material Icons class.
uses-material-design: true
# To add Flutter specific assets to your application, add an assets section,
# like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images

View File

@@ -6,6 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:sf_localizations/sf_localizations.dart';
import '../widgets/activation_code_dialog.dart';
class LegacyScanWatchStepScreen extends ConsumerWidget {
const LegacyScanWatchStepScreen({super.key});
@@ -43,6 +45,7 @@ class LegacyScanWatchStepScreen extends ConsumerWidget {
);
if (result == null || result.isEmpty) return;
vm.onWatchQrScanned(result);
showActivationCodeDialog(context);
},
child: Container(
width: 170,

View File

@@ -15,6 +15,7 @@ mixin SignUpFormValidation {
String emailErrorFor(String value) {
final email = value.trim();
if (email.isEmpty) return I18n.errorEmailRequired;
if (email.contains('+')) return I18n.errorEmailPlusNotAllowed;
if (!_emailRegex.hasMatch(email)) return I18n.errorEmailInvalid;
return '';
}

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:location/src/features/location/presentation/state/location_view_model.dart';
import 'package:location/src/features/location/presentation/state/location_view_state.dart';
import 'package:location/src/features/location/presentation/widgets/location_map.dart';
import 'package:sf_localizations/sf_localizations.dart';
@@ -16,6 +17,55 @@ class LocationScreen extends ConsumerWidget {
final controlPanelState = ref.watch(controlPanelViewModelProvider);
final locationState = ref.watch(locationViewModelProvider);
ref.listen(
locationViewModelProvider.select((s) => s.errorEvent),
(previous, next) {
if (next != null) {
final message = switch (next) {
LocationErrorEvent.geofenceCreate => context.translate(I18n.errorGeofenceCreate),
LocationErrorEvent.geofenceUpdate => context.translate(I18n.errorGeofenceUpdate),
LocationErrorEvent.geofenceDelete => context.translate(I18n.errorGeofenceDelete),
LocationErrorEvent.frequentPlaceCreate => context.translate(I18n.errorFrequentPlaceCreate),
LocationErrorEvent.frequentPlaceUpdate => context.translate(I18n.errorFrequentPlaceUpdate),
LocationErrorEvent.frequentPlaceDelete => context.translate(I18n.errorFrequentPlaceDelete),
LocationErrorEvent.positionHistory => context.translate(I18n.errorPositionHistory),
LocationErrorEvent.locationFrequency => context.translate(I18n.errorLocationFrequency),
};
showTopSnackbar(context, message: message, type: MessageType.error);
}
},
);
ref.listen(
controlPanelViewModelProvider.select((s) => s.errorMessage),
(previous, next) {
if (next.isNotEmpty) {
showTopSnackbar(
context,
message: context.translate(I18n.errorGeneric),
type: MessageType.error,
);
}
},
);
ref.listen(
locationViewModelProvider.select((s) => s.successMessage),
(previous, next) {
if (next != null) {
final message = switch (next) {
LocationSuccessEvent.geofenceCreated => context.translate(I18n.geofenceCreated),
LocationSuccessEvent.geofenceUpdated => context.translate(I18n.geofenceUpdated),
LocationSuccessEvent.geofenceDeleted => context.translate(I18n.geofenceDeleted),
LocationSuccessEvent.frequentPlaceCreated => context.translate(I18n.frequentPlaceCreated),
LocationSuccessEvent.frequentPlaceUpdated => context.translate(I18n.frequentPlaceUpdated),
LocationSuccessEvent.frequentPlaceDeleted => context.translate(I18n.frequentPlaceDeleted),
};
showTopSnackbar(context, message: message, type: MessageType.success);
}
},
);
if (controlPanelState.isLoading) {
return LegacyPageLayout(
theme: theme,

View File

@@ -119,6 +119,14 @@ class LocationMapViewModel extends Notifier<LocationMapViewState> {
state = state.copyWith(actionsExpanded: !state.actionsExpanded);
}
void toggleFrequencyExpanded() {
state = state.copyWith(frequencyExpanded: !state.frequencyExpanded);
}
void collapseFrequency() {
state = state.copyWith(frequencyExpanded: false);
}
void updateMapZoom(double zoom) {
state = state.copyWith(mapZoom: zoom);
}

View File

@@ -25,6 +25,7 @@ abstract class LocationMapViewState with _$LocationMapViewState {
PositionEntity? selectedHistoryPosition,
@Default(false) bool isFollowing,
@Default(false) bool actionsExpanded,
@Default(false) bool frequencyExpanded,
@Default(_defaultZoom) double mapZoom,
}) = _LocationMapViewState;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LocationMapViewState {
bool get showGeofences; bool get showFrequentPlaces; PlacingMode get placingMode; bool get adjustingRadius; double get previewRadius; LatLng? get previewPoint; GeofenceEntity? get selectedGeofence; GeofenceEntity? get editingGeofence; FrequentPlaceEntity? get selectedFrequentPlace; PositionEntity? get selectedHistoryPosition; bool get isFollowing; bool get actionsExpanded; double get mapZoom;
bool get showGeofences; bool get showFrequentPlaces; PlacingMode get placingMode; bool get adjustingRadius; double get previewRadius; LatLng? get previewPoint; GeofenceEntity? get selectedGeofence; GeofenceEntity? get editingGeofence; FrequentPlaceEntity? get selectedFrequentPlace; PositionEntity? get selectedHistoryPosition; bool get isFollowing; bool get actionsExpanded; bool get frequencyExpanded; double get mapZoom;
/// Create a copy of LocationMapViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $LocationMapViewStateCopyWith<LocationMapViewState> get copyWith => _$LocationMa
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocationMapViewState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom));
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocationMapViewState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.frequencyExpanded, frequencyExpanded) || other.frequencyExpanded == frequencyExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom));
}
@override
int get hashCode => Object.hash(runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,mapZoom);
int get hashCode => Object.hash(runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,frequencyExpanded,mapZoom);
@override
String toString() {
return 'LocationMapViewState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, mapZoom: $mapZoom)';
return 'LocationMapViewState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, frequencyExpanded: $frequencyExpanded, mapZoom: $mapZoom)';
}
@@ -45,7 +45,7 @@ abstract mixin class $LocationMapViewStateCopyWith<$Res> {
factory $LocationMapViewStateCopyWith(LocationMapViewState value, $Res Function(LocationMapViewState) _then) = _$LocationMapViewStateCopyWithImpl;
@useResult
$Res call({
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, double mapZoom
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom
});
@@ -62,7 +62,7 @@ class _$LocationMapViewStateCopyWithImpl<$Res>
/// Create a copy of LocationMapViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? mapZoom = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? frequencyExpanded = null,Object? mapZoom = null,}) {
return _then(_self.copyWith(
showGeofences: null == showGeofences ? _self.showGeofences : showGeofences // ignore: cast_nullable_to_non_nullable
as bool,showFrequentPlaces: null == showFrequentPlaces ? _self.showFrequentPlaces : showFrequentPlaces // ignore: cast_nullable_to_non_nullable
@@ -76,6 +76,7 @@ as GeofenceEntity?,selectedFrequentPlace: freezed == selectedFrequentPlace ? _se
as FrequentPlaceEntity?,selectedHistoryPosition: freezed == selectedHistoryPosition ? _self.selectedHistoryPosition : selectedHistoryPosition // ignore: cast_nullable_to_non_nullable
as PositionEntity?,isFollowing: null == isFollowing ? _self.isFollowing : isFollowing // ignore: cast_nullable_to_non_nullable
as bool,actionsExpanded: null == actionsExpanded ? _self.actionsExpanded : actionsExpanded // ignore: cast_nullable_to_non_nullable
as bool,frequencyExpanded: null == frequencyExpanded ? _self.frequencyExpanded : frequencyExpanded // ignore: cast_nullable_to_non_nullable
as bool,mapZoom: null == mapZoom ? _self.mapZoom : mapZoom // ignore: cast_nullable_to_non_nullable
as double,
));
@@ -210,10 +211,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, double mapZoom)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LocationMapViewState() when $default != null:
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.mapZoom);case _:
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom);case _:
return orElse();
}
@@ -231,10 +232,10 @@ return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, double mapZoom) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom) $default,) {final _that = this;
switch (_that) {
case _LocationMapViewState():
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.mapZoom);case _:
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom);case _:
throw StateError('Unexpected subclass');
}
@@ -251,10 +252,10 @@ return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, double mapZoom)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom)? $default,) {final _that = this;
switch (_that) {
case _LocationMapViewState() when $default != null:
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.mapZoom);case _:
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom);case _:
return null;
}
@@ -266,7 +267,7 @@ return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_
class _LocationMapViewState implements LocationMapViewState {
const _LocationMapViewState({this.showGeofences = true, this.showFrequentPlaces = true, this.placingMode = PlacingMode.none, this.adjustingRadius = false, this.previewRadius = 200.0, this.previewPoint, this.selectedGeofence, this.editingGeofence, this.selectedFrequentPlace, this.selectedHistoryPosition, this.isFollowing = false, this.actionsExpanded = false, this.mapZoom = _defaultZoom});
const _LocationMapViewState({this.showGeofences = true, this.showFrequentPlaces = true, this.placingMode = PlacingMode.none, this.adjustingRadius = false, this.previewRadius = 200.0, this.previewPoint, this.selectedGeofence, this.editingGeofence, this.selectedFrequentPlace, this.selectedHistoryPosition, this.isFollowing = false, this.actionsExpanded = false, this.frequencyExpanded = false, this.mapZoom = _defaultZoom});
@override@JsonKey() final bool showGeofences;
@@ -281,6 +282,7 @@ class _LocationMapViewState implements LocationMapViewState {
@override final PositionEntity? selectedHistoryPosition;
@override@JsonKey() final bool isFollowing;
@override@JsonKey() final bool actionsExpanded;
@override@JsonKey() final bool frequencyExpanded;
@override@JsonKey() final double mapZoom;
/// Create a copy of LocationMapViewState
@@ -293,16 +295,16 @@ _$LocationMapViewStateCopyWith<_LocationMapViewState> get copyWith => __$Locatio
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocationMapViewState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocationMapViewState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.frequencyExpanded, frequencyExpanded) || other.frequencyExpanded == frequencyExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom));
}
@override
int get hashCode => Object.hash(runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,mapZoom);
int get hashCode => Object.hash(runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,frequencyExpanded,mapZoom);
@override
String toString() {
return 'LocationMapViewState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, mapZoom: $mapZoom)';
return 'LocationMapViewState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, frequencyExpanded: $frequencyExpanded, mapZoom: $mapZoom)';
}
@@ -313,7 +315,7 @@ abstract mixin class _$LocationMapViewStateCopyWith<$Res> implements $LocationMa
factory _$LocationMapViewStateCopyWith(_LocationMapViewState value, $Res Function(_LocationMapViewState) _then) = __$LocationMapViewStateCopyWithImpl;
@override @useResult
$Res call({
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, double mapZoom
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom
});
@@ -330,7 +332,7 @@ class __$LocationMapViewStateCopyWithImpl<$Res>
/// Create a copy of LocationMapViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? mapZoom = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? frequencyExpanded = null,Object? mapZoom = null,}) {
return _then(_LocationMapViewState(
showGeofences: null == showGeofences ? _self.showGeofences : showGeofences // ignore: cast_nullable_to_non_nullable
as bool,showFrequentPlaces: null == showFrequentPlaces ? _self.showFrequentPlaces : showFrequentPlaces // ignore: cast_nullable_to_non_nullable
@@ -344,6 +346,7 @@ as GeofenceEntity?,selectedFrequentPlace: freezed == selectedFrequentPlace ? _se
as FrequentPlaceEntity?,selectedHistoryPosition: freezed == selectedHistoryPosition ? _self.selectedHistoryPosition : selectedHistoryPosition // ignore: cast_nullable_to_non_nullable
as PositionEntity?,isFollowing: null == isFollowing ? _self.isFollowing : isFollowing // ignore: cast_nullable_to_non_nullable
as bool,actionsExpanded: null == actionsExpanded ? _self.actionsExpanded : actionsExpanded // ignore: cast_nullable_to_non_nullable
as bool,frequencyExpanded: null == frequencyExpanded ? _self.frequencyExpanded : frequencyExpanded // ignore: cast_nullable_to_non_nullable
as bool,mapZoom: null == mapZoom ? _self.mapZoom : mapZoom // ignore: cast_nullable_to_non_nullable
as double,
));

View File

@@ -24,7 +24,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
@override
LocationViewState build() {
_locationRepository = ref.read(locationRepositoryProvider);
final device = ref.watch(selectedDeviceProvider);
final device = ref.read(selectedDeviceProvider);
if (device != null) {
_fetchData(device.id);
}
@@ -45,7 +45,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
state = state.copyWith(isLoading: false);
}
}
@@ -56,7 +56,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
required double longitude,
required double radius,
}) async {
state = state.copyWith(isSubmitting: true, errorMessage: '');
state = state.copyWith(isSubmitting: true, errorEvent: null, successMessage: null);
try {
final user = await ref.read(userInfoProvider.future);
final device = ref.read(selectedDeviceProvider);
@@ -78,10 +78,11 @@ class LocationViewModel extends Notifier<LocationViewState> {
state = state.copyWith(
geofences: [...state.geofences, created],
isSubmitting: false,
successMessage: LocationSuccessEvent.geofenceCreated,
);
return true;
} catch (e) {
return _handleError(e);
return _handleErrorEvent(LocationErrorEvent.geofenceCreate);
}
}
@@ -93,7 +94,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
required double longitude,
required double radius,
}) async {
state = state.copyWith(isSubmitting: true, errorMessage: '');
state = state.copyWith(isSubmitting: true, errorEvent: null, successMessage: null);
try {
final request = UpdateGeofenceRequestModel(
id: id,
@@ -110,24 +111,26 @@ class LocationViewModel extends Notifier<LocationViewState> {
geofences:
state.geofences.map((g) => g.id == id ? updated : g).toList(),
isSubmitting: false,
successMessage: LocationSuccessEvent.geofenceUpdated,
);
return true;
} catch (e) {
return _handleError(e);
return _handleErrorEvent(LocationErrorEvent.geofenceUpdate);
}
}
Future<bool> deleteGeofence({required String id}) async {
state = state.copyWith(errorMessage: '');
state = state.copyWith(errorEvent: null, successMessage: null);
try {
await _locationRepository.deleteGeofence(geofenceId: id);
if (!ref.mounted) return false;
state = state.copyWith(
geofences: state.geofences.where((g) => g.id != id).toList(),
successMessage: LocationSuccessEvent.geofenceDeleted,
);
return true;
} catch (e) {
return _handleError(e);
return _handleErrorEvent(LocationErrorEvent.geofenceDelete);
}
}
@@ -137,7 +140,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
required double lng,
List<WifiInfoEntity> wifiList = const [],
}) async {
state = state.copyWith(isSubmitting: true, errorMessage: '');
state = state.copyWith(isSubmitting: true, errorEvent: null, successMessage: null);
try {
final user = await ref.read(userInfoProvider.future);
final device = ref.read(selectedDeviceProvider);
@@ -164,10 +167,11 @@ class LocationViewModel extends Notifier<LocationViewState> {
state = state.copyWith(
frequentPlaces: [...state.frequentPlaces, created],
isSubmitting: false,
successMessage: LocationSuccessEvent.frequentPlaceCreated,
);
return true;
} catch (e) {
return _handleError(e);
return _handleErrorEvent(LocationErrorEvent.frequentPlaceCreate);
}
}
@@ -178,7 +182,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
required double lng,
List<WifiInfoEntity> wifiList = const [],
}) async {
state = state.copyWith(isSubmitting: true, errorMessage: '');
state = state.copyWith(isSubmitting: true, errorEvent: null, successMessage: null);
try {
final request = UpdateFrequentPlaceRequestModel(
id: id,
@@ -200,25 +204,27 @@ class LocationViewModel extends Notifier<LocationViewState> {
frequentPlaces:
state.frequentPlaces.map((f) => f.id == id ? updated : f).toList(),
isSubmitting: false,
successMessage: LocationSuccessEvent.frequentPlaceUpdated,
);
return true;
} catch (e) {
return _handleError(e);
return _handleErrorEvent(LocationErrorEvent.frequentPlaceUpdate);
}
}
Future<bool> deleteFrequentPlace({required String id}) async {
state = state.copyWith(errorMessage: '');
state = state.copyWith(errorEvent: null, successMessage: null);
try {
await _locationRepository.deleteFrequentPlace(frequentPlaceId: id);
if (!ref.mounted) return false;
state = state.copyWith(
frequentPlaces:
state.frequentPlaces.where((f) => f.id != id).toList(),
successMessage: LocationSuccessEvent.frequentPlaceDeleted,
);
return true;
} catch (e) {
return _handleError(e);
return _handleErrorEvent(LocationErrorEvent.frequentPlaceDelete);
}
}
@@ -229,7 +235,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
state = state.copyWith(isLoadingHistory: true, errorMessage: '');
state = state.copyWith(isLoadingHistory: true, errorEvent: null);
try {
final positions = await _locationRepository.getPositionHistory(
deviceIdentificator: device.identificator,
@@ -246,7 +252,7 @@ class LocationViewModel extends Notifier<LocationViewState> {
if (!ref.mounted) return;
state = state.copyWith(
isLoadingHistory: false,
errorMessage: _formatError(e),
errorEvent: LocationErrorEvent.positionHistory,
);
}
}
@@ -262,17 +268,34 @@ class LocationViewModel extends Notifier<LocationViewState> {
state = state.copyWith(showRouteTrail: !state.showRouteTrail);
}
bool _handleError(Object e) {
Future<bool> updateLocationFrequency({required int frequency}) async {
final device = ref.read(selectedDeviceProvider);
if (device == null) return false;
state = state.copyWith(isSubmitting: true, errorEvent: null);
try {
final updatedSettings = device.settings.copyWith(frequency: frequency);
await ref.read(deviceSettingsUpdateProvider).updateDeviceSettings(
device: device,
updatedSettings: updatedSettings,
);
if (!ref.mounted) return false;
ref.syncDeviceSettings(device, updatedSettings);
state = state.copyWith(isSubmitting: false);
return true;
} catch (e) {
return _handleErrorEvent(LocationErrorEvent.locationFrequency);
}
}
bool _handleErrorEvent(LocationErrorEvent event) {
if (!ref.mounted) return false;
state = state.copyWith(
isSubmitting: false,
errorMessage: _formatError(e),
errorEvent: event,
);
return false;
}
String _formatError(Object e) {
final msg = e.toString();
return msg.startsWith('Exception: ') ? msg.substring(11) : msg;
}
}

View File

@@ -5,6 +5,26 @@ import 'package:location/src/core/domain/entities/frequent_place_entity.dart';
part 'location_view_state.freezed.dart';
enum LocationSuccessEvent {
geofenceCreated,
geofenceUpdated,
geofenceDeleted,
frequentPlaceCreated,
frequentPlaceUpdated,
frequentPlaceDeleted,
}
enum LocationErrorEvent {
geofenceCreate,
geofenceUpdate,
geofenceDelete,
frequentPlaceCreate,
frequentPlaceUpdate,
frequentPlaceDelete,
positionHistory,
locationFrequency,
}
@freezed
abstract class LocationViewState with _$LocationViewState {
const factory LocationViewState({
@@ -15,6 +35,7 @@ abstract class LocationViewState with _$LocationViewState {
@Default(false) bool isLoadingHistory,
@Default(false) bool isSubmitting,
@Default(false) bool showRouteTrail,
@Default('') String errorMessage,
LocationErrorEvent? errorEvent,
LocationSuccessEvent? successMessage,
}) = _LocationViewState;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LocationViewState {
List<GeofenceEntity> get geofences; List<FrequentPlaceEntity> get frequentPlaces; List<PositionEntity> get positionHistory; bool get isLoading; bool get isLoadingHistory; bool get isSubmitting; bool get showRouteTrail; String get errorMessage;
List<GeofenceEntity> get geofences; List<FrequentPlaceEntity> get frequentPlaces; List<PositionEntity> get positionHistory; bool get isLoading; bool get isLoadingHistory; bool get isSubmitting; bool get showRouteTrail; LocationErrorEvent? get errorEvent; LocationSuccessEvent? get successMessage;
/// Create a copy of LocationViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $LocationViewStateCopyWith<LocationViewState> get copyWith => _$LocationViewStat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocationViewState&&const DeepCollectionEquality().equals(other.geofences, geofences)&&const DeepCollectionEquality().equals(other.frequentPlaces, frequentPlaces)&&const DeepCollectionEquality().equals(other.positionHistory, positionHistory)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingHistory, isLoadingHistory) || other.isLoadingHistory == isLoadingHistory)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.showRouteTrail, showRouteTrail) || other.showRouteTrail == showRouteTrail)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocationViewState&&const DeepCollectionEquality().equals(other.geofences, geofences)&&const DeepCollectionEquality().equals(other.frequentPlaces, frequentPlaces)&&const DeepCollectionEquality().equals(other.positionHistory, positionHistory)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingHistory, isLoadingHistory) || other.isLoadingHistory == isLoadingHistory)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.showRouteTrail, showRouteTrail) || other.showRouteTrail == showRouteTrail)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(geofences),const DeepCollectionEquality().hash(frequentPlaces),const DeepCollectionEquality().hash(positionHistory),isLoading,isLoadingHistory,isSubmitting,showRouteTrail,errorMessage);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(geofences),const DeepCollectionEquality().hash(frequentPlaces),const DeepCollectionEquality().hash(positionHistory),isLoading,isLoadingHistory,isSubmitting,showRouteTrail,errorEvent,successMessage);
@override
String toString() {
return 'LocationViewState(geofences: $geofences, frequentPlaces: $frequentPlaces, positionHistory: $positionHistory, isLoading: $isLoading, isLoadingHistory: $isLoadingHistory, isSubmitting: $isSubmitting, showRouteTrail: $showRouteTrail, errorMessage: $errorMessage)';
return 'LocationViewState(geofences: $geofences, frequentPlaces: $frequentPlaces, positionHistory: $positionHistory, isLoading: $isLoading, isLoadingHistory: $isLoadingHistory, isSubmitting: $isSubmitting, showRouteTrail: $showRouteTrail, errorEvent: $errorEvent, successMessage: $successMessage)';
}
@@ -45,7 +45,7 @@ abstract mixin class $LocationViewStateCopyWith<$Res> {
factory $LocationViewStateCopyWith(LocationViewState value, $Res Function(LocationViewState) _then) = _$LocationViewStateCopyWithImpl;
@useResult
$Res call({
List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, String errorMessage
List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, LocationErrorEvent? errorEvent, LocationSuccessEvent? successMessage
});
@@ -62,7 +62,7 @@ class _$LocationViewStateCopyWithImpl<$Res>
/// Create a copy of LocationViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? geofences = null,Object? frequentPlaces = null,Object? positionHistory = null,Object? isLoading = null,Object? isLoadingHistory = null,Object? isSubmitting = null,Object? showRouteTrail = null,Object? errorMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? geofences = null,Object? frequentPlaces = null,Object? positionHistory = null,Object? isLoading = null,Object? isLoadingHistory = null,Object? isSubmitting = null,Object? showRouteTrail = null,Object? errorEvent = freezed,Object? successMessage = freezed,}) {
return _then(_self.copyWith(
geofences: null == geofences ? _self.geofences : geofences // ignore: cast_nullable_to_non_nullable
as List<GeofenceEntity>,frequentPlaces: null == frequentPlaces ? _self.frequentPlaces : frequentPlaces // ignore: cast_nullable_to_non_nullable
@@ -71,8 +71,9 @@ as List<PositionEntity>,isLoading: null == isLoading ? _self.isLoading : isLoadi
as bool,isLoadingHistory: null == isLoadingHistory ? _self.isLoadingHistory : isLoadingHistory // ignore: cast_nullable_to_non_nullable
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,showRouteTrail: null == showRouteTrail ? _self.showRouteTrail : showRouteTrail // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as LocationErrorEvent?,successMessage: freezed == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as LocationSuccessEvent?,
));
}
@@ -157,10 +158,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, LocationErrorEvent? errorEvent, LocationSuccessEvent? successMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LocationViewState() when $default != null:
return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that.isLoading,_that.isLoadingHistory,_that.isSubmitting,_that.showRouteTrail,_that.errorMessage);case _:
return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that.isLoading,_that.isLoadingHistory,_that.isSubmitting,_that.showRouteTrail,_that.errorEvent,_that.successMessage);case _:
return orElse();
}
@@ -178,10 +179,10 @@ return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, String errorMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, LocationErrorEvent? errorEvent, LocationSuccessEvent? successMessage) $default,) {final _that = this;
switch (_that) {
case _LocationViewState():
return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that.isLoading,_that.isLoadingHistory,_that.isSubmitting,_that.showRouteTrail,_that.errorMessage);case _:
return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that.isLoading,_that.isLoadingHistory,_that.isSubmitting,_that.showRouteTrail,_that.errorEvent,_that.successMessage);case _:
throw StateError('Unexpected subclass');
}
@@ -198,10 +199,10 @@ return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, String errorMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, LocationErrorEvent? errorEvent, LocationSuccessEvent? successMessage)? $default,) {final _that = this;
switch (_that) {
case _LocationViewState() when $default != null:
return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that.isLoading,_that.isLoadingHistory,_that.isSubmitting,_that.showRouteTrail,_that.errorMessage);case _:
return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that.isLoading,_that.isLoadingHistory,_that.isSubmitting,_that.showRouteTrail,_that.errorEvent,_that.successMessage);case _:
return null;
}
@@ -213,7 +214,7 @@ return $default(_that.geofences,_that.frequentPlaces,_that.positionHistory,_that
class _LocationViewState implements LocationViewState {
const _LocationViewState({final List<GeofenceEntity> geofences = const [], final List<FrequentPlaceEntity> frequentPlaces = const [], final List<PositionEntity> positionHistory = const [], this.isLoading = true, this.isLoadingHistory = false, this.isSubmitting = false, this.showRouteTrail = false, this.errorMessage = ''}): _geofences = geofences,_frequentPlaces = frequentPlaces,_positionHistory = positionHistory;
const _LocationViewState({final List<GeofenceEntity> geofences = const [], final List<FrequentPlaceEntity> frequentPlaces = const [], final List<PositionEntity> positionHistory = const [], this.isLoading = true, this.isLoadingHistory = false, this.isSubmitting = false, this.showRouteTrail = false, this.errorEvent, this.successMessage}): _geofences = geofences,_frequentPlaces = frequentPlaces,_positionHistory = positionHistory;
final List<GeofenceEntity> _geofences;
@@ -241,7 +242,8 @@ class _LocationViewState implements LocationViewState {
@override@JsonKey() final bool isLoadingHistory;
@override@JsonKey() final bool isSubmitting;
@override@JsonKey() final bool showRouteTrail;
@override@JsonKey() final String errorMessage;
@override final LocationErrorEvent? errorEvent;
@override final LocationSuccessEvent? successMessage;
/// Create a copy of LocationViewState
/// with the given fields replaced by the non-null parameter values.
@@ -253,16 +255,16 @@ _$LocationViewStateCopyWith<_LocationViewState> get copyWith => __$LocationViewS
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocationViewState&&const DeepCollectionEquality().equals(other._geofences, _geofences)&&const DeepCollectionEquality().equals(other._frequentPlaces, _frequentPlaces)&&const DeepCollectionEquality().equals(other._positionHistory, _positionHistory)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingHistory, isLoadingHistory) || other.isLoadingHistory == isLoadingHistory)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.showRouteTrail, showRouteTrail) || other.showRouteTrail == showRouteTrail)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocationViewState&&const DeepCollectionEquality().equals(other._geofences, _geofences)&&const DeepCollectionEquality().equals(other._frequentPlaces, _frequentPlaces)&&const DeepCollectionEquality().equals(other._positionHistory, _positionHistory)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingHistory, isLoadingHistory) || other.isLoadingHistory == isLoadingHistory)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.showRouteTrail, showRouteTrail) || other.showRouteTrail == showRouteTrail)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_geofences),const DeepCollectionEquality().hash(_frequentPlaces),const DeepCollectionEquality().hash(_positionHistory),isLoading,isLoadingHistory,isSubmitting,showRouteTrail,errorMessage);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_geofences),const DeepCollectionEquality().hash(_frequentPlaces),const DeepCollectionEquality().hash(_positionHistory),isLoading,isLoadingHistory,isSubmitting,showRouteTrail,errorEvent,successMessage);
@override
String toString() {
return 'LocationViewState(geofences: $geofences, frequentPlaces: $frequentPlaces, positionHistory: $positionHistory, isLoading: $isLoading, isLoadingHistory: $isLoadingHistory, isSubmitting: $isSubmitting, showRouteTrail: $showRouteTrail, errorMessage: $errorMessage)';
return 'LocationViewState(geofences: $geofences, frequentPlaces: $frequentPlaces, positionHistory: $positionHistory, isLoading: $isLoading, isLoadingHistory: $isLoadingHistory, isSubmitting: $isSubmitting, showRouteTrail: $showRouteTrail, errorEvent: $errorEvent, successMessage: $successMessage)';
}
@@ -273,7 +275,7 @@ abstract mixin class _$LocationViewStateCopyWith<$Res> implements $LocationViewS
factory _$LocationViewStateCopyWith(_LocationViewState value, $Res Function(_LocationViewState) _then) = __$LocationViewStateCopyWithImpl;
@override @useResult
$Res call({
List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, String errorMessage
List<GeofenceEntity> geofences, List<FrequentPlaceEntity> frequentPlaces, List<PositionEntity> positionHistory, bool isLoading, bool isLoadingHistory, bool isSubmitting, bool showRouteTrail, LocationErrorEvent? errorEvent, LocationSuccessEvent? successMessage
});
@@ -290,7 +292,7 @@ class __$LocationViewStateCopyWithImpl<$Res>
/// Create a copy of LocationViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? geofences = null,Object? frequentPlaces = null,Object? positionHistory = null,Object? isLoading = null,Object? isLoadingHistory = null,Object? isSubmitting = null,Object? showRouteTrail = null,Object? errorMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? geofences = null,Object? frequentPlaces = null,Object? positionHistory = null,Object? isLoading = null,Object? isLoadingHistory = null,Object? isSubmitting = null,Object? showRouteTrail = null,Object? errorEvent = freezed,Object? successMessage = freezed,}) {
return _then(_LocationViewState(
geofences: null == geofences ? _self._geofences : geofences // ignore: cast_nullable_to_non_nullable
as List<GeofenceEntity>,frequentPlaces: null == frequentPlaces ? _self._frequentPlaces : frequentPlaces // ignore: cast_nullable_to_non_nullable
@@ -299,8 +301,9 @@ as List<PositionEntity>,isLoading: null == isLoading ? _self.isLoading : isLoadi
as bool,isLoadingHistory: null == isLoadingHistory ? _self.isLoadingHistory : isLoadingHistory // ignore: cast_nullable_to_non_nullable
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,showRouteTrail: null == showRouteTrail ? _self.showRouteTrail : showRouteTrail // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as LocationErrorEvent?,successMessage: freezed == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as LocationSuccessEvent?,
));
}

View File

@@ -124,8 +124,8 @@ class _FrequentPlaceSheetState extends ConsumerState<_FrequentPlaceSheet> {
final isSubmitting = ref.watch(
locationViewModelProvider.select((s) => s.isSubmitting),
);
final errorMessage = ref.watch(
locationViewModelProvider.select((s) => s.errorMessage),
final errorEvent = ref.watch(
locationViewModelProvider.select((s) => s.errorEvent),
);
return DraggableScrollableSheet(
@@ -270,11 +270,11 @@ class _FrequentPlaceSheetState extends ConsumerState<_FrequentPlaceSheet> {
);
}),
],
if (errorMessage.isNotEmpty)
if (errorEvent != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
errorMessage,
context.translate(I18n.errorGeneric),
style:
const TextStyle(color: Colors.red, fontSize: 13),
),

View File

@@ -130,8 +130,8 @@ class _GeofenceSheetState extends ConsumerState<_GeofenceSheet> {
final isSubmitting = ref.watch(
locationViewModelProvider.select((s) => s.isSubmitting),
);
final errorMessage = ref.watch(
locationViewModelProvider.select((s) => s.errorMessage),
final errorEvent = ref.watch(
locationViewModelProvider.select((s) => s.errorEvent),
);
return DraggableScrollableSheet(
@@ -252,11 +252,11 @@ class _GeofenceSheetState extends ConsumerState<_GeofenceSheet> {
primaryColor: primaryColor,
),
),
if (errorMessage.isNotEmpty)
if (errorEvent != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
errorMessage,
context.translate(I18n.errorGeneric),
style: const TextStyle(color: Colors.red, fontSize: 13),
),
),

Some files were not shown because too many files have changed in this diff Show More