settings ui
This commit is contained in:
@@ -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,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
abstract class SettingsRemoteDatasource {
|
||||
// Future<UserEntity> getLoggedUser({required String token});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
abstract class SettingsRepository {
|
||||
// Future<List<ContactEntity>> getContacts({required String userId});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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: [
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
abstract class ShutdownUseCase {
|
||||
Future<void> shutdown({required String deviceId});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
abstract class SetContactsUseCase {
|
||||
Future<void> setContacts({required String deviceId, required String phone});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
abstract class SetSoundUseCase {
|
||||
Future<void> setSound({required String deviceId});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
abstract class SyncClockUseCase {
|
||||
Future<void> syncClock({required String deviceId});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user