settings ui

This commit is contained in:
2026-03-12 17:40:06 +01:00
parent 9bf06f2480
commit ec4e42b408
65 changed files with 4095 additions and 6 deletions

View File

@@ -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}}
{"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}}

File diff suppressed because one or more lines are too long

View File

@@ -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<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
@@ -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',

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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,
),

View File

@@ -0,0 +1,4 @@
abstract class SettingsRemoteDatasource {
// Future<UserEntity> getLoggedUser({required String token});
}

View File

@@ -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<UserEntity> getLoggedUser({required String token}) async {
try {
final response = await _repository.get<Map<String, dynamic>>(
'/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;
}

View File

@@ -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<List<ContactEntity>> getContacts({required String userId}) {
return _remote.getContacts(userId: userId);
}*/
}

View File

@@ -0,0 +1,3 @@
abstract class SettingsRepository {
// Future<List<ContactEntity>> getContacts({required String userId});
}

View File

@@ -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<SettingsRemoteDatasource>((ref) {
final questiaRepository = getIt<QuestiaRepository>();
return SettingsRemoteDatasourceImpl(questiaRepository);
});

View File

@@ -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<SettingsRepository>((ref) {
final remote = ref.read(settingsRemoteDatasourceProvider);
return SettingsRepositoryImpl(remote);
});

View File

@@ -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<void> buildPage(BuildContext context, GoRouterState state) {
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
return MaterialPage<void>(
key: state.pageKey,
child: AlarmScreen(navigationContract: navigationContract),
);
}
}

View File

@@ -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;
}

View File

@@ -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>(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<AlarmEntity> get copyWith => _$AlarmEntityCopyWithImpl<AlarmEntity>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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

View File

@@ -0,0 +1,6 @@
import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
abstract class GetAlarmsUseCase {
Future<List<AlarmEntity>> getAlarms({required String deviceId});
}

View File

@@ -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<List<AlarmEntity>> getAlarms({required String deviceId}) async {
return [];
// return _repository.getAlarms(deviceId: deviceId);
}
}

View File

@@ -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: [
],
),
),
);
}
}

View File

@@ -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<GetAlarmsUseCase>((ref) {
final settingsRepository = ref.read(settingsRepositoryProvider);
return GetAlarmsUseCaseImpl(settingsRepository);
});

View File

@@ -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, AlarmViewState>(
AlarmViewModel.new,
);
class AlarmViewModel extends Notifier<AlarmViewState> {
late final GetAlarmsUseCase _getAlarmsUseCase;
// late final UserEntity loggedUser;
@override
AlarmViewState build() {
_getAlarmsUseCase = ref.read(getAlarmsUseCaseProvider);
Future.microtask(() => load());
return const AlarmViewState();
}
Future<void> load() async {
final device = ref.read(selectedDeviceProvider);
// final alarms = await _getAlarmsUseCase.getAlarms(deviceId: device.identificator);
// setAlarms(alarms);
}
Future<void> setAlarms(List<AlarmEntity> 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<bool> alarmDays = state.alarmDays.toList();
alarmDays[index] = !alarmDays[index];
state = state.copyWith(
alarmDays: alarmDays,
);
}
Future<void> setAlarm() async {
}
}

View File

@@ -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<AlarmEntity?> alarms,
@Default(TimeOfDay(hour: 0, minute: 0)) TimeOfDay alarmTime,
@Default('ONCE') String dateOption,
@Default([false, false, false, false, false, false, false]) List<bool> alarmDays,
@Default(true) bool isLoading,
@Default('') String errorMessage,
}) = _AlarmViewState;
}

View File

