From ec4e42b4087abad923089bd3121a40eb45ecb6f4 Mon Sep 17 00:00:00 2001 From: aitorarana Date: Thu, 12 Mar 2026 17:40:06 +0100 Subject: [PATCH] settings ui --- .flutter-plugins-dependencies | 2 +- .../reports/problems/problems-report.html | 2 +- .../mobile_app/lib/navigation/app_router.dart | 88 ++++++ apps/mobile_app/pubspec.lock | 7 + apps/mobile_app/pubspec.yaml | 2 + apps/mobile_app/pubspec_overrides.yaml | 4 + .../presentation/control_panel_screen.dart | 5 +- .../settings_remote_datasource.dart | 4 + .../settings_remote_datasource_impl.dart | 73 +++++ .../settings_repository_impl.dart | 13 + .../repositories/settings_repository.dart | 3 + .../settings_remote_datasource_provider.dart | 9 + .../settings_repository_provider.dart | 9 + .../lib/src/features/alarm/alarm_builder.dart | 18 ++ .../alarm/domain/entities/alarm_entity.dart | 10 + .../domain/entities/alarm_entity.freezed.dart | 271 ++++++++++++++++ .../alarm/domain/get_alarms_use_case.dart | 6 + .../domain/get_alarms_use_case_impl.dart | 15 + .../alarm/presentation/alarm_screen.dart | 152 +++++++++ .../get_alarms_use_case_provider.dart | 9 + .../presentation/state/alarm_view_model.dart | 73 +++++ .../presentation/state/alarm_view_state.dart | 17 + .../state/alarm_view_state.freezed.dart | 298 ++++++++++++++++++ .../widgets/new_alarm_dialog.dart | 245 ++++++++++++++ .../domain/shutdown_use_case.dart | 4 + .../domain/shutdown_use_case_impl.dart | 15 + .../providers/shutdown_use_case_provider.dart | 10 + .../remote_management_screen.dart | 150 +++++++++ .../state/remote_management_view_model.dart | 82 +++++ .../state/remote_management_view_state.dart | 14 + .../remote_management_view_state.freezed.dart | 277 ++++++++++++++++ .../presentation/widgets/confirm_dialog.dart | 59 ++++ .../remote_management_builder.dart | 19 ++ .../presentation/settings_screen.dart | 181 +++++++++++ .../features/settings/settings_builder.dart | 18 ++ .../domain/set_contacts_use_case.dart | 4 + .../domain/set_contacts_use_case_impl.dart | 15 + .../set_contacts_use_case_provider.dart | 10 + .../presentation/sos_contacts_screen.dart | 132 ++++++++ .../state/sos_contacts_view_model.dart | 119 +++++++ .../state/sos_contacts_view_state.dart | 17 + .../sos_contacts_view_state.freezed.dart | 289 +++++++++++++++++ .../widgets/edit_phone_dialog.dart | 92 ++++++ .../sos_contacts/sos_contacts_builder.dart | 19 ++ .../sound/domain/set_sound_use_case.dart | 3 + .../sound/domain/set_sound_use_case_impl.dart | 16 + .../set_sound_use_case_provider.dart | 10 + .../sound/presentation/sound_screen.dart | 162 ++++++++++ .../presentation/state/sound_view_model.dart | 62 ++++ .../presentation/state/sound_view_state.dart | 14 + .../state/sound_view_state.freezed.dart | 280 ++++++++++++++++ .../lib/src/features/sound/sound_builder.dart | 19 ++ .../domain/sync_clock_use_case.dart | 3 + .../domain/sync_clock_use_case_impl.dart | 16 + .../set_sound_use_case_provider.dart | 10 + .../state/sync_clock_view_model.dart | 53 ++++ .../state/sync_clock_view_state.dart | 13 + .../state/sync_clock_view_state.freezed.dart | 277 ++++++++++++++++ .../presentation/sync_clock_screen.dart | 156 +++++++++ .../sync_clock/sync_clock_builder.dart | 19 ++ packages/navigation/lib/app_routes.dart | 17 + .../lib/src/api/questia_api.dart | 1 + packages/sf_localizations/assets/l10n/en.json | 20 +- packages/sf_localizations/assets/l10n/es.json | 51 ++- .../lib/src/generated/i18n.dart | 38 +++ 65 files changed, 4095 insertions(+), 6 deletions(-) create mode 100644 modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource.dart create mode 100644 modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource_impl.dart create mode 100644 modules/legacy/modules/settings/lib/src/core/data/repositories/settings_repository_impl.dart create mode 100644 modules/legacy/modules/settings/lib/src/core/domain/repositories/settings_repository.dart create mode 100644 modules/legacy/modules/settings/lib/src/core/providers/settings_remote_datasource_provider.dart create mode 100644 modules/legacy/modules/settings/lib/src/core/providers/settings_repository_provider.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/alarm_builder.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.freezed.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case_impl.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/presentation/alarm_screen.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/presentation/providers/get_alarms_use_case_provider.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_model.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.freezed.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/alarm/presentation/widgets/new_alarm_dialog.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case_impl.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/presentation/providers/shutdown_use_case_provider.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/presentation/remote_management_screen.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_model.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.freezed.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/presentation/widgets/confirm_dialog.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/remote_management/remote_management_builder.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/settings/presentation/settings_screen.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/settings/settings_builder.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case_impl.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/providers/set_contacts_use_case_provider.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/sos_contacts_screen.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_model.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.freezed.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/widgets/edit_phone_dialog.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sos_contacts/sos_contacts_builder.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case_impl.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/presentation/providers/set_sound_use_case_provider.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/presentation/sound_screen.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_model.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.freezed.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sound/sound_builder.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case_impl.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/providers/set_sound_use_case_provider.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_model.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.freezed.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/sync_clock_screen.dart create mode 100644 modules/legacy/modules/settings/lib/src/features/sync_clock/sync_clock_builder.dart diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index ad128130..b4eaa85f 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"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}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"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}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"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":[]}],"date_created":"2026-02-27 12:35:56.235180","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_secure_storage-9.2.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.3\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_secure_storage-9.2.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.20\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_secure_storage_macos-3.1.3\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.3\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_secure_storage_linux-1.2.3\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_secure_storage_windows-3.1.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"C:\\\\Users\\\\Aitor Arana\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_secure_storage_web-1.2.1\\\\","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"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":[]}],"date_created":"2026-03-09 09:15:02.914305","version":"3.35.6","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/apps/mobile_app/android/build/reports/problems/problems-report.html b/apps/mobile_app/android/build/reports/problems/problems-report.html index ab9c3ecb..e302409b 100644 --- a/apps/mobile_app/android/build/reports/problems/problems-report.html +++ b/apps/mobile_app/android/build/reports/problems/problems-report.html @@ -650,7 +650,7 @@ code + .copy-button { diff --git a/apps/mobile_app/lib/navigation/app_router.dart b/apps/mobile_app/lib/navigation/app_router.dart index 0c86c309..a1531132 100644 --- a/apps/mobile_app/lib/navigation/app_router.dart +++ b/apps/mobile_app/lib/navigation/app_router.dart @@ -16,6 +16,7 @@ import 'package:navigation/navigation.dart'; import 'package:notifications/notifications.dart'; import 'package:payments/payments.dart'; import 'package:profile/profile.dart'; +import 'package:settings/settings.dart'; import 'package:splash/splash.dart'; final GlobalKey rootNavigatorKey = GlobalKey(); @@ -141,8 +142,95 @@ void configureAppRouter() { ), ], ), + StatefulShellBranch( + routes: [ + GoRoute( + path: AppRoutes.settings, + name: 'settings', + pageBuilder: SettingsBuilder().buildPage, + routes: [ + GoRoute( + path: AppRoutes.alarm, + name: 'alarm', + pageBuilder: AlarmBuilder().buildPage, + ), + /*GoRoute ( + path: AppRoutes.appStore, + name: 'app_store', + // pageBuilder: AppStoreBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.battery, + name: 'battery', + // pageBuilder: BatteryBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.blockPhone, + name: 'block_phone', + // pageBuilder: BlockPhoneBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.disableFunctions, + name: 'disable_functions', + // pageBuilder: DisableFunctionsBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.language, + name: 'language', + // pageBuilder: LanguageBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.legacyNotifications, + name: 'legacy_notifications', + // pageBuilder: LegacyNotificationsBuilder().buildPage, + ),*/ + GoRoute ( + path: AppRoutes.remoteManagement, + name: 'remote_management', + pageBuilder: RemoteManagementBuilder().buildPage, + ), + /*GoRoute ( + path: AppRoutes.remoteOnOff, + name: 'remote_on_off', + // pageBuilder: RemoteOnOffBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.smsAlert, + name: 'sms_alert', + // pageBuilder: SmsAlertBuilder().buildPage, + ),*/ + GoRoute ( + path: AppRoutes.sosContacts, + name: 'sos_agenda', + pageBuilder: SosContactsBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.sound, + name: 'sound', + pageBuilder: SoundBuilder().buildPage, + ), + /*GoRoute ( + path: AppRoutes.syncClock, + name: 'sync_clock', + // pageBuilder: SyncClockBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.timezone, + name: 'timezone', + // pageBuilder: TimezoneBuilder().buildPage, + ), + GoRoute ( + path: AppRoutes.wifiSettings, + name: 'wifi_settings', + // pageBuilder: WifiSettingsBuilder().buildPage, + ),*/ + ], + ), + ], + ), ], ), + GoRoute( path: AppRoutes.login, name: 'login', diff --git a/apps/mobile_app/pubspec.lock b/apps/mobile_app/pubspec.lock index ba2d4ca1..a3ae34d1 100644 --- a/apps/mobile_app/pubspec.lock +++ b/apps/mobile_app/pubspec.lock @@ -1081,6 +1081,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.0" + settings: + dependency: "direct main" + description: + path: "../../modules/legacy/modules/settings" + relative: true + source: path + version: "1.0.0+1" sf_infrastructure: dependency: "direct main" description: diff --git a/apps/mobile_app/pubspec.yaml b/apps/mobile_app/pubspec.yaml index cae23142..7cda24e1 100644 --- a/apps/mobile_app/pubspec.yaml +++ b/apps/mobile_app/pubspec.yaml @@ -65,6 +65,8 @@ dependencies: path: ../../modules/legacy/modules/location legacy_auth: path: ../../modules/legacy/modules/legacy_auth + settings: + path: ../../modules/legacy/modules/settings #packages dependencies go here navigation: path: ../../packages/navigation diff --git a/apps/mobile_app/pubspec_overrides.yaml b/apps/mobile_app/pubspec_overrides.yaml index 4b31be77..a87b35e2 100644 --- a/apps/mobile_app/pubspec_overrides.yaml +++ b/apps/mobile_app/pubspec_overrides.yaml @@ -1,4 +1,6 @@ # melos_managed_dependency_overrides: account,activity,auth,customer_service,dashboard_shell,design_system,flutter_treezor_entrust_sdk_bridge,fonts,home,legacy_dashboard_shell,legacy_shared,navigation,notifications,payments,profile,sca_treezor,sf_infrastructure,sf_localizations,sf_shared,splash,utils,control_panel,device_management,legacy_auth,location +# melos_managed_dependency_overrides: settings +# melos_managed_dependency_overrides: account,activity,auth,customer_service,dashboard_shell,design_system,flutter_treezor_entrust_sdk_bridge,fonts,home,legacy_dashboard_shell,legacy_shared,navigation,notifications,payments,profile,sca_treezor,sf_infrastructure,sf_localizations,sf_shared,splash,utils,control_panel,device_management dependency_overrides: account: path: ../../modules/legacy/modules/account @@ -40,6 +42,8 @@ dependency_overrides: path: ../../modules/profile sca_treezor: path: ../../packages/sca_treezor + settings: + path: ..\\..\\modules\\legacy\\modules\\settings sf_infrastructure: path: ../../packages/sf_infrastructure sf_localizations: diff --git a/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart b/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart index a02549da..58854019 100644 --- a/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart +++ b/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart @@ -176,9 +176,10 @@ class _MenuSection extends ConsumerWidget { text: I18n.accountSettings, ), SizedBox(height: SizeUtils.getByScreen(small: 8, big: 7)), - // TODO: Implementar navegación a Device Settings _SectionButton( - onPressed: () {}, + onPressed: () { + navigationContract.pushTo(AppRoutes.settings); + }, icon: Icons.settings_outlined, text: I18n.deviceSettings, ), diff --git a/modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource.dart b/modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource.dart new file mode 100644 index 00000000..f0b70396 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource.dart @@ -0,0 +1,4 @@ + +abstract class SettingsRemoteDatasource { + // Future getLoggedUser({required String token}); +} diff --git a/modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource_impl.dart b/modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource_impl.dart new file mode 100644 index 00000000..3a33bde1 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/core/data/datasources/settings_remote_datasource_impl.dart @@ -0,0 +1,73 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:settings/src/core/data/datasources/settings_remote_datasource.dart'; +import 'package:sf_infrastructure/sf_infrastructure.dart'; + +class SettingsRemoteDatasourceImpl implements SettingsRemoteDatasource { + SettingsRemoteDatasourceImpl(this._repository); + + final QuestiaRepository _repository; + + /*@override + Future getLoggedUser({required String token}) async { + try { + final response = await _repository.get>( + '/users/api/auth/me', + ); + final data = response.data!['item']; + if (data == null || data.isEmpty) { + throw Exception('Empty response from /auth/me'); + } + + final model = GetLoggedUserResponseModel.fromJson(data); + final model = GetLoggedUserResponseModel(item: + GetLoggedUserItemResponseModel( + id: '1111', + firstName: 'Juan', + email: 'juan@test.com', + phone: '111111111')); + return model.toEntity(); + } on DioException catch (error) { + throw _mapDioError( + error, + defaultMessage: error.message ?? 'Error getting logged user', + ); + } + }*/ +} + +Exception _mapDioError(DioException error, {required String defaultMessage}) { + final apiMsg = _extractApiMessage(error.response?.data); + final msg = apiMsg ?? error.message ?? defaultMessage; + return Exception(msg); +} + +String? _extractApiMessage(Object? data) { + if (data == null) return null; + + if (data is Map) { + final errorObj = data['error']; + if (errorObj is Map && errorObj['message'] is String) { + return (errorObj['message'] as String).trim(); + } + if (data['message'] is String) { + return (data['message'] as String).trim(); + } + return null; + } + + if (data is String) { + final raw = data.trim(); + if (raw.isEmpty) return null; + + try { + final decoded = jsonDecode(raw); + return _extractApiMessage(decoded); + } catch (_) { + return raw; + } + } + + return null; +} diff --git a/modules/legacy/modules/settings/lib/src/core/data/repositories/settings_repository_impl.dart b/modules/legacy/modules/settings/lib/src/core/data/repositories/settings_repository_impl.dart new file mode 100644 index 00000000..c6a30bb1 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/core/data/repositories/settings_repository_impl.dart @@ -0,0 +1,13 @@ +import 'package:settings/src/core/data/datasources/settings_remote_datasource.dart'; +import 'package:settings/src/core/domain/repositories/settings_repository.dart'; + +class SettingsRepositoryImpl implements SettingsRepository { + const SettingsRepositoryImpl(this._remote); + + final SettingsRemoteDatasource _remote; + + /*@override + Future> getContacts({required String userId}) { + return _remote.getContacts(userId: userId); + }*/ +} diff --git a/modules/legacy/modules/settings/lib/src/core/domain/repositories/settings_repository.dart b/modules/legacy/modules/settings/lib/src/core/domain/repositories/settings_repository.dart new file mode 100644 index 00000000..5d948c13 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/core/domain/repositories/settings_repository.dart @@ -0,0 +1,3 @@ +abstract class SettingsRepository { + // Future> getContacts({required String userId}); +} diff --git a/modules/legacy/modules/settings/lib/src/core/providers/settings_remote_datasource_provider.dart b/modules/legacy/modules/settings/lib/src/core/providers/settings_remote_datasource_provider.dart new file mode 100644 index 00000000..1b2bfe98 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/core/providers/settings_remote_datasource_provider.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/core/data/datasources/settings_remote_datasource.dart'; +import 'package:settings/src/core/data/datasources/settings_remote_datasource_impl.dart'; +import 'package:sf_infrastructure/sf_infrastructure.dart'; + +final settingsRemoteDatasourceProvider = Provider((ref) { + final questiaRepository = getIt(); + return SettingsRemoteDatasourceImpl(questiaRepository); +}); diff --git a/modules/legacy/modules/settings/lib/src/core/providers/settings_repository_provider.dart b/modules/legacy/modules/settings/lib/src/core/providers/settings_repository_provider.dart new file mode 100644 index 00000000..19137b81 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/core/providers/settings_repository_provider.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/core/data/repositories/settings_repository_impl.dart'; +import 'package:settings/src/core/domain/repositories/settings_repository.dart'; +import 'package:settings/src/core/providers/settings_remote_datasource_provider.dart'; + +final settingsRepositoryProvider = Provider((ref) { + final remote = ref.read(settingsRemoteDatasourceProvider); + return SettingsRepositoryImpl(remote); +}); diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/alarm_builder.dart b/modules/legacy/modules/settings/lib/src/features/alarm/alarm_builder.dart new file mode 100644 index 00000000..4f71735c --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/alarm_builder.dart @@ -0,0 +1,18 @@ +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 'package:settings/src/features/alarm/presentation/alarm_screen.dart'; + +class AlarmBuilder { + const AlarmBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: AlarmScreen(navigationContract: navigationContract), + ); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.dart b/modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.dart new file mode 100644 index 00000000..2c029e74 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.dart @@ -0,0 +1,10 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'alarm_entity.freezed.dart'; + +@freezed +abstract class AlarmEntity with _$AlarmEntity { + const factory AlarmEntity({ + required String id, + }) = _AlarmEntity; +} diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.freezed.dart b/modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.freezed.dart new file mode 100644 index 00000000..6dbd7dcd --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/domain/entities/alarm_entity.freezed.dart @@ -0,0 +1,271 @@ +// 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 'alarm_entity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$AlarmEntity { + + String get id; +/// Create a copy of AlarmEntity +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AlarmEntityCopyWith get copyWith => _$AlarmEntityCopyWithImpl(this as AlarmEntity, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AlarmEntity&&(identical(other.id, id) || other.id == id)); +} + + +@override +int get hashCode => Object.hash(runtimeType,id); + +@override +String toString() { + return 'AlarmEntity(id: $id)'; +} + + +} + +/// @nodoc +abstract mixin class $AlarmEntityCopyWith<$Res> { + factory $AlarmEntityCopyWith(AlarmEntity value, $Res Function(AlarmEntity) _then) = _$AlarmEntityCopyWithImpl; +@useResult +$Res call({ + String id +}); + + + + +} +/// @nodoc +class _$AlarmEntityCopyWithImpl<$Res> + implements $AlarmEntityCopyWith<$Res> { + _$AlarmEntityCopyWithImpl(this._self, this._then); + + final AlarmEntity _self; + final $Res Function(AlarmEntity) _then; + +/// Create a copy of AlarmEntity +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [AlarmEntity]. +extension AlarmEntityPatterns on AlarmEntity { +/// 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 Function( _AlarmEntity value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AlarmEntity() 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 Function( _AlarmEntity value) $default,){ +final _that = this; +switch (_that) { +case _AlarmEntity(): +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? Function( _AlarmEntity value)? $default,){ +final _that = this; +switch (_that) { +case _AlarmEntity() 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 Function( String id)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AlarmEntity() when $default != null: +return $default(_that.id);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 Function( String id) $default,) {final _that = this; +switch (_that) { +case _AlarmEntity(): +return $default(_that.id);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? Function( String id)? $default,) {final _that = this; +switch (_that) { +case _AlarmEntity() when $default != null: +return $default(_that.id);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _AlarmEntity implements AlarmEntity { + const _AlarmEntity({required this.id}); + + +@override final String id; + +/// Create a copy of AlarmEntity +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AlarmEntityCopyWith<_AlarmEntity> get copyWith => __$AlarmEntityCopyWithImpl<_AlarmEntity>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AlarmEntity&&(identical(other.id, id) || other.id == id)); +} + + +@override +int get hashCode => Object.hash(runtimeType,id); + +@override +String toString() { + return 'AlarmEntity(id: $id)'; +} + + +} + +/// @nodoc +abstract mixin class _$AlarmEntityCopyWith<$Res> implements $AlarmEntityCopyWith<$Res> { + factory _$AlarmEntityCopyWith(_AlarmEntity value, $Res Function(_AlarmEntity) _then) = __$AlarmEntityCopyWithImpl; +@override @useResult +$Res call({ + String id +}); + + + + +} +/// @nodoc +class __$AlarmEntityCopyWithImpl<$Res> + implements _$AlarmEntityCopyWith<$Res> { + __$AlarmEntityCopyWithImpl(this._self, this._then); + + final _AlarmEntity _self; + final $Res Function(_AlarmEntity) _then; + +/// Create a copy of AlarmEntity +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,}) { + return _then(_AlarmEntity( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case.dart b/modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case.dart new file mode 100644 index 00000000..acda4007 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case.dart @@ -0,0 +1,6 @@ + +import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart'; + +abstract class GetAlarmsUseCase { + Future> getAlarms({required String deviceId}); +} diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case_impl.dart b/modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case_impl.dart new file mode 100644 index 00000000..5189c024 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/domain/get_alarms_use_case_impl.dart @@ -0,0 +1,15 @@ +import 'package:settings/src/core/domain/repositories/settings_repository.dart'; +import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart'; +import 'package:settings/src/features/alarm/domain/get_alarms_use_case.dart'; + +class GetAlarmsUseCaseImpl implements GetAlarmsUseCase { + GetAlarmsUseCaseImpl(this._repository); + + final SettingsRepository _repository; + + @override + Future> getAlarms({required String deviceId}) async { + return []; + // return _repository.getAlarms(deviceId: deviceId); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/presentation/alarm_screen.dart b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/alarm_screen.dart new file mode 100644 index 00000000..ab57a8a9 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/alarm_screen.dart @@ -0,0 +1,152 @@ +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:navigation/navigation.dart'; +import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart'; +import 'package:settings/src/features/alarm/presentation/state/alarm_view_model.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +import 'widgets/new_alarm_dialog.dart'; + +class AlarmScreen extends ConsumerWidget { + + final NavigationContract navigationContract; + + const AlarmScreen({ + super.key, + required this.navigationContract, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return LegacyPageLayout( + theme: theme, + title: context.translate(I18n.alarm), + body: Column( + children: [ + Center(child: + Icon(Icons.alarm_outlined, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 240, + ) + ), + SizedBox(height: 36), + Text(context.translate(I18n.alarmsMessage), + textAlign: TextAlign.left, + ), + SizedBox(height: 36), + _AlarmsSection() + ], + ) + ); + } +} + +class _AlarmsSection extends ConsumerWidget { + + const _AlarmsSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final alarms = ref.watch( + alarmViewModelProvider.select((s)=>s.alarms) + ); + + return SingleChildScrollView( + child: Column( + children: alarms.map((alarm) { + if (alarm == null) { + return const _NewAlarmButton(); + } + else{ + return _AlarmButton(alarm: alarm); + } + }).toList() + ), + ); + } +} + +class _NewAlarmButton extends ConsumerWidget { + + const _NewAlarmButton(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return TextButton( + onPressed: (){showDialog(context: context, builder: (context)=>Dialog( + child: NewAlarmDialog() + ));}, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 18, vertical: 12), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundSecondary), + borderRadius: BorderRadius.all(Radius.circular(18)) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(context.translate(I18n.addAlarm), + style: TextStyle( + fontWeight: FontWeight.w500, + color: theme.getColorFor(ThemeCode.textPrimary), + fontSize: 18 + ), + ), + DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: theme.getColorFor(ThemeCode.legacyPrimary) + ), + child: Icon( + Icons.add, + color: theme.getColorFor(ThemeCode.textSecondary), + size: 58, + ), + ) + ], + ), + ), + ); + } + +} + +class _AlarmButton extends ConsumerWidget { + + final AlarmEntity alarm; + + const _AlarmButton({ + required this.alarm, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return TextButton( + onPressed: (){showDialog(context: context, builder: (context)=>Dialog( + child: NewAlarmDialog() + ));}, + child: Container( + padding: EdgeInsets.symmetric(), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundSecondary), + borderRadius: BorderRadius.all(Radius.circular(18)) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + + ], + ), + ), + ); + } + +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/presentation/providers/get_alarms_use_case_provider.dart b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/providers/get_alarms_use_case_provider.dart new file mode 100644 index 00000000..137266e0 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/providers/get_alarms_use_case_provider.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/core/providers/settings_repository_provider.dart'; +import 'package:settings/src/features/alarm/domain/get_alarms_use_case.dart'; +import 'package:settings/src/features/alarm/domain/get_alarms_use_case_impl.dart'; + +final getAlarmsUseCaseProvider = Provider.autoDispose((ref) { + final settingsRepository = ref.read(settingsRepositoryProvider); + return GetAlarmsUseCaseImpl(settingsRepository); +}); diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_model.dart b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_model.dart new file mode 100644 index 00000000..084ce46a --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_model.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:legacy_shared/legacy_shared.dart'; +import 'package:settings/src/features/alarm/domain/get_alarms_use_case.dart'; +import 'package:settings/src/features/alarm/presentation/providers/get_alarms_use_case_provider.dart'; +import 'package:settings/src/features/alarm/presentation/state/alarm_view_state.dart'; + +import '../../domain/entities/alarm_entity.dart'; + +final alarmViewModelProvider = +NotifierProvider.autoDispose( + AlarmViewModel.new, +); + +class AlarmViewModel extends Notifier { + late final GetAlarmsUseCase _getAlarmsUseCase; + + // late final UserEntity loggedUser; + + @override + AlarmViewState build() { + _getAlarmsUseCase = ref.read(getAlarmsUseCaseProvider); + + Future.microtask(() => load()); + + return const AlarmViewState(); + } + + Future load() async { + final device = ref.read(selectedDeviceProvider); + + // final alarms = await _getAlarmsUseCase.getAlarms(deviceId: device.identificator); + // setAlarms(alarms); + } + + Future setAlarms(List alarms) async { + state = state.copyWith( + alarms: alarms, + isLoading: false, + ); + } + + void setAlarmTime(TimeOfDay? value) { + if (value == null) return; + if (state.alarmTime == value) return; + + state = state.copyWith( + alarmTime: value, + ); + } + + void setDateOption(String value) { + if (state.dateOption == value) return; + + state = state.copyWith( + dateOption: value, + ); + } + + void toggleAlarmDay(int index) { + List alarmDays = state.alarmDays.toList(); + + alarmDays[index] = !alarmDays[index]; + + state = state.copyWith( + alarmDays: alarmDays, + ); + } + + Future setAlarm() async { + + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.dart b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.dart new file mode 100644 index 00000000..e940455a --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart'; + +part 'alarm_view_state.freezed.dart'; + +@freezed +abstract class AlarmViewState with _$AlarmViewState { + const factory AlarmViewState({ + @Default([null, null, null]) List alarms, + @Default(TimeOfDay(hour: 0, minute: 0)) TimeOfDay alarmTime, + @Default('ONCE') String dateOption, + @Default([false, false, false, false, false, false, false]) List alarmDays, + @Default(true) bool isLoading, + @Default('') String errorMessage, + }) = _AlarmViewState; +} diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.freezed.dart b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.freezed.dart new file mode 100644 index 00000000..210b92c5 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/state/alarm_view_state.freezed.dart @@ -0,0 +1,298 @@ +// 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 'alarm_view_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$AlarmViewState { + + List get alarms; TimeOfDay get alarmTime; String get dateOption; List get alarmDays; bool get isLoading; String get errorMessage; +/// Create a copy of AlarmViewState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AlarmViewStateCopyWith get copyWith => _$AlarmViewStateCopyWithImpl(this as AlarmViewState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AlarmViewState&&const DeepCollectionEquality().equals(other.alarms, alarms)&&(identical(other.alarmTime, alarmTime) || other.alarmTime == alarmTime)&&(identical(other.dateOption, dateOption) || other.dateOption == dateOption)&&const DeepCollectionEquality().equals(other.alarmDays, alarmDays)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(alarms),alarmTime,dateOption,const DeepCollectionEquality().hash(alarmDays),isLoading,errorMessage); + +@override +String toString() { + return 'AlarmViewState(alarms: $alarms, alarmTime: $alarmTime, dateOption: $dateOption, alarmDays: $alarmDays, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class $AlarmViewStateCopyWith<$Res> { + factory $AlarmViewStateCopyWith(AlarmViewState value, $Res Function(AlarmViewState) _then) = _$AlarmViewStateCopyWithImpl; +@useResult +$Res call({ + List alarms, TimeOfDay alarmTime, String dateOption, List alarmDays, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class _$AlarmViewStateCopyWithImpl<$Res> + implements $AlarmViewStateCopyWith<$Res> { + _$AlarmViewStateCopyWithImpl(this._self, this._then); + + final AlarmViewState _self; + final $Res Function(AlarmViewState) _then; + +/// Create a copy of AlarmViewState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? alarms = null,Object? alarmTime = null,Object? dateOption = null,Object? alarmDays = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_self.copyWith( +alarms: null == alarms ? _self.alarms : alarms // ignore: cast_nullable_to_non_nullable +as List,alarmTime: null == alarmTime ? _self.alarmTime : alarmTime // ignore: cast_nullable_to_non_nullable +as TimeOfDay,dateOption: null == dateOption ? _self.dateOption : dateOption // ignore: cast_nullable_to_non_nullable +as String,alarmDays: null == alarmDays ? _self.alarmDays : alarmDays // ignore: cast_nullable_to_non_nullable +as List,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [AlarmViewState]. +extension AlarmViewStatePatterns on AlarmViewState { +/// 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 Function( _AlarmViewState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AlarmViewState() 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 Function( _AlarmViewState value) $default,){ +final _that = this; +switch (_that) { +case _AlarmViewState(): +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? Function( _AlarmViewState value)? $default,){ +final _that = this; +switch (_that) { +case _AlarmViewState() 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 Function( List alarms, TimeOfDay alarmTime, String dateOption, List alarmDays, bool isLoading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AlarmViewState() when $default != null: +return $default(_that.alarms,_that.alarmTime,_that.dateOption,_that.alarmDays,_that.isLoading,_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 Function( List alarms, TimeOfDay alarmTime, String dateOption, List alarmDays, bool isLoading, String errorMessage) $default,) {final _that = this; +switch (_that) { +case _AlarmViewState(): +return $default(_that.alarms,_that.alarmTime,_that.dateOption,_that.alarmDays,_that.isLoading,_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? Function( List alarms, TimeOfDay alarmTime, String dateOption, List alarmDays, bool isLoading, String errorMessage)? $default,) {final _that = this; +switch (_that) { +case _AlarmViewState() when $default != null: +return $default(_that.alarms,_that.alarmTime,_that.dateOption,_that.alarmDays,_that.isLoading,_that.errorMessage);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _AlarmViewState implements AlarmViewState { + const _AlarmViewState({final List alarms = const [null, null, null], this.alarmTime = const TimeOfDay(hour: 0, minute: 0), this.dateOption = 'ONCE', final List alarmDays = const [false, false, false, false, false, false, false], this.isLoading = true, this.errorMessage = ''}): _alarms = alarms,_alarmDays = alarmDays; + + + final List _alarms; +@override@JsonKey() List get alarms { + if (_alarms is EqualUnmodifiableListView) return _alarms; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_alarms); +} + +@override@JsonKey() final TimeOfDay alarmTime; +@override@JsonKey() final String dateOption; + final List _alarmDays; +@override@JsonKey() List get alarmDays { + if (_alarmDays is EqualUnmodifiableListView) return _alarmDays; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_alarmDays); +} + +@override@JsonKey() final bool isLoading; +@override@JsonKey() final String errorMessage; + +/// Create a copy of AlarmViewState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AlarmViewStateCopyWith<_AlarmViewState> get copyWith => __$AlarmViewStateCopyWithImpl<_AlarmViewState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AlarmViewState&&const DeepCollectionEquality().equals(other._alarms, _alarms)&&(identical(other.alarmTime, alarmTime) || other.alarmTime == alarmTime)&&(identical(other.dateOption, dateOption) || other.dateOption == dateOption)&&const DeepCollectionEquality().equals(other._alarmDays, _alarmDays)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_alarms),alarmTime,dateOption,const DeepCollectionEquality().hash(_alarmDays),isLoading,errorMessage); + +@override +String toString() { + return 'AlarmViewState(alarms: $alarms, alarmTime: $alarmTime, dateOption: $dateOption, alarmDays: $alarmDays, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class _$AlarmViewStateCopyWith<$Res> implements $AlarmViewStateCopyWith<$Res> { + factory _$AlarmViewStateCopyWith(_AlarmViewState value, $Res Function(_AlarmViewState) _then) = __$AlarmViewStateCopyWithImpl; +@override @useResult +$Res call({ + List alarms, TimeOfDay alarmTime, String dateOption, List alarmDays, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class __$AlarmViewStateCopyWithImpl<$Res> + implements _$AlarmViewStateCopyWith<$Res> { + __$AlarmViewStateCopyWithImpl(this._self, this._then); + + final _AlarmViewState _self; + final $Res Function(_AlarmViewState) _then; + +/// Create a copy of AlarmViewState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? alarms = null,Object? alarmTime = null,Object? dateOption = null,Object? alarmDays = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_AlarmViewState( +alarms: null == alarms ? _self._alarms : alarms // ignore: cast_nullable_to_non_nullable +as List,alarmTime: null == alarmTime ? _self.alarmTime : alarmTime // ignore: cast_nullable_to_non_nullable +as TimeOfDay,dateOption: null == dateOption ? _self.dateOption : dateOption // ignore: cast_nullable_to_non_nullable +as String,alarmDays: null == alarmDays ? _self._alarmDays : alarmDays // ignore: cast_nullable_to_non_nullable +as List,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/modules/legacy/modules/settings/lib/src/features/alarm/presentation/widgets/new_alarm_dialog.dart b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/widgets/new_alarm_dialog.dart new file mode 100644 index 00000000..8e35d41c --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/alarm/presentation/widgets/new_alarm_dialog.dart @@ -0,0 +1,245 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/features/alarm/presentation/state/alarm_view_model.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +class NewAlarmDialog extends ConsumerWidget { + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return Container( + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundSecondary), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + height: 290, + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _Header(theme: theme), + const _TimeSection(), + const _DaysSection(), + const _SaveSection(), + ], + ), + ); + } +} + +class _Header extends StatelessWidget { + + final ThemePort theme; + + const _Header({ + required this.theme, + }); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Center(child: Text(context.translate(I18n.alarmSettings).toUpperCase(), + style: TextStyle( + color: theme.getColorFor(ThemeCode.legacyPrimary) + ), + )), + Align( + alignment: Alignment.topRight, + child: IconButton( + onPressed: (){Navigator.pop(context);}, + padding: EdgeInsets.zero, + icon: Icon( + Icons.close, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 18, + ) + ), + ) + ], + ); + } +} + +class _TimeSection extends ConsumerWidget { + + const _TimeSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + final vm = ref.read(alarmViewModelProvider.notifier); + final alarmTime = ref.watch( + alarmViewModelProvider.select((s) => s.alarmTime) + ); + final dateOption = ref.watch( + alarmViewModelProvider.select((s) => s.dateOption) + ); + + Map dateOptions = { + 'ONCE': context.translate(I18n.once), + 'DAILY': context.translate(I18n.daily), + 'SELECT': context.translate(I18n.selectDay), + }; + + return Column( + children: [ + Text(context.translate(I18n.setDateTime), + style: TextStyle( + color: theme.getColorFor(ThemeCode.legacyPrimary) + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () async { + final time = await showTimePicker( + context: context, + initialTime: const TimeOfDay(hour: 00, minute: 00), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true), + child: child!, + ); + }, + ); + + vm.setAlarmTime(time); + }, + child: Container( + decoration: BoxDecoration( + border: Border.fromBorderSide(BorderSide( + color: theme.getColorFor(ThemeCode.legacyPrimary) + )), + borderRadius: BorderRadius.all(Radius.circular(14)) + ), + width: 128, + height: 50, + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: Center( + child: Text('${alarmTime.hour.toString().padLeft(2, '0')}:' + '${alarmTime.minute.toString().padLeft(2, '0')}', + style: TextStyle( + color: theme.getColorFor(ThemeCode.legacyPrimary) + ), + ), + ), + ), + ), + Expanded(child: CustomDropdown( + items: dateOptions.values + .map(Text.new) + .toList(growable: false), + values: dateOptions.keys.toList(growable: false), + value: dateOption, + onChanged: (value){ + vm.setDateOption(value); + }, + height: 50, + color: theme.getColorFor(ThemeCode.legacyPrimary), + )) + ], + ) + ], + ); + } +} + +class _DaysSection extends ConsumerWidget { + + const _DaysSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + + final theme = ref.read(themePortProvider); + + final vm = ref.read(alarmViewModelProvider.notifier); + final dateOption = ref.watch( + alarmViewModelProvider.select((s)=>s.dateOption) + ); + final alarmDays = ref.watch( + alarmViewModelProvider.select((s)=>s.alarmDays) + ); + + final days = [ + 'LUN', + 'MAR', + 'MIE', + 'JUE', + 'VIE', + 'SAB', + 'DOM', + ]; + + if (dateOption == 'SELECT') { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(days.length, (int index){ + return TextButton( + onPressed: (){ + vm.toggleAlarmDay(index); + }, + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.zero), + minimumSize: WidgetStatePropertyAll(Size.zero), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: alarmDays[index] + ? theme.getColorFor(ThemeCode.legacyPrimary) + : theme.getColorFor(ThemeCode.backgroundPrimary), + border: Border.fromBorderSide( + BorderSide(color: theme.getColorFor(ThemeCode.legacyPrimary)) + ), + ), + padding: EdgeInsets.all(8), + child: Text(days[index], + style: TextStyle( + color: alarmDays[index] + ? theme.getColorFor(ThemeCode.textSecondary) + : theme.getColorFor(ThemeCode.textPrimary), + fontSize: 12 + ), + ), + ) + ); + }) + ); + } + else { + return SizedBox.shrink(); + } + } + +} + +class _SaveSection extends ConsumerWidget { + + const _SaveSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + final vm = ref.read(alarmViewModelProvider.notifier); + + return PrimaryButton( + onPressed: (){ + vm.setAlarm(); + + Navigator.pop(context); + }, + text: context.translate(I18n.save), + color: theme.getColorFor(ThemeCode.legacyPrimary) + ); + } +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case.dart new file mode 100644 index 00000000..bd1edc4e --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case.dart @@ -0,0 +1,4 @@ + +abstract class ShutdownUseCase { + Future shutdown({required String deviceId}); +} diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case_impl.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case_impl.dart new file mode 100644 index 00000000..ec1cdc83 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/domain/shutdown_use_case_impl.dart @@ -0,0 +1,15 @@ +import 'package:settings/src/core/domain/repositories/settings_repository.dart'; + +import 'shutdown_use_case.dart'; + +class ShutdownUseCaseImpl implements ShutdownUseCase { + ShutdownUseCaseImpl(this._repository); + + final SettingsRepository _repository; + + @override + Future shutdown({required String deviceId}) async { + return; + // return _repository.shutdown(deviceId: deviceId); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/providers/shutdown_use_case_provider.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/providers/shutdown_use_case_provider.dart new file mode 100644 index 00000000..bf9be79c --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/providers/shutdown_use_case_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/core/providers/settings_repository_provider.dart'; + +import '../../domain/shutdown_use_case.dart'; +import '../../domain/shutdown_use_case_impl.dart'; + +final shutdownUseCaseProvider = Provider.autoDispose((ref) { + final settingsRepository = ref.read(settingsRepositoryProvider); + return ShutdownUseCaseImpl(settingsRepository); +}); diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/remote_management_screen.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/remote_management_screen.dart new file mode 100644 index 00000000..d405f2f3 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/remote_management_screen.dart @@ -0,0 +1,150 @@ +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:navigation/navigation.dart'; +import 'package:settings/src/features/remote_management/presentation/state/remote_management_view_model.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +import 'widgets/confirm_dialog.dart'; + +class RemoteManagementScreen extends ConsumerWidget { + + final NavigationContract navigationContract; + + const RemoteManagementScreen({ + super.key, + required this.navigationContract, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return LegacyPageLayout( + theme: theme, + title: context.translate(I18n.remoteManagement), + body: Column( + children: [ + Center(child: + Icon(Icons.settings_remote_outlined, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 180, + ) + ), + SizedBox(height: 36), + _OptionsSection() + ], + ) + ); + } +} + +class _OptionsSection extends ConsumerWidget { + + const _OptionsSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final vm = ref.read(remoteManagementViewModelProvider.notifier); + + return SingleChildScrollView( + child: Column( + children: [ + _SectionButton( + title: context.translate(I18n.remoteTurnOff), + subtitle: context.translate(I18n.remoteTurnOffMessage), + icon: Icons.settings_power_outlined, + onPressed: () {showDialog(context: context, builder: (context)=>Dialog( + child: ConfirmDialog( + title: context.translate(I18n.remoteTurnOff), + message: context.translate(I18n.remoteTurnOffConfirm), + onConfirm: () { + vm.turnOff(); + Navigator.pop(context); + } + ), + ));}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.remoteRestart), + subtitle: context.translate(I18n.remoteRestartMessage), + icon: Icons.refresh_outlined, + onPressed: () {showDialog(context: context, builder: (context)=>Dialog( + child: ConfirmDialog( + title: context.translate(I18n.remoteRestart), + message: context.translate(I18n.remoteRestartConfirm), + onConfirm: () { + vm.restart(); + Navigator.pop(context); + } + ), + ));}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.remoteFactoryReset), + subtitle: context.translate(I18n.remoteFactoryResetMessage), + icon: Icons.restart_alt_outlined, + onPressed: () {showDialog(context: context, builder: (context)=>Dialog( + child: ConfirmDialog( + title: context.translate(I18n.remoteFactoryReset), + message: context.translate(I18n.remoteFactoryResetConfirm), + onConfirm: () { + vm.factoryReset(); + Navigator.pop(context); + } + ), + ));}, + ), + ] + ), + ); + } +} + +class _SectionButton extends ConsumerWidget { + + final String title; + final String subtitle; + final IconData icon; + final VoidCallback onPressed; + + const _SectionButton({ + required this.title, + required this.subtitle, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return SectionButton( + onPressed: onPressed, + icon: Icon(icon, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 48, + ), + body: Column( + children: [ + Text(title, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14 + ) + ), + Text(subtitle, + style: TextStyle( + fontSize: 12, + color: theme.getColorFor(ThemeCode.textPrimary) + ), + ) + ], + ) + ); + } + +} diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_model.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_model.dart new file mode 100644 index 00000000..238ac168 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_model.dart @@ -0,0 +1,82 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:legacy_shared/legacy_shared.dart'; +import 'package:sf_shared/sf_shared.dart'; +import 'remote_management_view_state.dart'; + +final remoteManagementViewModelProvider = +NotifierProvider.autoDispose( + RemoteManagementViewModel.new, +); + +class RemoteManagementViewModel extends Notifier { + // late final TurnOffUsecase _turnOffUseCase; + // late final RestartUsecase _restartUseCase; + // late final FactoryResetUseCase _factoryResetUseCase; + + @override + RemoteManagementViewState build() { + // _turnOffUseCase = ref.read(turnOffUseCaseProvider); + // _restartUseCase = ref.read(restartUseCaseProvider); + // _factoryResetUseCase = ref.read(factoryResetUseCaseProvider); + + Future.microtask(() => load()); + + return const RemoteManagementViewState(); + } + + Future load() async { + final device = ref.read(selectedDeviceProvider); + + setDevice(device!); + } + + void setDevice(DeviceEntity device) { + state = state.copyWith( + deviceId: device.identificator, + isLoading: false, + ); + } + + Future turnOff() async { + try { + state = state.copyWith( + isLoading: true, + ); + // _turnOffUseCase.turnOff(); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } + + Future restart() async { + try { + state = state.copyWith( + isLoading: true, + ); + // _restartUseCase.restart(); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } + + Future factoryReset() async { + try { + state = state.copyWith( + isLoading: true, + ); + // _factoryResetUseCase.fatoryReset(); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.dart new file mode 100644 index 00000000..a9e96e0a --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart'; + +part 'remote_management_view_state.freezed.dart'; + +@freezed +abstract class RemoteManagementViewState with _$RemoteManagementViewState { + const factory RemoteManagementViewState({ + @Default('') String deviceId, + @Default(true) bool isLoading, + @Default('') String errorMessage, + }) = _RemoteManagementViewState; +} diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.freezed.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.freezed.dart new file mode 100644 index 00000000..576c74c5 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/state/remote_management_view_state.freezed.dart @@ -0,0 +1,277 @@ +// 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 'remote_management_view_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$RemoteManagementViewState { + + String get deviceId; bool get isLoading; String get errorMessage; +/// Create a copy of RemoteManagementViewState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$RemoteManagementViewStateCopyWith get copyWith => _$RemoteManagementViewStateCopyWithImpl(this as RemoteManagementViewState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteManagementViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,isLoading,errorMessage); + +@override +String toString() { + return 'RemoteManagementViewState(deviceId: $deviceId, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class $RemoteManagementViewStateCopyWith<$Res> { + factory $RemoteManagementViewStateCopyWith(RemoteManagementViewState value, $Res Function(RemoteManagementViewState) _then) = _$RemoteManagementViewStateCopyWithImpl; +@useResult +$Res call({ + String deviceId, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class _$RemoteManagementViewStateCopyWithImpl<$Res> + implements $RemoteManagementViewStateCopyWith<$Res> { + _$RemoteManagementViewStateCopyWithImpl(this._self, this._then); + + final RemoteManagementViewState _self; + final $Res Function(RemoteManagementViewState) _then; + +/// Create a copy of RemoteManagementViewState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_self.copyWith( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [RemoteManagementViewState]. +extension RemoteManagementViewStatePatterns on RemoteManagementViewState { +/// 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 Function( _RemoteManagementViewState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _RemoteManagementViewState() 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 Function( _RemoteManagementViewState value) $default,){ +final _that = this; +switch (_that) { +case _RemoteManagementViewState(): +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? Function( _RemoteManagementViewState value)? $default,){ +final _that = this; +switch (_that) { +case _RemoteManagementViewState() 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 Function( String deviceId, bool isLoading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _RemoteManagementViewState() when $default != null: +return $default(_that.deviceId,_that.isLoading,_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 Function( String deviceId, bool isLoading, String errorMessage) $default,) {final _that = this; +switch (_that) { +case _RemoteManagementViewState(): +return $default(_that.deviceId,_that.isLoading,_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? Function( String deviceId, bool isLoading, String errorMessage)? $default,) {final _that = this; +switch (_that) { +case _RemoteManagementViewState() when $default != null: +return $default(_that.deviceId,_that.isLoading,_that.errorMessage);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _RemoteManagementViewState implements RemoteManagementViewState { + const _RemoteManagementViewState({this.deviceId = '', this.isLoading = true, this.errorMessage = ''}); + + +@override@JsonKey() final String deviceId; +@override@JsonKey() final bool isLoading; +@override@JsonKey() final String errorMessage; + +/// Create a copy of RemoteManagementViewState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$RemoteManagementViewStateCopyWith<_RemoteManagementViewState> get copyWith => __$RemoteManagementViewStateCopyWithImpl<_RemoteManagementViewState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteManagementViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,isLoading,errorMessage); + +@override +String toString() { + return 'RemoteManagementViewState(deviceId: $deviceId, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class _$RemoteManagementViewStateCopyWith<$Res> implements $RemoteManagementViewStateCopyWith<$Res> { + factory _$RemoteManagementViewStateCopyWith(_RemoteManagementViewState value, $Res Function(_RemoteManagementViewState) _then) = __$RemoteManagementViewStateCopyWithImpl; +@override @useResult +$Res call({ + String deviceId, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class __$RemoteManagementViewStateCopyWithImpl<$Res> + implements _$RemoteManagementViewStateCopyWith<$Res> { + __$RemoteManagementViewStateCopyWithImpl(this._self, this._then); + + final _RemoteManagementViewState _self; + final $Res Function(_RemoteManagementViewState) _then; + +/// Create a copy of RemoteManagementViewState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_RemoteManagementViewState( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/widgets/confirm_dialog.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/widgets/confirm_dialog.dart new file mode 100644 index 00000000..07380d50 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/presentation/widgets/confirm_dialog.dart @@ -0,0 +1,59 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +class ConfirmDialog extends ConsumerWidget { + + final String title; + final String message; + final VoidCallback onConfirm; + + const ConfirmDialog({ + required this.title, + required this.message, + required this.onConfirm, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return Container( + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + height: 180, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16 + ), + ), + Text(message), + Row( + children: [ + Expanded(child: PrimaryButton( + onPressed: (){Navigator.pop(context);}, + text: context.translate(I18n.cancel), + color: theme.getColorFor(ThemeCode.legacyPrimary), + )), + SizedBox(width: 14), + Expanded(child: PrimaryButton( + onPressed: onConfirm, + text: context.translate(I18n.accept), + color: theme.getColorFor(ThemeCode.legacyPrimary), + )), + ], + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/remote_management/remote_management_builder.dart b/modules/legacy/modules/settings/lib/src/features/remote_management/remote_management_builder.dart new file mode 100644 index 00000000..3cbbb200 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/remote_management/remote_management_builder.dart @@ -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/remote_management_screen.dart'; + +class RemoteManagementBuilder { + const RemoteManagementBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: RemoteManagementScreen(navigationContract: navigationContract), + ); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/settings/presentation/settings_screen.dart b/modules/legacy/modules/settings/lib/src/features/settings/presentation/settings_screen.dart new file mode 100644 index 00000000..18222c18 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/settings/presentation/settings_screen.dart @@ -0,0 +1,181 @@ +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:navigation/navigation.dart'; +import 'package:sf_localizations/sf_localizations.dart'; +import 'package:utils/utils.dart'; + +class SettingsScreen extends ConsumerWidget { + + final NavigationContract navigationContract; + + const SettingsScreen({ + super.key, + required this.navigationContract, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return LegacyPageLayout( + theme: theme, + title: context.translate(I18n.settings), + body: SingleChildScrollView( + child: Column( + children: [ + _AppSectionButton( + onPressed: (){navigationContract.pushTo(AppRoutes.alarm);}, + icon: Icons.notifications_outlined, + text: context.translate(I18n.alarm) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.apps_rounded, + text: context.translate(I18n.appStore) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.phone_outlined, + text: context.translate(I18n.blockPhone) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.check, + text: context.translate(I18n.timezone) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.translate_outlined, + text: context.translate(I18n.language) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.nightlight_outlined, + text: context.translate(I18n.battery) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){navigationContract.pushTo(AppRoutes.remoteManagement);}, + icon: Icons.settings_remote_outlined, + text: context.translate(I18n.remoteManagement) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.message_outlined, + text: context.translate(I18n.legacyNotifications) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.sms_outlined, + text: context.translate(I18n.smsAlert) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){navigationContract.pushTo(AppRoutes.sosContacts);}, + icon: Icons.perm_contact_calendar_outlined, + text: context.translate(I18n.sosContacts) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){navigationContract.pushTo(AppRoutes.sound);}, + icon: Icons.volume_up_outlined, + text: context.translate(I18n.sound) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.wifi_find_outlined, + text: context.translate(I18n.wifiSettings) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.settings_power_outlined, + text: context.translate(I18n.remoteOnOff) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.dashboard_customize_outlined, + text: context.translate(I18n.disableFunctions) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + _AppSectionButton( + onPressed: (){}, + icon: Icons.share_arrival_time_outlined, + text: context.translate(I18n.syncClock) + ), + SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)), + ], + ), + ) + ); + } + +} + +class _AppSectionButton extends ConsumerWidget { + + final GestureTapCallback onPressed; + final IconData icon; + final String text; + + const _AppSectionButton({ + required this.onPressed, + required this.icon, + required this.text, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return GestureDetector( + onTap: onPressed, + child: Container( + padding: SizeUtils.getByScreen( + small: EdgeInsets.symmetric(horizontal: 22, vertical: 14), + big: EdgeInsets.symmetric(horizontal: 21, vertical: 12) + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(SizeUtils.getByScreen(small: 12, big: 18))), + color: theme.getColorFor(ThemeCode.backgroundSecondary), + ), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: theme.getColorFor(ThemeCode.backgroundPrimary), + ), + padding: EdgeInsets.all(SizeUtils.getByScreen(small: 0, big: 0)), + child: Icon(icon, + size: SizeUtils.getByScreen(small: 52, big: 48), + color: Color(0xFF588EA5), + weight: 30, + ), + ), + SizedBox(width: SizeUtils.getByScreen(small: 16, big: 15)), + Expanded( + child: Text(context.translate(text), + style: TextStyle( + fontSize: SizeUtils.getByScreen(small: 18, big: 19), + fontWeight: FontWeight.w500 + ) + ) + ) + ], + ), + ) + ); + } +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/settings/settings_builder.dart b/modules/legacy/modules/settings/lib/src/features/settings/settings_builder.dart new file mode 100644 index 00000000..52f135e3 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/settings/settings_builder.dart @@ -0,0 +1,18 @@ +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 'package:settings/src/features/settings/presentation/settings_screen.dart'; + +class SettingsBuilder { + const SettingsBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: SettingsScreen(navigationContract: navigationContract), + ); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case.dart new file mode 100644 index 00000000..727d2bf6 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case.dart @@ -0,0 +1,4 @@ + +abstract class SetContactsUseCase { + Future setContacts({required String deviceId, required String phone}); +} diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case_impl.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case_impl.dart new file mode 100644 index 00000000..93073ce6 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/domain/set_contacts_use_case_impl.dart @@ -0,0 +1,15 @@ +import 'package:settings/src/core/domain/repositories/settings_repository.dart'; + +import 'set_contacts_use_case.dart'; + +class SetContactsUseCaseImpl implements SetContactsUseCase { + SetContactsUseCaseImpl(this._repository); + + final SettingsRepository _repository; + + @override + Future setContacts({required String deviceId, required String phone}) async { + return; + // return _repository.setContact(deviceId: deviceId, phone: phone); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/providers/set_contacts_use_case_provider.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/providers/set_contacts_use_case_provider.dart new file mode 100644 index 00000000..ae9ebd7e --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/providers/set_contacts_use_case_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/core/providers/settings_repository_provider.dart'; + +import '../../domain/set_contacts_use_case.dart'; +import '../../domain/set_contacts_use_case_impl.dart'; + +final setContactsUseCaseProvider = Provider.autoDispose((ref) { + final settingsRepository = ref.read(settingsRepositoryProvider); + return SetContactsUseCaseImpl(settingsRepository); +}); diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/sos_contacts_screen.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/sos_contacts_screen.dart new file mode 100644 index 00000000..ee6358a7 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/sos_contacts_screen.dart @@ -0,0 +1,132 @@ +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:navigation/navigation.dart'; +import 'package:settings/src/features/sos_contacts/presentation/widgets/edit_phone_dialog.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +import 'state/sos_contacts_view_model.dart'; + +class SosContactsScreen extends ConsumerWidget { + + final NavigationContract navigationContract; + + const SosContactsScreen({ + super.key, + required this.navigationContract, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return LegacyPageLayout( + theme: theme, + title: context.translate(I18n.sosContacts), + body: Column( + children: [ + _ContactsSection() + ], + ), + footer: _SaveSection(), + ); + } +} + +class _ContactsSection extends ConsumerWidget { + + const _ContactsSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final vm = ref.read(sosContactsViewModelProvider.notifier); + + final contacts = ref.watch( + sosContactsViewModelProvider.select((s)=>s.contacts) + ); + + return Column( + children: List.generate(contacts.length, (index) => + _ContactButton( + index: index, + phone: contacts[index] + ), + ) + ); + } +} + +class _ContactButton extends ConsumerWidget { + + final int index; + final String phone; + + const _ContactButton({ + required this.index, + required this.phone, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return SectionButton( + onPressed: (){showDialog(context: context, builder: (context)=>Dialog( + child: EditPhoneDialog(index: index, phone: phone) + ));}, + icon: Icon(Icons.perm_contact_calendar_outlined, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 36, + ), + iconPadding: 8, + body: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + Text(context.translate(I18n.number, args: {'num': index + 1}), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: theme.getColorFor(ThemeCode.textPrimary) + ) + ), + Text(phone, + style: TextStyle( + fontSize: 12, + color: theme.getColorFor(ThemeCode.textTertiary) + ), + ) + ], + ), + Icon(Icons.settings_phone_outlined, + color: theme.getColorFor(ThemeCode.textTertiary), + size: 24, + ) + ], + ) + ); + } +} + +class _SaveSection extends ConsumerWidget { + + const _SaveSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + final vm = ref.read(sosContactsViewModelProvider.notifier); + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: PrimaryButton( + onPressed: vm.submit, + text: context.translate(I18n.save), + color: theme.getColorFor(ThemeCode.legacyPrimary) + ), + ); + } +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_model.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_model.dart new file mode 100644 index 00000000..677be768 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_model.dart @@ -0,0 +1,119 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:legacy_shared/legacy_shared.dart'; +import 'package:sf_shared/sf_shared.dart'; + +import 'sos_contacts_view_state.dart'; + +final sosContactsViewModelProvider = +NotifierProvider.autoDispose( + SosContactsViewModel.new, +); + +class SosContactsViewModel extends Notifier { + + static final RegExp _phoneRegex = RegExp(r'^\+?\d{6,15}$'); + + // late final SetContactsUseCase _setContactsUseCase; + late final TextEditingController phoneController; + + @override + SosContactsViewState build() { + // _setContactsUseCase = ref.read(setContactsUseCaseProvider); + + phoneController = TextEditingController(); + phoneController.addListener(_onPhoneChanged); + + Future.microtask(() => load()); + + return const SosContactsViewState(); + } + + Future load() async { + final device = ref.read(selectedDeviceProvider); + + setDevice(device!); + } + + void setDevice(DeviceEntity device) { + state = state.copyWith( + deviceId: device.identificator, + isLoading: false, + ); + } + + void _onPhoneChanged() { + final value = phoneController.text; + + if (value == state.phone) return; + + state = state.copyWith( + phone: value, + errorMessage: '', + ); + } + + void setContactPhone(int index) { + List contacts = state.contacts.toList(); + final phone = state.phone; + + if (phone.isEmpty){ + state = state.copyWith(errorMessage: 'errorMessagePhoneIsEmpty'); + return; + } + if (!_phoneRegex.hasMatch(phone)) { + state = state.copyWith(errorMessage: 'errorMessagePhoneIsInvalid'); + return; + } + + contacts[index] = phone; + + phoneController.text = ''; + state = state.copyWith( + contacts: contacts, + phone: '', + ); + } + + Future submit() async { + try { + state = state.copyWith( + isLoading: true, + ); + // _setContactsUseCase.setContacts(); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } + + Future restart() async { + try { + state = state.copyWith( + isLoading: true, + ); + // _restartUseCase.restart(); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } + + Future factoryReset() async { + try { + state = state.copyWith( + isLoading: true, + ); + // _factoryResetUseCase.fatoryReset(); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.dart new file mode 100644 index 00000000..12de61b5 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:legacy_shared/legacy_shared.dart'; +import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart'; + +part 'sos_contacts_view_state.freezed.dart'; + +@freezed +abstract class SosContactsViewState with _$SosContactsViewState { + const factory SosContactsViewState({ + @Default('') String deviceId, + @Default(['', '', '']) List contacts, + @Default('') String phone, + @Default(true) bool isLoading, + @Default('') String errorMessage, + }) = _SosContactsViewState; +} diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.freezed.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.freezed.dart new file mode 100644 index 00000000..c5e429b3 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/state/sos_contacts_view_state.freezed.dart @@ -0,0 +1,289 @@ +// 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 'sos_contacts_view_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$SosContactsViewState { + + String get deviceId; List get contacts; String get phone; bool get isLoading; String get errorMessage; +/// Create a copy of SosContactsViewState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SosContactsViewStateCopyWith get copyWith => _$SosContactsViewStateCopyWithImpl(this as SosContactsViewState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SosContactsViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&const DeepCollectionEquality().equals(other.contacts, contacts)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,const DeepCollectionEquality().hash(contacts),phone,isLoading,errorMessage); + +@override +String toString() { + return 'SosContactsViewState(deviceId: $deviceId, contacts: $contacts, phone: $phone, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class $SosContactsViewStateCopyWith<$Res> { + factory $SosContactsViewStateCopyWith(SosContactsViewState value, $Res Function(SosContactsViewState) _then) = _$SosContactsViewStateCopyWithImpl; +@useResult +$Res call({ + String deviceId, List contacts, String phone, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class _$SosContactsViewStateCopyWithImpl<$Res> + implements $SosContactsViewStateCopyWith<$Res> { + _$SosContactsViewStateCopyWithImpl(this._self, this._then); + + final SosContactsViewState _self; + final $Res Function(SosContactsViewState) _then; + +/// Create a copy of SosContactsViewState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? contacts = null,Object? phone = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_self.copyWith( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,contacts: null == contacts ? _self.contacts : contacts // ignore: cast_nullable_to_non_nullable +as List,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SosContactsViewState]. +extension SosContactsViewStatePatterns on SosContactsViewState { +/// 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 Function( _SosContactsViewState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SosContactsViewState() 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 Function( _SosContactsViewState value) $default,){ +final _that = this; +switch (_that) { +case _SosContactsViewState(): +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? Function( _SosContactsViewState value)? $default,){ +final _that = this; +switch (_that) { +case _SosContactsViewState() 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 Function( String deviceId, List contacts, String phone, bool isLoading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SosContactsViewState() when $default != null: +return $default(_that.deviceId,_that.contacts,_that.phone,_that.isLoading,_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 Function( String deviceId, List contacts, String phone, bool isLoading, String errorMessage) $default,) {final _that = this; +switch (_that) { +case _SosContactsViewState(): +return $default(_that.deviceId,_that.contacts,_that.phone,_that.isLoading,_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? Function( String deviceId, List contacts, String phone, bool isLoading, String errorMessage)? $default,) {final _that = this; +switch (_that) { +case _SosContactsViewState() when $default != null: +return $default(_that.deviceId,_that.contacts,_that.phone,_that.isLoading,_that.errorMessage);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _SosContactsViewState implements SosContactsViewState { + const _SosContactsViewState({this.deviceId = '', final List contacts = const ['', '', ''], this.phone = '', this.isLoading = true, this.errorMessage = ''}): _contacts = contacts; + + +@override@JsonKey() final String deviceId; + final List _contacts; +@override@JsonKey() List get contacts { + if (_contacts is EqualUnmodifiableListView) return _contacts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_contacts); +} + +@override@JsonKey() final String phone; +@override@JsonKey() final bool isLoading; +@override@JsonKey() final String errorMessage; + +/// Create a copy of SosContactsViewState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SosContactsViewStateCopyWith<_SosContactsViewState> get copyWith => __$SosContactsViewStateCopyWithImpl<_SosContactsViewState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SosContactsViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&const DeepCollectionEquality().equals(other._contacts, _contacts)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,const DeepCollectionEquality().hash(_contacts),phone,isLoading,errorMessage); + +@override +String toString() { + return 'SosContactsViewState(deviceId: $deviceId, contacts: $contacts, phone: $phone, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class _$SosContactsViewStateCopyWith<$Res> implements $SosContactsViewStateCopyWith<$Res> { + factory _$SosContactsViewStateCopyWith(_SosContactsViewState value, $Res Function(_SosContactsViewState) _then) = __$SosContactsViewStateCopyWithImpl; +@override @useResult +$Res call({ + String deviceId, List contacts, String phone, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class __$SosContactsViewStateCopyWithImpl<$Res> + implements _$SosContactsViewStateCopyWith<$Res> { + __$SosContactsViewStateCopyWithImpl(this._self, this._then); + + final _SosContactsViewState _self; + final $Res Function(_SosContactsViewState) _then; + +/// Create a copy of SosContactsViewState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? contacts = null,Object? phone = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_SosContactsViewState( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,contacts: null == contacts ? _self._contacts : contacts // ignore: cast_nullable_to_non_nullable +as List,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/widgets/edit_phone_dialog.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/widgets/edit_phone_dialog.dart new file mode 100644 index 00000000..6fd773a2 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/presentation/widgets/edit_phone_dialog.dart @@ -0,0 +1,92 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/features/sos_contacts/presentation/state/sos_contacts_view_model.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +class EditPhoneDialog extends ConsumerWidget { + + final int index; + final String phone; + + const EditPhoneDialog({ + required this.index, + required this.phone, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + final vm = ref.read(sosContactsViewModelProvider.notifier); + final errorMessage = ref.watch( + sosContactsViewModelProvider.select((s)=>s.errorMessage) + ); + + return Container( + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + height: 220, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Stack( + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton( + onPressed: (){Navigator.pop(context);}, + icon: Icon(Icons.close, + color: theme.getColorFor(ThemeCode.legacyPrimary) + ) + ), + ), + Center( + child: Text( + context.translate(I18n.number, args: {'num': index + 1}).toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: theme.getColorFor(ThemeCode.legacyPrimary), + ), + ), + ), + ], + ), + CustomTextField( + controller: vm.phoneController, + keyboardType: TextInputType.phone, + hint: context.translate(I18n.insertPhone), + ), + if (errorMessage.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + errorMessage, + style: const TextStyle(color: Colors.red, fontSize: 13), + textAlign: TextAlign.center, + ), + ], + PrimaryButton( + onPressed: (){ + vm.setContactPhone(index); + + final errorMessage = ref.read( + sosContactsViewModelProvider.select((s)=>s.errorMessage) + ); + + if (errorMessage.isEmpty){ + Navigator.pop(context); + } + }, + text: context.translate(I18n.save), + color: theme.getColorFor(ThemeCode.legacyPrimary) + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/sos_contacts/sos_contacts_builder.dart b/modules/legacy/modules/settings/lib/src/features/sos_contacts/sos_contacts_builder.dart new file mode 100644 index 00000000..cfcd3d9c --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sos_contacts/sos_contacts_builder.dart @@ -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/sos_contacts_screen.dart'; + +class SosContactsBuilder { + const SosContactsBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: SosContactsScreen(navigationContract: navigationContract), + ); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case.dart b/modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case.dart new file mode 100644 index 00000000..504c8829 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case.dart @@ -0,0 +1,3 @@ +abstract class SetSoundUseCase { + Future setSound({required String deviceId}); +} diff --git a/modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case_impl.dart b/modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case_impl.dart new file mode 100644 index 00000000..0fbbfaf6 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/domain/set_sound_use_case_impl.dart @@ -0,0 +1,16 @@ +import 'package:settings/src/core/domain/repositories/settings_repository.dart'; + +import 'set_sound_use_case.dart'; + + +class SetSoundUseCaseImpl implements SetSoundUseCase { + SetSoundUseCaseImpl(this._repository); + + final SettingsRepository _repository; + + @override + Future setSound({required String deviceId}) async { + return; + // return _repository.setSound(deviceId: deviceId); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sound/presentation/providers/set_sound_use_case_provider.dart b/modules/legacy/modules/settings/lib/src/features/sound/presentation/providers/set_sound_use_case_provider.dart new file mode 100644 index 00000000..9e618619 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/presentation/providers/set_sound_use_case_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/core/providers/settings_repository_provider.dart'; + +import '../../domain/set_sound_use_case.dart'; +import '../../domain/set_sound_use_case_impl.dart'; + +final setSoundUseCaseProvider = Provider.autoDispose((ref) { + final settingsRepository = ref.read(settingsRepositoryProvider); + return SetSoundUseCaseImpl(settingsRepository); +}); diff --git a/modules/legacy/modules/settings/lib/src/features/sound/presentation/sound_screen.dart b/modules/legacy/modules/settings/lib/src/features/sound/presentation/sound_screen.dart new file mode 100644 index 00000000..440de844 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/presentation/sound_screen.dart @@ -0,0 +1,162 @@ +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:navigation/navigation.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +import 'state/sound_view_model.dart'; + +class SoundScreen extends ConsumerWidget { + + final NavigationContract navigationContract; + + const SoundScreen({ + super.key, + required this.navigationContract, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return LegacyPageLayout( + theme: theme, + title: context.translate(I18n.sound), + body: Padding( + padding: EdgeInsets.symmetric(horizontal: 18, vertical: 12), + child: Column( + children: [ + Center(child: + Icon(Icons.volume_up, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 180, + ) + ), + SizedBox(height: 36), + const _OptionsSection() + ], + ), + ), + footer: const _SaveSection(), + ); + } +} + +class _OptionsSection extends ConsumerWidget { + + const _OptionsSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final vm = ref.read(soundViewModelProvider.notifier); + + final soundOption = ref.watch( + soundViewModelProvider.select((s)=>s.soundOption) + ); + + return SingleChildScrollView( + child: Column( + children: [ + _SectionButton( + title: context.translate(I18n.soundAndVibration), + icon: Icons.volume_up_outlined, + active: soundOption == 'SOUND_AND_VIBRATION', + onPressed: () {vm.setSoundOption('SOUND_AND_VIBRATION');}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.soundOnly), + icon: Icons.volume_up_outlined, + active: soundOption == 'SOUND', + onPressed: () {vm.setSoundOption('SOUND');}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.vibrationOnly), + icon: Icons.vibration_outlined, + active: soundOption == 'VIBRATION', + onPressed: () {vm.setSoundOption('VIBRATION');}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.silent), + icon: Icons.volume_mute_outlined, + active: soundOption == 'SILENT', + onPressed: () {vm.setSoundOption('SILENT');}, + ), + ] + ), + ); + } +} + +class _SectionButton extends ConsumerWidget { + + final String title; + final bool active; + final IconData icon; + final VoidCallback onPressed; + + const _SectionButton({ + required this.title, + required this.active, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return SectionButton( + onPressed: onPressed, + icon: Icon(icon, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 36, + ), + iconPadding: 8, + body: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: active + ? theme.getColorFor(ThemeCode.textPrimary) + : theme.getColorFor(ThemeCode.textTertiary), + ) + ), + Switch( + value: active, + onChanged: (_){onPressed();}, + ) + ], + ) + ); + } + +} + +class _SaveSection extends ConsumerWidget { + + const _SaveSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + final vm = ref.read(soundViewModelProvider.notifier); + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: PrimaryButton( + onPressed: vm.submit, + text: context.translate(I18n.save), + color: theme.getColorFor(ThemeCode.legacyPrimary) + ), + ); + } + +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_model.dart b/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_model.dart new file mode 100644 index 00000000..a72738c8 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_model.dart @@ -0,0 +1,62 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:legacy_shared/legacy_shared.dart'; +import 'package:sf_shared/sf_shared.dart'; + +import '../../domain/set_sound_use_case.dart'; +import '../providers/set_sound_use_case_provider.dart'; +import 'sound_view_state.dart'; + +final soundViewModelProvider = +NotifierProvider.autoDispose( + SoundViewModel.new, +); + +class SoundViewModel extends Notifier { + late final SetSoundUseCase _setSoundUseCase; + + @override + SoundViewState build() { + _setSoundUseCase = ref.read(setSoundUseCaseProvider); + + Future.microtask(() => load()); + + return const SoundViewState(); + } + + Future load() async { + final device = ref.read(selectedDeviceProvider); + setDevice(device!); + state = state.copyWith( + soundOption: 'SOUND_AND_VIBRATION', + isLoading: false, + ); + } + + void setDevice(DeviceEntity device) { + state = state.copyWith( + deviceId: device.identificator, + ); + } + + void setSoundOption(String value) { + if (state.soundOption == value) return; + + state = state.copyWith( + soundOption: value + ); + } + + Future submit() async { + try { + state = state.copyWith( + isLoading: true, + ); + _setSoundUseCase.setSound(deviceId: state.deviceId); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.dart b/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.dart new file mode 100644 index 00000000..42a9cace --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'sound_view_state.freezed.dart'; + +@freezed +abstract class SoundViewState with _$SoundViewState { + const factory SoundViewState({ + @Default('') String deviceId, + String? soundOption, + @Default(true) bool isLoading, + @Default('') String errorMessage, + }) = _SoundViewState; +} diff --git a/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.freezed.dart b/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.freezed.dart new file mode 100644 index 00000000..596b2d85 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/presentation/state/sound_view_state.freezed.dart @@ -0,0 +1,280 @@ +// 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 'sound_view_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$SoundViewState { + + String get deviceId; String? get soundOption; bool get isLoading; String get errorMessage; +/// Create a copy of SoundViewState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SoundViewStateCopyWith get copyWith => _$SoundViewStateCopyWithImpl(this as SoundViewState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SoundViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.soundOption, soundOption) || other.soundOption == soundOption)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,soundOption,isLoading,errorMessage); + +@override +String toString() { + return 'SoundViewState(deviceId: $deviceId, soundOption: $soundOption, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class $SoundViewStateCopyWith<$Res> { + factory $SoundViewStateCopyWith(SoundViewState value, $Res Function(SoundViewState) _then) = _$SoundViewStateCopyWithImpl; +@useResult +$Res call({ + String deviceId, String? soundOption, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class _$SoundViewStateCopyWithImpl<$Res> + implements $SoundViewStateCopyWith<$Res> { + _$SoundViewStateCopyWithImpl(this._self, this._then); + + final SoundViewState _self; + final $Res Function(SoundViewState) _then; + +/// Create a copy of SoundViewState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? soundOption = freezed,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_self.copyWith( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,soundOption: freezed == soundOption ? _self.soundOption : soundOption // ignore: cast_nullable_to_non_nullable +as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SoundViewState]. +extension SoundViewStatePatterns on SoundViewState { +/// 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 Function( _SoundViewState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SoundViewState() 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 Function( _SoundViewState value) $default,){ +final _that = this; +switch (_that) { +case _SoundViewState(): +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? Function( _SoundViewState value)? $default,){ +final _that = this; +switch (_that) { +case _SoundViewState() 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 Function( String deviceId, String? soundOption, bool isLoading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SoundViewState() when $default != null: +return $default(_that.deviceId,_that.soundOption,_that.isLoading,_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 Function( String deviceId, String? soundOption, bool isLoading, String errorMessage) $default,) {final _that = this; +switch (_that) { +case _SoundViewState(): +return $default(_that.deviceId,_that.soundOption,_that.isLoading,_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? Function( String deviceId, String? soundOption, bool isLoading, String errorMessage)? $default,) {final _that = this; +switch (_that) { +case _SoundViewState() when $default != null: +return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.errorMessage);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _SoundViewState implements SoundViewState { + const _SoundViewState({this.deviceId = '', this.soundOption, this.isLoading = true, this.errorMessage = ''}); + + +@override@JsonKey() final String deviceId; +@override final String? soundOption; +@override@JsonKey() final bool isLoading; +@override@JsonKey() final String errorMessage; + +/// Create a copy of SoundViewState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SoundViewStateCopyWith<_SoundViewState> get copyWith => __$SoundViewStateCopyWithImpl<_SoundViewState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SoundViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.soundOption, soundOption) || other.soundOption == soundOption)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,soundOption,isLoading,errorMessage); + +@override +String toString() { + return 'SoundViewState(deviceId: $deviceId, soundOption: $soundOption, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class _$SoundViewStateCopyWith<$Res> implements $SoundViewStateCopyWith<$Res> { + factory _$SoundViewStateCopyWith(_SoundViewState value, $Res Function(_SoundViewState) _then) = __$SoundViewStateCopyWithImpl; +@override @useResult +$Res call({ + String deviceId, String? soundOption, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class __$SoundViewStateCopyWithImpl<$Res> + implements _$SoundViewStateCopyWith<$Res> { + __$SoundViewStateCopyWithImpl(this._self, this._then); + + final _SoundViewState _self; + final $Res Function(_SoundViewState) _then; + +/// Create a copy of SoundViewState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? soundOption = freezed,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_SoundViewState( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,soundOption: freezed == soundOption ? _self.soundOption : soundOption // ignore: cast_nullable_to_non_nullable +as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/modules/legacy/modules/settings/lib/src/features/sound/sound_builder.dart b/modules/legacy/modules/settings/lib/src/features/sound/sound_builder.dart new file mode 100644 index 00000000..31088468 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sound/sound_builder.dart @@ -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/sound_screen.dart'; + +class SoundBuilder { + const SoundBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: SoundScreen(navigationContract: navigationContract), + ); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case.dart new file mode 100644 index 00000000..61a4adca --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case.dart @@ -0,0 +1,3 @@ +abstract class SyncClockUseCase { + Future syncClock({required String deviceId}); +} diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case_impl.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case_impl.dart new file mode 100644 index 00000000..bf9f9b86 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/domain/sync_clock_use_case_impl.dart @@ -0,0 +1,16 @@ +import 'package:settings/src/core/domain/repositories/settings_repository.dart'; + +import 'sync_clock_use_case.dart'; + +class SyncClockUseCaseImpl implements SyncClockUseCase { + + SyncClockUseCaseImpl(this._repository); + + final SettingsRepository _repository; + + @override + Future syncClock({required String deviceId}) async { + return; + // return _repository.syncClock(deviceId: deviceId); + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/providers/set_sound_use_case_provider.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/providers/set_sound_use_case_provider.dart new file mode 100644 index 00000000..b4119e08 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/providers/set_sound_use_case_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:settings/src/core/providers/settings_repository_provider.dart'; + +import '../../domain/sync_clock_use_case.dart'; +import '../../domain/sync_clock_use_case_impl.dart'; + +final syncClockUseCaseProvider = Provider.autoDispose((ref) { + final settingsRepository = ref.read(settingsRepositoryProvider); + return SyncClockUseCaseImpl(settingsRepository); +}); diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_model.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_model.dart new file mode 100644 index 00000000..a80f9ac6 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_model.dart @@ -0,0 +1,53 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:legacy_shared/legacy_shared.dart'; +import 'package:sf_shared/sf_shared.dart'; + +import '../../domain/sync_clock_use_case.dart'; +import '../providers/set_sound_use_case_provider.dart'; +import 'sync_clock_view_state.dart'; + +final syncClockViewModelProvider = +NotifierProvider.autoDispose( + SyncClockViewModel.new, +); + +class SyncClockViewModel extends Notifier { + late final SyncClockUseCase _syncClockUseCase; + + @override + SyncClockViewState build() { + _syncClockUseCase = ref.read(syncClockUseCaseProvider); + + Future.microtask(() => load()); + + return const SyncClockViewState(); + } + + Future load() async { + final device = ref.read(selectedDeviceProvider); + setDevice(device!); + } + + void setDevice(DeviceEntity device) { + state = state.copyWith( + deviceId: device.identificator, + isLoading: false, + ); + } + + Future syncClock() async { + if (state.isLoading) return; + + try { + state = state.copyWith( + isLoading: true, + ); + _syncClockUseCase.syncClock(deviceId: state.deviceId); + } catch (e) { + state = state.copyWith( + isLoading: false, + errorMessage: e.toString() + ); + } + } +} diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.dart new file mode 100644 index 00000000..face21d0 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'sync_clock_view_state.freezed.dart'; + +@freezed +abstract class SyncClockViewState with _$SyncClockViewState { + const factory SyncClockViewState({ + @Default('') String deviceId, + @Default(true) bool isLoading, + @Default('') String errorMessage, + }) = _SyncClockViewState; +} diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.freezed.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.freezed.dart new file mode 100644 index 00000000..d0abebe6 --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/state/sync_clock_view_state.freezed.dart @@ -0,0 +1,277 @@ +// 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 'sync_clock_view_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$SyncClockViewState { + + String get deviceId; bool get isLoading; String get errorMessage; +/// Create a copy of SyncClockViewState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SyncClockViewStateCopyWith get copyWith => _$SyncClockViewStateCopyWithImpl(this as SyncClockViewState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SyncClockViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,isLoading,errorMessage); + +@override +String toString() { + return 'SyncClockViewState(deviceId: $deviceId, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class $SyncClockViewStateCopyWith<$Res> { + factory $SyncClockViewStateCopyWith(SyncClockViewState value, $Res Function(SyncClockViewState) _then) = _$SyncClockViewStateCopyWithImpl; +@useResult +$Res call({ + String deviceId, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class _$SyncClockViewStateCopyWithImpl<$Res> + implements $SyncClockViewStateCopyWith<$Res> { + _$SyncClockViewStateCopyWithImpl(this._self, this._then); + + final SyncClockViewState _self; + final $Res Function(SyncClockViewState) _then; + +/// Create a copy of SyncClockViewState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_self.copyWith( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SyncClockViewState]. +extension SyncClockViewStatePatterns on SyncClockViewState { +/// 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 Function( _SyncClockViewState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SyncClockViewState() 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 Function( _SyncClockViewState value) $default,){ +final _that = this; +switch (_that) { +case _SyncClockViewState(): +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? Function( _SyncClockViewState value)? $default,){ +final _that = this; +switch (_that) { +case _SyncClockViewState() 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 Function( String deviceId, bool isLoading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SyncClockViewState() when $default != null: +return $default(_that.deviceId,_that.isLoading,_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 Function( String deviceId, bool isLoading, String errorMessage) $default,) {final _that = this; +switch (_that) { +case _SyncClockViewState(): +return $default(_that.deviceId,_that.isLoading,_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? Function( String deviceId, bool isLoading, String errorMessage)? $default,) {final _that = this; +switch (_that) { +case _SyncClockViewState() when $default != null: +return $default(_that.deviceId,_that.isLoading,_that.errorMessage);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _SyncClockViewState implements SyncClockViewState { + const _SyncClockViewState({this.deviceId = '', this.isLoading = true, this.errorMessage = ''}); + + +@override@JsonKey() final String deviceId; +@override@JsonKey() final bool isLoading; +@override@JsonKey() final String errorMessage; + +/// Create a copy of SyncClockViewState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SyncClockViewStateCopyWith<_SyncClockViewState> get copyWith => __$SyncClockViewStateCopyWithImpl<_SyncClockViewState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SyncClockViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,deviceId,isLoading,errorMessage); + +@override +String toString() { + return 'SyncClockViewState(deviceId: $deviceId, isLoading: $isLoading, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class _$SyncClockViewStateCopyWith<$Res> implements $SyncClockViewStateCopyWith<$Res> { + factory _$SyncClockViewStateCopyWith(_SyncClockViewState value, $Res Function(_SyncClockViewState) _then) = __$SyncClockViewStateCopyWithImpl; +@override @useResult +$Res call({ + String deviceId, bool isLoading, String errorMessage +}); + + + + +} +/// @nodoc +class __$SyncClockViewStateCopyWithImpl<$Res> + implements _$SyncClockViewStateCopyWith<$Res> { + __$SyncClockViewStateCopyWithImpl(this._self, this._then); + + final _SyncClockViewState _self; + final $Res Function(_SyncClockViewState) _then; + +/// Create a copy of SyncClockViewState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? isLoading = null,Object? errorMessage = null,}) { + return _then(_SyncClockViewState( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/sync_clock_screen.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/sync_clock_screen.dart new file mode 100644 index 00000000..a9046c9f --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/presentation/sync_clock_screen.dart @@ -0,0 +1,156 @@ +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:navigation/navigation.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +import '../../sound/presentation/state/sound_view_model.dart'; + +class SyncClockScreen extends ConsumerWidget { + + final NavigationContract navigationContract; + + const SyncClockScreen({ + super.key, + required this.navigationContract, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return LegacyPageLayout( + theme: theme, + title: context.translate(I18n.syncClock), + body: Padding( + padding: EdgeInsets.symmetric(horizontal: 18, vertical: 12), + child: Column( + children: [ + Text(context.translate(I18n.syncClockMessage)), + SizedBox(height: 36), + ], + ), + ), + footer: const _SaveSection(), + ); + } +} + +class _OptionsSection extends ConsumerWidget { + + const _OptionsSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final vm = ref.read(soundViewModelProvider.notifier); + + final soundOption = ref.watch( + soundViewModelProvider.select((s)=>s.soundOption) + ); + + return SingleChildScrollView( + child: Column( + children: [ + _SectionButton( + title: context.translate(I18n.soundAndVibration), + icon: Icons.volume_up_outlined, + active: soundOption == 'SOUND_AND_VIBRATION', + onPressed: () {vm.setSoundOption('SOUND_AND_VIBRATION');}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.soundOnly), + icon: Icons.volume_up_outlined, + active: soundOption == 'SOUND', + onPressed: () {vm.setSoundOption('SOUND');}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.vibrationOnly), + icon: Icons.vibration_outlined, + active: soundOption == 'VIBRATION', + onPressed: () {vm.setSoundOption('VIBRATION');}, + ), + SizedBox(height: 12), + _SectionButton( + title: context.translate(I18n.silent), + icon: Icons.volume_mute_outlined, + active: soundOption == 'SILENT', + onPressed: () {vm.setSoundOption('SILENT');}, + ), + ] + ), + ); + } +} + +class _SectionButton extends ConsumerWidget { + + final String title; + final bool active; + final IconData icon; + final VoidCallback onPressed; + + const _SectionButton({ + required this.title, + required this.active, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + return SectionButton( + onPressed: onPressed, + icon: Icon(icon, + color: theme.getColorFor(ThemeCode.legacyPrimary), + size: 36, + ), + iconPadding: 8, + body: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: active + ? theme.getColorFor(ThemeCode.textPrimary) + : theme.getColorFor(ThemeCode.textTertiary), + ) + ), + Switch( + value: active, + onChanged: (_){onPressed();}, + ) + ], + ) + ); + } + +} + +class _SaveSection extends ConsumerWidget { + + const _SaveSection(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.read(themePortProvider); + + final vm = ref.read(soundViewModelProvider.notifier); + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: PrimaryButton( + onPressed: vm.submit, + text: context.translate(I18n.save), + color: theme.getColorFor(ThemeCode.legacyPrimary) + ), + ); + } + +} \ No newline at end of file diff --git a/modules/legacy/modules/settings/lib/src/features/sync_clock/sync_clock_builder.dart b/modules/legacy/modules/settings/lib/src/features/sync_clock/sync_clock_builder.dart new file mode 100644 index 00000000..6eefaedc --- /dev/null +++ b/modules/legacy/modules/settings/lib/src/features/sync_clock/sync_clock_builder.dart @@ -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/sync_clock_screen.dart'; + +class SyncClockBuilder { + const SyncClockBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: SyncClockScreen(navigationContract: navigationContract), + ); + } +} diff --git a/packages/navigation/lib/app_routes.dart b/packages/navigation/lib/app_routes.dart index c3c6a4a2..166e5084 100644 --- a/packages/navigation/lib/app_routes.dart +++ b/packages/navigation/lib/app_routes.dart @@ -66,4 +66,21 @@ class AppRoutes { static const linkedDevices = '$accountSettings/linked_devices'; static const appUsers = '$accountSettings/app_users'; static const deleteAccount = '$accountSettings/delete_account'; + + static const settings = '$controlPanel/settings'; + static const alarm = '$settings/alarm'; + static const appStore = '$settings/app_store'; + static const battery = '$settings/battery'; + static const blockPhone = '$settings/block_phone'; + static const disableFunctions = '$settings/disable_functions'; + static const language = '$settings/language'; + static const legacyNotifications = '$settings/legacy_notifications'; + static const remoteManagement = '$settings/remote_management'; + static const remoteOnOff = '$settings/remote_on_off'; + static const smsAlert = '$settings/sms_alert'; + static const sosContacts = '$settings/sos_agenda'; + static const sound = '$settings/sound'; + static const syncClock = '$settings/sync_clock'; + static const timezone = '$settings/timezone'; + static const wifiSettings = '$settings/wifi_settings'; } diff --git a/packages/sf_infrastructure/lib/src/api/questia_api.dart b/packages/sf_infrastructure/lib/src/api/questia_api.dart index 9a27d95f..9bad7459 100644 --- a/packages/sf_infrastructure/lib/src/api/questia_api.dart +++ b/packages/sf_infrastructure/lib/src/api/questia_api.dart @@ -30,6 +30,7 @@ class QuestiaApi { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }) { + print('URI:${String.fromEnvironment('env')}'); return _dio.post( path, data: data, diff --git a/packages/sf_localizations/assets/l10n/en.json b/packages/sf_localizations/assets/l10n/en.json index affe37d6..fc942068 100755 --- a/packages/sf_localizations/assets/l10n/en.json +++ b/packages/sf_localizations/assets/l10n/en.json @@ -550,5 +550,23 @@ "ratherNotSay": "I'd rather not say", "personalDataMessage": "*This is the app user's personal data", "newContact": "New contact", - "deleteContactMessage": "Are you sure you want to delete this phone from the list?" + "deleteContactMessage": "Are you sure you want to delete this phone from the list?", + "remoteListening": "Remote Listening", + "alarmSettings": "Alarm settings", + "setDateTime": "Set time and days:", + "addAlarm": "Add new alarm", + "alarmsMessage": "*Set up to 3 alarms", + "once": "Once", + "daily": "Daily", + "selectDay": "Select days", + "remoteTurnOff": "Apagado Remoto", + "remoteTurnOffMessage": "Remote Shutdown", + "remoteTurnOffConfirm": "Remotely shut down the device.", + "remoteRestart": "Are you sure you want to remotely shut down the device?", + "remoteRestartMessage": "Remote Restart", + "remoteRestartConfirm": "Remotely restart the device.", + "remoteFactoryReset": "Are you sure you want to remotely restart the device?", + "remoteFactoryResetMessage": "Restore Default Settings", + "remoteFactoryResetConfirm": "Restore your device to factory settings?", + "number": "Number {num}:" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/es.json b/packages/sf_localizations/assets/l10n/es.json index 4c25d407..2b826bcf 100644 --- a/packages/sf_localizations/assets/l10n/es.json +++ b/packages/sf_localizations/assets/l10n/es.json @@ -548,5 +548,54 @@ "ratherNotSay": "Prefiero no decirlo", "personalDataMessage": "*Estos son los datos personales del usuario de la aplicación", "newContact": "Nuevo contacto", - "deleteContactMessage": "¿Estás seguro de que deseas eliminar este número de la lista?" + "deleteContactMessage": "¿Estás seguro de que deseas eliminar este número de la lista?", + "settings": "Ajustes", + "alarm": "Alarmas", + "appStore": "App Store", + "battery": "Ahorro Nocturno de Batería", + "blockPhone": "Bloqueo de números", + "disableFunctions": "Deshabilitar Funciones", + "language": "Idioma", + "legacyNotifications": "Notificaciones", + "remoteManagement": "Programación remota", + "remoteOnOff": "Encendido y Apagado Programado", + "smsAlert": "Alertas SMS", + "sosContacts": "Agenda SOS", + "sound": "Sonidos", + "syncClock": "Sincronización de tiempo", + "timezone": "Cambio de horario y zona", + "wifiSettings": "Configuración WiFi", + "alarmSettings": "Ajustes de alarma", + "setDateTime": "Establece la hora y días:", + "addAlarm": "Agregar nueva alarma", + "alarmsMessage": "*Configura hasta 3 alarmas", + "once": "Una vez", + "daily": "Diario", + "selectDay": "Selección de días", + "remoteTurnOff": "Apagado Remoto", + "remoteTurnOffMessage": "Apaga el dispositivo de forma remota", + "remoteTurnOffConfirm": "¿Seguro que deseas apagar el dispositivo en remoto?", + "remoteRestart": "Reinicio Remoto", + "remoteRestartMessage": "Reinicia el dispositivo de forma remota", + "remoteRestartConfirm": "¿Seguro que deseas reiniciar el dispositivo en remoto?", + "remoteFactoryReset": "Restaurar Configuración Predeterminada", + "remoteFactoryResetMessage": "Restaura tu dispositivo a la configuración de fábrica", + "remoteFactoryResetConfirm": "¿Seguro que quieres restablecer este dispositivo a la configuración de fábrica?", + "number": "Número {num}:", + "settings": "Ajustes", + "alarm": "Alarmas", + "appStore": "App Store", + "blockPhone": "Bloqueo de números", + "timezone": "Cambio de horario y zona", + "language": "Idioma", + "battery": "Ahorro nocturno de batería", + "remoteManagement": "programación remota", + "legacyNotifications": "notificaciones", + "smsAlert": "Alertas SMS", + "sosContacts": "Agenda SOS", + "sound": "Sonidos", + "wifiSettings": "Cofiguración WiFi", + "remoteOnOff": "Encendido y apagado programado", + "disableFunctions": "Deshabilitar funciones", + "syncClock": "Sincronización de tiempo" } \ No newline at end of file diff --git a/packages/sf_localizations/lib/src/generated/i18n.dart b/packages/sf_localizations/lib/src/generated/i18n.dart index 4b18d82e..836f9f8c 100755 --- a/packages/sf_localizations/lib/src/generated/i18n.dart +++ b/packages/sf_localizations/lib/src/generated/i18n.dart @@ -669,4 +669,42 @@ class I18n { static const String personalDataMessage = 'personalDataMessage'; static const String newContact = 'newContact'; static const String deleteContactMessage = 'deleteContactMessage'; + static const String alarmSettings = 'alarmSettings'; + static const String setDateTime = 'setDateTime'; + static const String addAlarm = 'addAlarm'; + static const String alarmsMessage = 'alarmsMessage'; + static const String once = 'once'; + static const String daily = 'daily'; + static const String selectDay = 'selectDay'; + static const String remoteTurnOff = 'remoteTurnOff'; + static const String remoteTurnOffMessage = 'remoteTurnOffMessage'; + static const String remoteTurnOffConfirm = 'remoteTurnOffConfirm'; + static const String remoteRestart = 'remoteRestart'; + static const String remoteRestartMessage = 'remoteRestartMessage'; + static const String remoteRestartConfirm = 'remoteRestartConfirm'; + static const String remoteFactoryReset = 'remoteFactoryReset'; + static const String remoteFactoryResetMessage = 'remoteFactoryResetMessage'; + static const String remoteFactoryResetConfirm = 'remoteFactoryResetConfirm'; + static const String soundAndVibration = 'soundAndVibration'; + static const String soundOnly = 'soundOnly'; + static const String vibrationOnly = 'vibrationOnly'; + static const String silent = 'silent'; + static const String number = 'number'; + static const String syncClockMessage = 'syncClockMessage'; + static const String settings = 'settings'; + static const String alarm = 'alarm'; + static const String appStore = 'appStore'; + static const String blockPhone = 'blockPhone'; + static const String timezone = 'timezone'; + static const String language = 'language'; + static const String battery = 'battery'; + static const String remoteManagement = 'remoteManagement'; + static const String legacyNotifications = 'legacyNotifications'; + static const String smsAlert = 'smsAlert'; + static const String sosContacts = 'sosContacts'; + static const String sound = 'sound'; + static const String wifiSettings = 'wifiSettings'; + static const String remoteOnOff = 'remoteOnOff'; + static const String disableFunctions = 'disableFunctions'; + static const String syncClock = 'syncClock'; }