@@ -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>(T value) => value;
/// @nodoc
mixin _$AlarmViewState {
List<AlarmEntity?> get alarms; TimeOfDay get alarmTime; String get dateOption; List<bool> 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<AlarmViewState> get copyWith => _$AlarmViewStateCopyWithImpl<AlarmViewState>(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<AlarmEntity?> alarms, TimeOfDay alarmTime, String dateOption, List<bool> 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<AlarmEntity?>,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<bool>,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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(TResult Function( List<AlarmEntity?> alarms, TimeOfDay alarmTime, String dateOption, List<bool> 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 extends Object?>(TResult Function( List<AlarmEntity?> alarms, TimeOfDay alarmTime, String dateOption, List<bool> 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 extends Object?>(TResult? Function( List<AlarmEntity?> alarms, TimeOfDay alarmTime, String dateOption, List<bool> 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<AlarmEntity?> alarms = const [null, null, null], this.alarmTime = const TimeOfDay(hour: 0, minute: 0), this.dateOption = 'ONCE', final List<bool> alarmDays = const [false, false, false, false, false, false, false], this.isLoading = true, this.errorMessage = ''}): _alarms = alarms,_alarmDays = alarmDays;
final List<AlarmEntity?> _alarms;
@override@JsonKey() List<AlarmEntity?> 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<bool> _alarmDays;
@override@JsonKey() List<bool> 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<AlarmEntity?> alarms, TimeOfDay alarmTime, String dateOption, List<bool> 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<AlarmEntity?>,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<bool>,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

View File

@@ -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<String, String> dateOptions = <String, String>{
'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<Widget>.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)
);
}
}

View File

@@ -0,0 +1,4 @@
abstract class ShutdownUseCase {
Future<void> shutdown({required String deviceId});
}

View File

@@ -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<void> shutdown({required String deviceId}) async {
return;
// return _repository.shutdown(deviceId: deviceId);
}
}

View File

@@ -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<ShutdownUseCase>((ref) {
final settingsRepository = ref.read(settingsRepositoryProvider);
return ShutdownUseCaseImpl(settingsRepository);
});

View File

@@ -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)
),
)
],
)
);
}
}

View File

@@ -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, RemoteManagementViewState>(
RemoteManagementViewModel.new,
);
class RemoteManagementViewModel extends Notifier<RemoteManagementViewState> {
// 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<void> load() async {
final device = ref.read(selectedDeviceProvider);
setDevice(device!);
}
void setDevice(DeviceEntity device) {
state = state.copyWith(
deviceId: device.identificator,
isLoading: false,
);
}
Future<void> turnOff() async {
try {
state = state.copyWith(
isLoading: true,
);
// _turnOffUseCase.turnOff();
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString()
);
}
}
Future<void> restart() async {
try {
state = state.copyWith(
isLoading: true,
);
// _restartUseCase.restart();
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString()
);
}
}
Future<void> factoryReset() async {
try {
state = state.copyWith(
isLoading: true,
);
// _factoryResetUseCase.fatoryReset();
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString()
);
}
}
}

View File

@@ -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;
}

View File

@@ -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>(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<RemoteManagementViewState> get copyWith => _$RemoteManagementViewStateCopyWithImpl<RemoteManagementViewState>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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

View File

@@ -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),
)),
],
)
],
),
);
}
}

View File

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

View File

@@ -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
)
)
)
],
),
)
);
}
}

View File

@@ -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<void> buildPage(BuildContext context, GoRouterState state) {
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
return MaterialPage<void>(
key: state.pageKey,
child: SettingsScreen(navigationContract: navigationContract),
);
}
}

View File

@@ -0,0 +1,4 @@
abstract class SetContactsUseCase {
Future<void> setContacts({required String deviceId, required String phone});
}

View File

@@ -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<void> setContacts({required String deviceId, required String phone}) async {
return;
// return _repository.setContact(deviceId: deviceId, phone: phone);
}
}

View File

@@ -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<SetContactsUseCase>((ref) {
final settingsRepository = ref.read(settingsRepositoryProvider);
return SetContactsUseCaseImpl(settingsRepository);
});

View File

@@ -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<Widget>.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)
),
);
}
}

View File

@@ -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, SosContactsViewState>(
SosContactsViewModel.new,
);
class SosContactsViewModel extends Notifier<SosContactsViewState> {
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<void> 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<String> 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<void> submit() async {
try {
state = state.copyWith(
isLoading: true,
);
// _setContactsUseCase.setContacts();
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString()
);
}
}
Future<void> restart() async {
try {
state = state.copyWith(
isLoading: true,
);
// _restartUseCase.restart();
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString()
);
}
}
Future<void> factoryReset() async {
try {
state = state.copyWith(
isLoading: true,
);
// _factoryResetUseCase.fatoryReset();
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString()
);
}
}
}

View File

@@ -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<String> contacts,
@Default('') String phone,
@Default(true) bool isLoading,
@Default('') String errorMessage,
}) = _SosContactsViewState;
}

View File

@@ -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>(T value) => value;
/// @nodoc
mixin _$SosContactsViewState {
String get deviceId; List<String> 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<SosContactsViewState> get copyWith => _$SosContactsViewStateCopyWithImpl<SosContactsViewState>(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<String> 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<String>,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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(TResult Function( String deviceId, List<String> 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 extends Object?>(TResult Function( String deviceId, List<String> 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 extends Object?>(TResult? Function( String deviceId, List<String> 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<String> contacts = const ['', '', ''], this.phone = '', this.isLoading = true, this.errorMessage = ''}): _contacts = contacts;
@override@JsonKey() final String deviceId;
final List<String> _contacts;
@override@JsonKey() List<String> 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<String> 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<String>,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

View File

@@ -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)
)
],
),
);
}
}

View File

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

View File

@@ -0,0 +1,3 @@
abstract class SetSoundUseCase {
Future<void> setSound({required String deviceId});
}

View File

@@ -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<void> setSound({required String deviceId}) async {
return;
// return _repository.setSound(deviceId: deviceId);
}
}

View File

@@ -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<SetSoundUseCase>((ref) {
final settingsRepository = ref.read(settingsRepositoryProvider);
return SetSoundUseCaseImpl(settingsRepository);
});

View File

@@ -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)
),
);
}
}

View File

@@ -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, SoundViewState>(
SoundViewModel.new,
);
class SoundViewModel extends Notifier<SoundViewState> {
late final SetSoundUseCase _setSoundUseCase;
@override
SoundViewState build() {
_setSoundUseCase = ref.read(setSoundUseCaseProvider);
Future.microtask(() => load());
return const SoundViewState();
}
Future<void> 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<void> submit() async {
try {
state = state.copyWith(
isLoading: true,
);
_setSoundUseCase.setSound(deviceId: state.deviceId);
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString()
);
}
}
}

View File

@@ -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;
}

View File

@@ -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>(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<SoundViewState> get copyWith => _$SoundViewStateCopyWithImpl<SoundViewState>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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

View File

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

View File

@@ -0,0 +1,3 @@
abstract class SyncClockUseCase {
Future<void> syncClock({required String deviceId});
}

View File

@@ -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<void> syncClock({required String deviceId}) async {
return;
// return _repository.syncClock(deviceId: deviceId);
}
}

View File

@@ -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<SyncClockUseCase>((ref) {
final settingsRepository = ref.read(settingsRepositoryProvider);
return SyncClockUseCaseImpl(settingsRepository);
});

View File

@@ -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, SyncClockViewState>(
SyncClockViewModel.new,
);
class SyncClockViewModel extends Notifier<SyncClockViewState> {
late final SyncClockUseCase _syncClockUseCase;
@override
SyncClockViewState build() {
_syncClockUseCase = ref.read(syncClockUseCaseProvider);
Future.microtask(() => load());
return const SyncClockViewState();
}
Future<void> load() async {
final device = ref.read(selectedDeviceProvider);
setDevice(device!);
}
void setDevice(DeviceEntity device) {
state = state.copyWith(
deviceId: device.identificator,
isLoading: false,
);
}
Future<void> 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()
);
}
}
}

View File

@@ -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;
}

View File

@@ -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>(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<SyncClockViewState> get copyWith => _$SyncClockViewStateCopyWithImpl<SyncClockViewState>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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

View File

@@ -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)
),
);
}
}

View File

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

View File

@@ -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';
}

View File

@@ -30,6 +30,7 @@ class QuestiaApi {
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) {
print('URI:${String.fromEnvironment('env')}');
return _dio.post<T>(
path,
data: data,

View File

@@ -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}:"
}

View File

@@ -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"
}

View File

@@ -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';
}