feat(alarm): changed alarm endpoint and modified models, entities and presentation

This commit is contained in:
2026-04-16 18:42:29 +02:00
parent cda889a15b
commit 984a87f200
28 changed files with 1150 additions and 1382 deletions

View File

@@ -1,13 +1,9 @@
import 'package:settings/src/core/data/models/create_alarm_request_model.dart';
import 'package:settings/src/core/data/models/get_alarms_response_model.dart';
import 'package:settings/src/core/data/models/update_alarm_request_model.dart';
import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
abstract class AlarmRemoteDatasource {
Future<GetAlarmsResponseModel> getAlarms({required String deviceId});
Future<void> createAlarm({required CreateAlarmRequestModel request});
Future<void> updateAlarm({
required String alarmId,
required UpdateAlarmRequestModel request,
Future<List<AlarmEntity>> getAlarms({required String deviceId});
Future<List<AlarmEntity>> upsertAlarms({
required String deviceId,
required List<AlarmEntity> alarms,
});
Future<void> deleteAlarm({required String alarmId});
}

View File

@@ -1,9 +1,8 @@
import 'package:dio/dio.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:settings/src/core/data/datasources/alarm_remote_datasource.dart';
import 'package:settings/src/core/data/models/create_alarm_request_model.dart';
import 'package:settings/src/core/data/models/get_alarms_response_model.dart';
import 'package:settings/src/core/data/models/update_alarm_request_model.dart';
import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
class AlarmRemoteDatasourceImpl implements AlarmRemoteDatasource {
@@ -12,55 +11,48 @@ class AlarmRemoteDatasourceImpl implements AlarmRemoteDatasource {
final SaveFamilyRepository _repository;
@override
Future<GetAlarmsResponseModel> getAlarms({required String deviceId}) async {
Future<List<AlarmEntity>> getAlarms({required String deviceId}) async {
try {
final response = await _repository.get<Map<String, dynamic>>(
'/devices/$deviceId/takepills-reminders',
'/devices/$deviceId/alarms',
);
final data = response.data;
if (data == null || data.isEmpty) {
throw Exception(
'Empty response from /devices/$deviceId/takepills-reminders',
);
}
return GetAlarmsResponseModel.fromJson(data);
if (data == null || data.isEmpty) return [];
final model = GetAlarmsResponseModel.fromJson(data);
return model.item.toEntities();
} on DioException catch (error) {
if (error.response?.statusCode == 404) return [];
throw mapDioError(error, defaultMessage: 'Error getting alarms');
}
}
@override
Future<void> createAlarm({required CreateAlarmRequestModel request}) async {
await safeCall(
() => _repository.post<dynamic>(
'/takepills-reminders',
body: request.toJson(),
),
'Error creating alarm',
);
}
@override
Future<void> updateAlarm({
required String alarmId,
required UpdateAlarmRequestModel request,
Future<List<AlarmEntity>> upsertAlarms({
required String deviceId,
required List<AlarmEntity> alarms,
}) async {
await safeCall(
() => _repository.put<dynamic>(
'/takepills-reminders/$alarmId',
body: request.toJson(),
),
'Error updating alarm',
);
}
@override
Future<void> deleteAlarm({required String alarmId}) async {
await safeCall(
() => _repository.delete<dynamic>('/takepills-reminders/$alarmId'),
'Error deleting alarm',
);
try {
final body = {
'alarmList': alarms
.map((alarm) => {
'time': alarm.time,
'frequency': alarm.frequency.name,
if (alarm.frequency == AlarmFrequency.custom &&
alarm.week != null)
'week': alarm.week,
})
.toList(),
};
final response = await _repository.put<Map<String, dynamic>>(
'/devices/$deviceId/alarms',
body: body,
);
final data = response.data;
if (data == null || data.isEmpty) return alarms;
final model = UpsertAlarmsResponseModel.fromJson(data);
return model.item.toEntities();
} on DioException catch (error) {
throw mapDioError(error, defaultMessage: 'Error saving alarms');
}
}
}

View File

@@ -1,18 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'create_alarm_request_model.freezed.dart';
part 'create_alarm_request_model.g.dart';
@freezed
abstract class CreateAlarmRequestModel with _$CreateAlarmRequestModel {
const factory CreateAlarmRequestModel({
required String id,
required String deviceId,
required String time,
required String message,
required int order,
}) = _CreateAlarmRequestModel;
factory CreateAlarmRequestModel.fromJson(Map<String, dynamic> json) =>
_$CreateAlarmRequestModelFromJson(json);
}

View File

@@ -1,289 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'create_alarm_request_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$CreateAlarmRequestModel {
String get id; String get deviceId; String get time; String get message; int get order;
/// Create a copy of CreateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$CreateAlarmRequestModelCopyWith<CreateAlarmRequestModel> get copyWith => _$CreateAlarmRequestModelCopyWithImpl<CreateAlarmRequestModel>(this as CreateAlarmRequestModel, _$identity);
/// Serializes this CreateAlarmRequestModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CreateAlarmRequestModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.time, time) || other.time == time)&&(identical(other.message, message) || other.message == message)&&(identical(other.order, order) || other.order == order));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,time,message,order);
@override
String toString() {
return 'CreateAlarmRequestModel(id: $id, deviceId: $deviceId, time: $time, message: $message, order: $order)';
}
}
/// @nodoc
abstract mixin class $CreateAlarmRequestModelCopyWith<$Res> {
factory $CreateAlarmRequestModelCopyWith(CreateAlarmRequestModel value, $Res Function(CreateAlarmRequestModel) _then) = _$CreateAlarmRequestModelCopyWithImpl;
@useResult
$Res call({
String id, String deviceId, String time, String message, int order
});
}
/// @nodoc
class _$CreateAlarmRequestModelCopyWithImpl<$Res>
implements $CreateAlarmRequestModelCopyWith<$Res> {
_$CreateAlarmRequestModelCopyWithImpl(this._self, this._then);
final CreateAlarmRequestModel _self;
final $Res Function(CreateAlarmRequestModel) _then;
/// Create a copy of CreateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceId = null,Object? time = null,Object? message = null,Object? order = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,order: null == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [CreateAlarmRequestModel].
extension CreateAlarmRequestModelPatterns on CreateAlarmRequestModel {
/// 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( _CreateAlarmRequestModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _CreateAlarmRequestModel() 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( _CreateAlarmRequestModel value) $default,){
final _that = this;
switch (_that) {
case _CreateAlarmRequestModel():
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( _CreateAlarmRequestModel value)? $default,){
final _that = this;
switch (_that) {
case _CreateAlarmRequestModel() 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, String deviceId, String time, String message, int order)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CreateAlarmRequestModel() when $default != null:
return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order);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, String deviceId, String time, String message, int order) $default,) {final _that = this;
switch (_that) {
case _CreateAlarmRequestModel():
return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order);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, String deviceId, String time, String message, int order)? $default,) {final _that = this;
switch (_that) {
case _CreateAlarmRequestModel() when $default != null:
return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _CreateAlarmRequestModel implements CreateAlarmRequestModel {
const _CreateAlarmRequestModel({required this.id, required this.deviceId, required this.time, required this.message, required this.order});
factory _CreateAlarmRequestModel.fromJson(Map<String, dynamic> json) => _$CreateAlarmRequestModelFromJson(json);
@override final String id;
@override final String deviceId;
@override final String time;
@override final String message;
@override final int order;
/// Create a copy of CreateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$CreateAlarmRequestModelCopyWith<_CreateAlarmRequestModel> get copyWith => __$CreateAlarmRequestModelCopyWithImpl<_CreateAlarmRequestModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$CreateAlarmRequestModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CreateAlarmRequestModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.time, time) || other.time == time)&&(identical(other.message, message) || other.message == message)&&(identical(other.order, order) || other.order == order));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,time,message,order);
@override
String toString() {
return 'CreateAlarmRequestModel(id: $id, deviceId: $deviceId, time: $time, message: $message, order: $order)';
}
}
/// @nodoc
abstract mixin class _$CreateAlarmRequestModelCopyWith<$Res> implements $CreateAlarmRequestModelCopyWith<$Res> {
factory _$CreateAlarmRequestModelCopyWith(_CreateAlarmRequestModel value, $Res Function(_CreateAlarmRequestModel) _then) = __$CreateAlarmRequestModelCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceId, String time, String message, int order
});
}
/// @nodoc
class __$CreateAlarmRequestModelCopyWithImpl<$Res>
implements _$CreateAlarmRequestModelCopyWith<$Res> {
__$CreateAlarmRequestModelCopyWithImpl(this._self, this._then);
final _CreateAlarmRequestModel _self;
final $Res Function(_CreateAlarmRequestModel) _then;
/// Create a copy of CreateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceId = null,Object? time = null,Object? message = null,Object? order = null,}) {
return _then(_CreateAlarmRequestModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,order: null == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

View File

@@ -1,27 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'create_alarm_request_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_CreateAlarmRequestModel _$CreateAlarmRequestModelFromJson(
Map<String, dynamic> json,
) => _CreateAlarmRequestModel(
id: json['id'] as String,
deviceId: json['deviceId'] as String,
time: json['time'] as String,
message: json['message'] as String,
order: (json['order'] as num).toInt(),
);
Map<String, dynamic> _$CreateAlarmRequestModelToJson(
_CreateAlarmRequestModel instance,
) => <String, dynamic>{
'id': instance.id,
'deviceId': instance.deviceId,
'time': instance.time,
'message': instance.message,
'order': instance.order,
};

View File

@@ -6,48 +6,66 @@ part 'get_alarms_response_model.g.dart';
@freezed
abstract class GetAlarmsResponseModel with _$GetAlarmsResponseModel {
const factory GetAlarmsResponseModel({
required int total,
required List<AlarmItemResponseModel> items,
required int page,
required int pages,
}) = _GetAlarmsResponseModel;
const factory GetAlarmsResponseModel({required AlarmItemModel item}) =
_GetAlarmsResponseModel;
factory GetAlarmsResponseModel.fromJson(Map<String, dynamic> json) =>
_$GetAlarmsResponseModelFromJson(json);
}
@freezed
abstract class AlarmItemResponseModel with _$AlarmItemResponseModel {
const factory AlarmItemResponseModel({
required String id,
required String deviceId,
required String time,
required String message,
required int order,
required int createdAt,
int? updatedAt,
}) = _AlarmItemResponseModel;
abstract class UpsertAlarmsResponseModel with _$UpsertAlarmsResponseModel {
const factory UpsertAlarmsResponseModel({
@Default(false) bool isUpdated,
required AlarmItemModel item,
}) = _UpsertAlarmsResponseModel;
factory AlarmItemResponseModel.fromJson(Map<String, dynamic> json) =>
_$AlarmItemResponseModelFromJson(json);
factory UpsertAlarmsResponseModel.fromJson(Map<String, dynamic> json) =>
_$UpsertAlarmsResponseModelFromJson(json);
}
extension AlarmsResponseMapper on GetAlarmsResponseModel {
List<AlarmEntity> toEntity() {
return items.map((item) {
final decoded = AlarmEntity.decodeTime(item.time);
@freezed
abstract class AlarmItemModel with _$AlarmItemModel {
const factory AlarmItemModel({
required String id,
required String deviceId,
@Default([]) List<AlarmModel> alarmList,
int? createdAt,
int? updatedAt,
}) = _AlarmItemModel;
factory AlarmItemModel.fromJson(Map<String, dynamic> json) =>
_$AlarmItemModelFromJson(json);
}
@freezed
abstract class AlarmModel with _$AlarmModel {
const factory AlarmModel({
required String time,
required String frequency,
String? week,
}) = _AlarmModel;
factory AlarmModel.fromJson(Map<String, dynamic> json) =>
_$AlarmModelFromJson(json);
}
extension AlarmItemModelMapper on AlarmItemModel {
List<AlarmEntity> toEntities() {
return alarmList.map((alarm) {
return AlarmEntity(
id: item.id,
deviceId: item.deviceId,
time: decoded.time,
message: item.message,
order: item.order,
dateOption: decoded.dateOption,
days: decoded.days,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
time: alarm.time,
frequency: _parseFrequency(alarm.frequency),
week: alarm.week,
);
}).toList();
}
static AlarmFrequency _parseFrequency(String value) {
return switch (value) {
'every' => AlarmFrequency.every,
'custom' => AlarmFrequency.custom,
_ => AlarmFrequency.once,
};
}
}

View File

@@ -9,43 +9,55 @@ part of 'get_alarms_response_model.dart';
_GetAlarmsResponseModel _$GetAlarmsResponseModelFromJson(
Map<String, dynamic> json,
) => _GetAlarmsResponseModel(
total: (json['total'] as num).toInt(),
items: (json['items'] as List<dynamic>)
.map((e) => AlarmItemResponseModel.fromJson(e as Map<String, dynamic>))
.toList(),
page: (json['page'] as num).toInt(),
pages: (json['pages'] as num).toInt(),
item: AlarmItemModel.fromJson(json['item'] as Map<String, dynamic>),
);
Map<String, dynamic> _$GetAlarmsResponseModelToJson(
_GetAlarmsResponseModel instance,
) => <String, dynamic>{
'total': instance.total,
'items': instance.items,
'page': instance.page,
'pages': instance.pages,
};
) => <String, dynamic>{'item': instance.item};
_AlarmItemResponseModel _$AlarmItemResponseModelFromJson(
_UpsertAlarmsResponseModel _$UpsertAlarmsResponseModelFromJson(
Map<String, dynamic> json,
) => _AlarmItemResponseModel(
id: json['id'] as String,
deviceId: json['deviceId'] as String,
time: json['time'] as String,
message: json['message'] as String,
order: (json['order'] as num).toInt(),
createdAt: (json['createdAt'] as num).toInt(),
updatedAt: (json['updatedAt'] as num?)?.toInt(),
) => _UpsertAlarmsResponseModel(
isUpdated: json['isUpdated'] as bool? ?? false,
item: AlarmItemModel.fromJson(json['item'] as Map<String, dynamic>),
);
Map<String, dynamic> _$AlarmItemResponseModelToJson(
_AlarmItemResponseModel instance,
) => <String, dynamic>{
'id': instance.id,
'deviceId': instance.deviceId,
'time': instance.time,
'message': instance.message,
'order': instance.order,
'createdAt': instance.createdAt,
'updatedAt': instance.updatedAt,
};
Map<String, dynamic> _$UpsertAlarmsResponseModelToJson(
_UpsertAlarmsResponseModel instance,
) => <String, dynamic>{'isUpdated': instance.isUpdated, 'item': instance.item};
_AlarmItemModel _$AlarmItemModelFromJson(Map<String, dynamic> json) =>
_AlarmItemModel(
id: json['id'] as String,
deviceId: json['deviceId'] as String,
alarmList:
(json['alarmList'] as List<dynamic>?)
?.map((e) => AlarmModel.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
createdAt: (json['createdAt'] as num?)?.toInt(),
updatedAt: (json['updatedAt'] as num?)?.toInt(),
);
Map<String, dynamic> _$AlarmItemModelToJson(_AlarmItemModel instance) =>
<String, dynamic>{
'id': instance.id,
'deviceId': instance.deviceId,
'alarmList': instance.alarmList,
'createdAt': instance.createdAt,
'updatedAt': instance.updatedAt,
};
_AlarmModel _$AlarmModelFromJson(Map<String, dynamic> json) => _AlarmModel(
time: json['time'] as String,
frequency: json['frequency'] as String,
week: json['week'] as String?,
);
Map<String, dynamic> _$AlarmModelToJson(_AlarmModel instance) =>
<String, dynamic>{
'time': instance.time,
'frequency': instance.frequency,
'week': instance.week,
};

View File

@@ -1,17 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'update_alarm_request_model.freezed.dart';
part 'update_alarm_request_model.g.dart';
@freezed
abstract class UpdateAlarmRequestModel with _$UpdateAlarmRequestModel {
const factory UpdateAlarmRequestModel({
required String deviceId,
required String time,
required String message,
required int order,
}) = _UpdateAlarmRequestModel;
factory UpdateAlarmRequestModel.fromJson(Map<String, dynamic> json) =>
_$UpdateAlarmRequestModelFromJson(json);
}

View File

@@ -1,286 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'update_alarm_request_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$UpdateAlarmRequestModel {
String get deviceId; String get time; String get message; int get order;
/// Create a copy of UpdateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$UpdateAlarmRequestModelCopyWith<UpdateAlarmRequestModel> get copyWith => _$UpdateAlarmRequestModelCopyWithImpl<UpdateAlarmRequestModel>(this as UpdateAlarmRequestModel, _$identity);
/// Serializes this UpdateAlarmRequestModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is UpdateAlarmRequestModel&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.time, time) || other.time == time)&&(identical(other.message, message) || other.message == message)&&(identical(other.order, order) || other.order == order));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,deviceId,time,message,order);
@override
String toString() {
return 'UpdateAlarmRequestModel(deviceId: $deviceId, time: $time, message: $message, order: $order)';
}
}
/// @nodoc
abstract mixin class $UpdateAlarmRequestModelCopyWith<$Res> {
factory $UpdateAlarmRequestModelCopyWith(UpdateAlarmRequestModel value, $Res Function(UpdateAlarmRequestModel) _then) = _$UpdateAlarmRequestModelCopyWithImpl;
@useResult
$Res call({
String deviceId, String time, String message, int order
});
}
/// @nodoc
class _$UpdateAlarmRequestModelCopyWithImpl<$Res>
implements $UpdateAlarmRequestModelCopyWith<$Res> {
_$UpdateAlarmRequestModelCopyWithImpl(this._self, this._then);
final UpdateAlarmRequestModel _self;
final $Res Function(UpdateAlarmRequestModel) _then;
/// Create a copy of UpdateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? time = null,Object? message = null,Object? order = null,}) {
return _then(_self.copyWith(
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,order: null == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [UpdateAlarmRequestModel].
extension UpdateAlarmRequestModelPatterns on UpdateAlarmRequestModel {
/// 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( _UpdateAlarmRequestModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _UpdateAlarmRequestModel() 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( _UpdateAlarmRequestModel value) $default,){
final _that = this;
switch (_that) {
case _UpdateAlarmRequestModel():
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( _UpdateAlarmRequestModel value)? $default,){
final _that = this;
switch (_that) {
case _UpdateAlarmRequestModel() 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 time, String message, int order)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _UpdateAlarmRequestModel() when $default != null:
return $default(_that.deviceId,_that.time,_that.message,_that.order);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 time, String message, int order) $default,) {final _that = this;
switch (_that) {
case _UpdateAlarmRequestModel():
return $default(_that.deviceId,_that.time,_that.message,_that.order);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 time, String message, int order)? $default,) {final _that = this;
switch (_that) {
case _UpdateAlarmRequestModel() when $default != null:
return $default(_that.deviceId,_that.time,_that.message,_that.order);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _UpdateAlarmRequestModel implements UpdateAlarmRequestModel {
const _UpdateAlarmRequestModel({required this.deviceId, required this.time, required this.message, required this.order});
factory _UpdateAlarmRequestModel.fromJson(Map<String, dynamic> json) => _$UpdateAlarmRequestModelFromJson(json);
@override final String deviceId;
@override final String time;
@override final String message;
@override final int order;
/// Create a copy of UpdateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$UpdateAlarmRequestModelCopyWith<_UpdateAlarmRequestModel> get copyWith => __$UpdateAlarmRequestModelCopyWithImpl<_UpdateAlarmRequestModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$UpdateAlarmRequestModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateAlarmRequestModel&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.time, time) || other.time == time)&&(identical(other.message, message) || other.message == message)&&(identical(other.order, order) || other.order == order));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,deviceId,time,message,order);
@override
String toString() {
return 'UpdateAlarmRequestModel(deviceId: $deviceId, time: $time, message: $message, order: $order)';
}
}
/// @nodoc
abstract mixin class _$UpdateAlarmRequestModelCopyWith<$Res> implements $UpdateAlarmRequestModelCopyWith<$Res> {
factory _$UpdateAlarmRequestModelCopyWith(_UpdateAlarmRequestModel value, $Res Function(_UpdateAlarmRequestModel) _then) = __$UpdateAlarmRequestModelCopyWithImpl;
@override @useResult
$Res call({
String deviceId, String time, String message, int order
});
}
/// @nodoc
class __$UpdateAlarmRequestModelCopyWithImpl<$Res>
implements _$UpdateAlarmRequestModelCopyWith<$Res> {
__$UpdateAlarmRequestModelCopyWithImpl(this._self, this._then);
final _UpdateAlarmRequestModel _self;
final $Res Function(_UpdateAlarmRequestModel) _then;
/// Create a copy of UpdateAlarmRequestModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? time = null,Object? message = null,Object? order = null,}) {
return _then(_UpdateAlarmRequestModel(
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,order: null == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

View File

@@ -1,25 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'update_alarm_request_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_UpdateAlarmRequestModel _$UpdateAlarmRequestModelFromJson(
Map<String, dynamic> json,
) => _UpdateAlarmRequestModel(
deviceId: json['deviceId'] as String,
time: json['time'] as String,
message: json['message'] as String,
order: (json['order'] as num).toInt(),
);
Map<String, dynamic> _$UpdateAlarmRequestModelToJson(
_UpdateAlarmRequestModel instance,
) => <String, dynamic>{
'deviceId': instance.deviceId,
'time': instance.time,
'message': instance.message,
'order': instance.order,
};

View File

@@ -1,7 +1,4 @@
import 'package:settings/src/core/data/datasources/alarm_remote_datasource.dart';
import 'package:settings/src/core/data/models/create_alarm_request_model.dart';
import 'package:settings/src/core/data/models/get_alarms_response_model.dart';
import 'package:settings/src/core/data/models/update_alarm_request_model.dart';
import 'package:settings/src/core/domain/repositories/alarm_repository.dart';
import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
@@ -11,26 +8,12 @@ class AlarmRepositoryImpl implements AlarmRepository {
final AlarmRemoteDatasource _remote;
@override
Future<List<AlarmEntity>> getAlarms({required String deviceId}) async {
final response = await _remote.getAlarms(deviceId: deviceId);
return response.toEntity();
}
Future<List<AlarmEntity>> getAlarms({required String deviceId}) =>
_remote.getAlarms(deviceId: deviceId);
@override
Future<void> createAlarm({required CreateAlarmRequestModel request}) {
return _remote.createAlarm(request: request);
}
@override
Future<void> updateAlarm({
required String alarmId,
required UpdateAlarmRequestModel request,
}) {
return _remote.updateAlarm(alarmId: alarmId, request: request);
}
@override
Future<void> deleteAlarm({required String alarmId}) {
return _remote.deleteAlarm(alarmId: alarmId);
}
Future<List<AlarmEntity>> upsertAlarms({
required String deviceId,
required List<AlarmEntity> alarms,
}) => _remote.upsertAlarms(deviceId: deviceId, alarms: alarms);
}

View File

@@ -1,13 +1,9 @@
import 'package:settings/src/core/data/models/create_alarm_request_model.dart';
import 'package:settings/src/core/data/models/update_alarm_request_model.dart';
import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
abstract class AlarmRepository {
Future<List<AlarmEntity>> getAlarms({required String deviceId});
Future<void> createAlarm({required CreateAlarmRequestModel request});
Future<void> updateAlarm({
required String alarmId,
required UpdateAlarmRequestModel request,
Future<List<AlarmEntity>> upsertAlarms({
required String deviceId,
required List<AlarmEntity> alarms,
});
Future<void> deleteAlarm({required String alarmId});
}

View File

@@ -1,77 +1,14 @@
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'alarm_entity.freezed.dart';
enum AlarmDateOption { once, daily, custom }
enum AlarmFrequency { once, every, custom }
@freezed
abstract class AlarmEntity with _$AlarmEntity {
const AlarmEntity._();
const factory AlarmEntity({
required String id,
required String deviceId,
required TimeOfDay time,
required String message,
required int order,
required AlarmDateOption dateOption,
@Default([false, false, false, false, false, false, false]) List<bool> days,
int? createdAt,
int? updatedAt,
required String time,
required AlarmFrequency frequency,
String? week,
}) = _AlarmEntity;
/// Encodes time + dateOption + days into API format: HH:MM-1-{type}[-XXXXXXX]
String get encodedTime {
final hh = time.hour.toString().padLeft(2, '0');
final mm = time.minute.toString().padLeft(2, '0');
switch (dateOption) {
case AlarmDateOption.once:
return '$hh:$mm-1-1';
case AlarmDateOption.daily:
return '$hh:$mm-1-2';
case AlarmDateOption.custom:
final daysBits = days.map((d) => d ? '1' : '0').join();
return '$hh:$mm-1-3-$daysBits';
}
}
/// Parses API time format: HH:MM-1-{type}[-XXXXXXX]
static ({TimeOfDay time, AlarmDateOption dateOption, List<bool> days})
decodeTime(String raw) {
final parts = raw.split('-');
final timeParts = parts[0].split(':');
final time = TimeOfDay(
hour: int.parse(timeParts[0]),
minute: int.parse(timeParts[1]),
);
final type = int.parse(parts[2]);
switch (type) {
case 1:
return (
time: time,
dateOption: AlarmDateOption.once,
days: List.filled(7, false),
);
case 2:
return (
time: time,
dateOption: AlarmDateOption.daily,
days: List.filled(7, false),
);
case 3:
final daysStr = parts[3];
final days = List.generate(7, (i) => daysStr[i] == '1');
return (time: time, dateOption: AlarmDateOption.custom, days: days);
default:
return (
time: time,
dateOption: AlarmDateOption.once,
days: List.filled(7, false),
);
}
}
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AlarmEntity {
String get id; String get deviceId; TimeOfDay get time; String get message; int get order; AlarmDateOption get dateOption; List<bool> get days; int? get createdAt; int? get updatedAt;
String get time; AlarmFrequency get frequency; String? get week;
/// Create a copy of AlarmEntity
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $AlarmEntityCopyWith<AlarmEntity> get copyWith => _$AlarmEntityCopyWithImpl<Alar
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AlarmEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.time, time) || other.time == time)&&(identical(other.message, message) || other.message == message)&&(identical(other.order, order) || other.order == order)&&(identical(other.dateOption, dateOption) || other.dateOption == dateOption)&&const DeepCollectionEquality().equals(other.days, days)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AlarmEntity&&(identical(other.time, time) || other.time == time)&&(identical(other.frequency, frequency) || other.frequency == frequency)&&(identical(other.week, week) || other.week == week));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,time,message,order,dateOption,const DeepCollectionEquality().hash(days),createdAt,updatedAt);
int get hashCode => Object.hash(runtimeType,time,frequency,week);
@override
String toString() {
return 'AlarmEntity(id: $id, deviceId: $deviceId, time: $time, message: $message, order: $order, dateOption: $dateOption, days: $days, createdAt: $createdAt, updatedAt: $updatedAt)';
return 'AlarmEntity(time: $time, frequency: $frequency, week: $week)';
}
@@ -45,7 +45,7 @@ abstract mixin class $AlarmEntityCopyWith<$Res> {
factory $AlarmEntityCopyWith(AlarmEntity value, $Res Function(AlarmEntity) _then) = _$AlarmEntityCopyWithImpl;
@useResult
$Res call({
String id, String deviceId, TimeOfDay time, String message, int order, AlarmDateOption dateOption, List<bool> days, int? createdAt, int? updatedAt
String time, AlarmFrequency frequency, String? week
});
@@ -62,18 +62,12 @@ class _$AlarmEntityCopyWithImpl<$Res>
/// 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,Object? deviceId = null,Object? time = null,Object? message = null,Object? order = null,Object? dateOption = null,Object? days = null,Object? createdAt = freezed,Object? updatedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? time = null,Object? frequency = null,Object? week = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as TimeOfDay,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,order: null == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as int,dateOption: null == dateOption ? _self.dateOption : dateOption // ignore: cast_nullable_to_non_nullable
as AlarmDateOption,days: null == days ? _self.days : days // ignore: cast_nullable_to_non_nullable
as List<bool>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as int?,
time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as String,frequency: null == frequency ? _self.frequency : frequency // ignore: cast_nullable_to_non_nullable
as AlarmFrequency,week: freezed == week ? _self.week : week // ignore: cast_nullable_to_non_nullable
as String?,
));
}
@@ -158,10 +152,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceId, TimeOfDay time, String message, int order, AlarmDateOption dateOption, List<bool> days, int? createdAt, int? updatedAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String time, AlarmFrequency frequency, String? week)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AlarmEntity() when $default != null:
return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order,_that.dateOption,_that.days,_that.createdAt,_that.updatedAt);case _:
return $default(_that.time,_that.frequency,_that.week);case _:
return orElse();
}
@@ -179,10 +173,10 @@ return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order,_th
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceId, TimeOfDay time, String message, int order, AlarmDateOption dateOption, List<bool> days, int? createdAt, int? updatedAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String time, AlarmFrequency frequency, String? week) $default,) {final _that = this;
switch (_that) {
case _AlarmEntity():
return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order,_that.dateOption,_that.days,_that.createdAt,_that.updatedAt);case _:
return $default(_that.time,_that.frequency,_that.week);case _:
throw StateError('Unexpected subclass');
}
@@ -199,10 +193,10 @@ return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order,_th
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceId, TimeOfDay time, String message, int order, AlarmDateOption dateOption, List<bool> days, int? createdAt, int? updatedAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String time, AlarmFrequency frequency, String? week)? $default,) {final _that = this;
switch (_that) {
case _AlarmEntity() when $default != null:
return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order,_that.dateOption,_that.days,_that.createdAt,_that.updatedAt);case _:
return $default(_that.time,_that.frequency,_that.week);case _:
return null;
}
@@ -213,25 +207,13 @@ return $default(_that.id,_that.deviceId,_that.time,_that.message,_that.order,_th
/// @nodoc
class _AlarmEntity extends AlarmEntity {
const _AlarmEntity({required this.id, required this.deviceId, required this.time, required this.message, required this.order, required this.dateOption, final List<bool> days = const [false, false, false, false, false, false, false], this.createdAt, this.updatedAt}): _days = days,super._();
class _AlarmEntity implements AlarmEntity {
const _AlarmEntity({required this.time, required this.frequency, this.week});
@override final String id;
@override final String deviceId;
@override final TimeOfDay time;
@override final String message;
@override final int order;
@override final AlarmDateOption dateOption;
final List<bool> _days;
@override@JsonKey() List<bool> get days {
if (_days is EqualUnmodifiableListView) return _days;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_days);
}
@override final int? createdAt;
@override final int? updatedAt;
@override final String time;
@override final AlarmFrequency frequency;
@override final String? week;
/// Create a copy of AlarmEntity
/// with the given fields replaced by the non-null parameter values.
@@ -243,16 +225,16 @@ _$AlarmEntityCopyWith<_AlarmEntity> get copyWith => __$AlarmEntityCopyWithImpl<_
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AlarmEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.time, time) || other.time == time)&&(identical(other.message, message) || other.message == message)&&(identical(other.order, order) || other.order == order)&&(identical(other.dateOption, dateOption) || other.dateOption == dateOption)&&const DeepCollectionEquality().equals(other._days, _days)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AlarmEntity&&(identical(other.time, time) || other.time == time)&&(identical(other.frequency, frequency) || other.frequency == frequency)&&(identical(other.week, week) || other.week == week));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,time,message,order,dateOption,const DeepCollectionEquality().hash(_days),createdAt,updatedAt);
int get hashCode => Object.hash(runtimeType,time,frequency,week);
@override
String toString() {
return 'AlarmEntity(id: $id, deviceId: $deviceId, time: $time, message: $message, order: $order, dateOption: $dateOption, days: $days, createdAt: $createdAt, updatedAt: $updatedAt)';
return 'AlarmEntity(time: $time, frequency: $frequency, week: $week)';
}
@@ -263,7 +245,7 @@ abstract mixin class _$AlarmEntityCopyWith<$Res> implements $AlarmEntityCopyWith
factory _$AlarmEntityCopyWith(_AlarmEntity value, $Res Function(_AlarmEntity) _then) = __$AlarmEntityCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceId, TimeOfDay time, String message, int order, AlarmDateOption dateOption, List<bool> days, int? createdAt, int? updatedAt
String time, AlarmFrequency frequency, String? week
});
@@ -280,18 +262,12 @@ class __$AlarmEntityCopyWithImpl<$Res>
/// 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,Object? deviceId = null,Object? time = null,Object? message = null,Object? order = null,Object? dateOption = null,Object? days = null,Object? createdAt = freezed,Object? updatedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? time = null,Object? frequency = null,Object? week = freezed,}) {
return _then(_AlarmEntity(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as TimeOfDay,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,order: null == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as int,dateOption: null == dateOption ? _self.dateOption : dateOption // ignore: cast_nullable_to_non_nullable
as AlarmDateOption,days: null == days ? _self._days : days // ignore: cast_nullable_to_non_nullable
as List<bool>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as int?,
time: null == time ? _self.time : time // ignore: cast_nullable_to_non_nullable
as String,frequency: null == frequency ? _self.frequency : frequency // ignore: cast_nullable_to_non_nullable
as AlarmFrequency,week: freezed == week ? _self.week : week // ignore: cast_nullable_to_non_nullable
as String?,
));
}

View File

@@ -1,10 +1,10 @@
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:settings/src/features/alarm/presentation/state/alarm_view_state.dart';
import 'package:settings/src/features/alarm/presentation/widgets/alarm_form_screen.dart'
show showAlarmForm;
import 'package:sf_localizations/sf_localizations.dart';
@@ -18,33 +18,39 @@ class AlarmScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final state = ref.watch(alarmViewModelProvider);
final vm = ref.read(alarmViewModelProvider.notifier);
final (alarms, isLoading, isSaving, maxAlarms) = ref.watch(
alarmViewModelProvider.select(
(s) => (s.alarms, s.isLoading, s.isSaving, s.maxAlarms),
),
);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
ref.listen(alarmViewModelProvider.select((s) => s.errorMessage), (
_,
errorMessage,
) {
if (errorMessage.isNotEmpty) {
showTopSnackbar(
context,
message: errorMessage,
type: MessageType.error,
);
}
ref.listen(alarmViewModelProvider.select((s) => s.errorEvent), (_, next) {
if (next == null) return;
final message = switch (next) {
AlarmErrorEvent.load || AlarmErrorEvent.save => context.translate(
I18n.alarmError,
),
AlarmErrorEvent.maxAlarms => context
.translate(I18n.alarmMaxReached)
.replaceAll('{max}', maxAlarms.toString()),
};
showTopSnackbar(context, message: message, type: MessageType.error);
vm.clearError();
});
ref.listen(alarmViewModelProvider.select((s) => s.successMessage), (
ref.listen(alarmViewModelProvider.select((s) => s.saveSuccess), (
_,
successMessage,
success,
) {
if (successMessage.isNotEmpty) {
if (success) {
showTopSnackbar(
context,
message: context.translate(successMessage),
message: context.translate(I18n.alarmSaved),
type: MessageType.success,
);
ref.read(alarmViewModelProvider.notifier).clearSuccess();
vm.clearSaveSuccess();
}
});
@@ -74,7 +80,7 @@ class AlarmScreen extends ConsumerWidget {
),
),
actions: [
if (state.alarms.length < 3)
if (alarms.length < maxAlarms)
Padding(
padding: EdgeInsets.only(
right: SizeUtils.getByScreen(small: 16, big: 14),
@@ -85,7 +91,7 @@ class AlarmScreen extends ConsumerWidget {
shape: BoxShape.circle,
),
child: IconButton(
onPressed: () => _openForm(context),
onPressed: () => _openForm(context, vm),
icon: Icon(
Icons.add,
color: Colors.white,
@@ -98,17 +104,50 @@ class AlarmScreen extends ConsumerWidget {
),
body: SafeArea(
top: false,
child: state.isLoading
child: isLoading
? const Center(child: CircularProgressIndicator())
: state.alarms.isEmpty
? _EmptyState(primaryColor: primaryColor)
: _AlarmList(onEdit: (alarm) => _openForm(context, alarm: alarm)),
: alarms.isEmpty
? _EmptyState(primaryColor: primaryColor)
: _AlarmList(
alarms: alarms,
onEdit: (index, alarm) =>
_openForm(context, vm, index: index, alarm: alarm),
onDelete: vm.removeAlarm,
),
),
bottomNavigationBar: (!isLoading && alarms.isNotEmpty)
? Padding(
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: isSaving
? const Center(child: CircularProgressIndicator())
: PrimaryButton(
onPressed: vm.save,
text: context.translate(I18n.alarmSave),
color: primaryColor,
),
)
: null,
);
}
void _openForm(BuildContext context, {AlarmEntity? alarm}) {
showAlarmForm(context, alarm: alarm);
void _openForm(
BuildContext context,
AlarmViewModel vm, {
int? index,
AlarmEntity? alarm,
}) {
showAlarmForm(
context,
alarm: alarm,
onSave: (result) {
if (index != null) {
vm.updateAlarm(index, result);
} else {
vm.addAlarm(result);
}
},
);
}
}
@@ -143,14 +182,18 @@ class _EmptyState extends StatelessWidget {
}
class _AlarmList extends ConsumerWidget {
final void Function(AlarmEntity alarm) onEdit;
final List<AlarmEntity> alarms;
final void Function(int index, AlarmEntity alarm) onEdit;
final void Function(int index) onDelete;
const _AlarmList({required this.onEdit});
const _AlarmList({
required this.alarms,
required this.onEdit,
required this.onDelete,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final alarms = ref.watch(alarmViewModelProvider.select((s) => s.alarms));
return SingleChildScrollView(
child: Padding(
padding: SizeUtils.getByScreen(
@@ -158,8 +201,12 @@ class _AlarmList extends ConsumerWidget {
big: EdgeInsets.symmetric(horizontal: 21, vertical: 8),
),
child: Column(
children: alarms.map((alarm) {
return _AlarmCard(alarm: alarm, onEdit: () => onEdit(alarm));
children: alarms.asMap().entries.map((entry) {
return _AlarmCard(
alarm: entry.value,
onEdit: () => onEdit(entry.key, entry.value),
onDelete: () => onDelete(entry.key),
);
}).toList(),
),
),
@@ -170,35 +217,24 @@ class _AlarmList extends ConsumerWidget {
class _AlarmCard extends ConsumerWidget {
final AlarmEntity alarm;
final VoidCallback onEdit;
final VoidCallback onDelete;
const _AlarmCard({required this.alarm, required this.onEdit});
const _AlarmCard({
required this.alarm,
required this.onEdit,
required this.onDelete,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
final hh = alarm.time.hour.toString().padLeft(2, '0');
final mm = alarm.time.minute.toString().padLeft(2, '0');
String typeLabel;
String? daysLabel;
switch (alarm.dateOption) {
case AlarmDateOption.once:
typeLabel = context.translate(I18n.once);
case AlarmDateOption.daily:
typeLabel = context.translate(I18n.daily);
daysLabel = context.translate(I18n.daily);
case AlarmDateOption.custom:
typeLabel = context.translate(I18n.selectDay);
final active = <String>[];
for (final entry in weekDayI18nKeys.entries) {
final dayIndex = entry.key - 1;
if (alarm.days[dayIndex]) {
active.add(weekDayShortLabel(context, entry.value));
}
}
daysLabel = active.join(' ');
}
final frequencyLabel = switch (alarm.frequency) {
AlarmFrequency.once => context.translate(I18n.once),
AlarmFrequency.every => context.translate(I18n.daily),
AlarmFrequency.custom => context.translate(I18n.selectDay),
};
return Padding(
padding: EdgeInsets.only(
@@ -220,7 +256,7 @@ class _AlarmCard extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'$hh:$mm',
alarm.time,
style: TextStyle(
fontWeight: FontWeight.bold,
color: theme.getColorFor(ThemeCode.textPrimary),
@@ -228,7 +264,7 @@ class _AlarmCard extends ConsumerWidget {
),
),
Text(
typeLabel,
frequencyLabel,
style: TextStyle(
color: primaryColor,
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
@@ -237,27 +273,10 @@ class _AlarmCard extends ConsumerWidget {
),
],
),
SizedBox(height: 4),
Text(
alarm.message,
style: TextStyle(
color: theme.getColorFor(ThemeCode.textPrimary).withAlpha(178),
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (daysLabel != null) ...[
SizedBox(height: 2),
Text(
daysLabel,
style: TextStyle(
color: theme
.getColorFor(ThemeCode.textPrimary)
.withAlpha(128),
fontSize: SizeUtils.getByScreen(small: 12, big: 13),
),
),
if (alarm.frequency == AlarmFrequency.custom &&
alarm.week != null) ...[
SizedBox(height: 6),
_WeekDaysDisplay(week: alarm.week!, primaryColor: primaryColor),
],
SizedBox(height: 8),
Row(
@@ -272,7 +291,7 @@ class _AlarmCard extends ConsumerWidget {
_ActionButton(
icon: Icons.delete_outline,
color: Colors.red,
onPressed: () => _confirmDelete(context, ref),
onPressed: onDelete,
),
],
),
@@ -281,29 +300,35 @@ class _AlarmCard extends ConsumerWidget {
),
);
}
}
void _confirmDelete(BuildContext context, WidgetRef ref) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(context.translate(I18n.deleteAlarm)),
content: Text(context.translate(I18n.deleteAlarmConfirm)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.translate(I18n.cancel)),
),
TextButton(
onPressed: () {
ref.read(alarmViewModelProvider.notifier).deleteAlarm(alarm.id);
Navigator.pop(context);
},
child: Text(
context.translate(I18n.delete),
style: TextStyle(color: Colors.red),
),
),
],
class _WeekDaysDisplay extends StatelessWidget {
final String week;
final Color primaryColor;
const _WeekDaysDisplay({required this.week, required this.primaryColor});
@override
Widget build(BuildContext context) {
final labels = [
context.translate(I18n.weekdayMonShort),
context.translate(I18n.weekdayTueShort),
context.translate(I18n.weekdayWedShort),
context.translate(I18n.weekdayThuShort),
context.translate(I18n.weekdayFriShort),
context.translate(I18n.weekdaySatShort),
context.translate(I18n.weekdaySunShort),
];
final chars = week.padRight(7, '0').split('');
final activeLabels = <String>[];
for (int i = 0; i < 7; i++) {
if (chars[i] == '1') activeLabels.add(labels[i]);
}
return Text(
activeLabels.join(' '),
style: TextStyle(
color: primaryColor.withValues(alpha: 0.7),
fontSize: SizeUtils.getByScreen(small: 12, big: 13),
),
);
}

View File

@@ -1,17 +1,12 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:settings/src/core/data/models/create_alarm_request_model.dart';
import 'package:settings/src/core/data/models/update_alarm_request_model.dart';
import 'package:settings/src/core/domain/repositories/alarm_repository.dart';
import 'package:settings/src/core/providers/alarm_repository_provider.dart';
import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
import 'package:settings/src/features/alarm/presentation/state/alarm_view_state.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_tracking/sf_tracking.dart';
import 'package:uuid/uuid.dart';
final alarmViewModelProvider =
NotifierProvider.autoDispose<AlarmViewModel, AlarmViewState>(
@@ -26,130 +21,100 @@ class AlarmViewModel extends Notifier<AlarmViewState> {
AlarmViewState build() {
_repository = ref.read(alarmRepositoryProvider);
_tracking = ref.read(sfTrackingProvider);
final capabilities = ref.read(selectedDeviceProvider).value?.capabilities;
final maxAlarms = capabilities?.alarms?.maxAlarms ?? 3;
Future.microtask(_load);
return const AlarmViewState();
return AlarmViewState(maxAlarms: maxAlarms);
}
String _formatTime(TimeOfDay time) {
final hh = time.hour.toString().padLeft(2, '0');
final mm = time.minute.toString().padLeft(2, '0');
return '$hh:$mm';
}
String? get _deviceId => ref.read(selectedDeviceProvider).value?.id;
Future<void> _load() async {
try {
final device = ref.read(selectedDeviceProvider).value;
if (device == null) return;
final deviceId = _deviceId;
if (deviceId == null) {
state = state.copyWith(isLoading: false);
return;
}
final alarms = await _repository.getAlarms(deviceId: device.id);
try {
final alarms = await _repository.getAlarms(deviceId: deviceId);
if (!ref.mounted) return;
state = state.copyWith(alarms: alarms, isLoading: false);
} catch (e) {
} catch (_) {
if (!ref.mounted) return;
state = state.copyWith(
isLoading: false,
errorMessage: formatErrorMessage(e),
errorEvent: AlarmErrorEvent.load,
);
}
}
Future<void> createAlarm(AlarmEntity alarm) async {
state = state.copyWith(isSaving: true, errorMessage: '');
void addAlarm(AlarmEntity alarm) {
if (state.alarms.length >= state.maxAlarms) {
state = state.copyWith(errorEvent: AlarmErrorEvent.maxAlarms);
return;
}
state = state.copyWith(
alarms: [...state.alarms, alarm],
errorEvent: null,
);
}
void updateAlarm(int index, AlarmEntity alarm) {
if (index < 0 || index >= state.alarms.length) return;
final updated = [...state.alarms];
updated[index] = alarm;
state = state.copyWith(alarms: updated, errorEvent: null);
}
void removeAlarm(int index) {
if (index < 0 || index >= state.alarms.length) return;
final updated = [...state.alarms]..removeAt(index);
state = state.copyWith(alarms: updated, errorEvent: null);
}
Future<void> save() async {
final deviceId = _deviceId;
if (deviceId == null) return;
state = state.copyWith(
isSaving: true,
saveSuccess: false,
errorEvent: null,
);
try {
final device = ref.read(selectedDeviceProvider).value;
if (device == null) return;
final id = const Uuid().v4();
final request = CreateAlarmRequestModel(
id: id,
deviceId: device.id,
time: alarm.encodedTime,
message: alarm.message,
order: state.alarms.length + 1,
final alarms = await _repository.upsertAlarms(
deviceId: deviceId,
alarms: state.alarms,
);
if (!ref.mounted) return;
await _repository.createAlarm(request: request);
final updatedAlarm = alarm.copyWith(
id: id,
deviceId: device.id,
order: state.alarms.length + 1,
state = state.copyWith(
isSaving: false,
alarms: alarms,
saveSuccess: true,
);
unawaited(
_tracking.legacySettingsAlarmAdded(time: _formatTime(alarm.time)),
_tracking.legacySettingsAlarmsSaved(alarmsCount: alarms.length),
);
state = state.copyWith(
alarms: [...state.alarms, updatedAlarm],
isSaving: false,
successMessage: I18n.alarmCreated,
);
} catch (e) {
} catch (_) {
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
errorMessage: formatErrorMessage(e),
errorEvent: AlarmErrorEvent.save,
);
}
}
Future<void> updateAlarm(AlarmEntity alarm) async {
state = state.copyWith(isSaving: true, errorMessage: '');
try {
final request = UpdateAlarmRequestModel(
deviceId: alarm.deviceId,
time: alarm.encodedTime,
message: alarm.message,
order: alarm.order,
);
await _repository.updateAlarm(alarmId: alarm.id, request: request);
final updatedAlarms = state.alarms
.map((a) => a.id == alarm.id ? alarm : a)
.toList();
unawaited(
_tracking.legacySettingsAlarmUpdated(time: _formatTime(alarm.time)),
);
state = state.copyWith(
alarms: updatedAlarms,
isSaving: false,
successMessage: I18n.alarmUpdated,
);
} catch (e) {
state = state.copyWith(
isSaving: false,
errorMessage: formatErrorMessage(e),
);
}
void clearError() {
if (state.errorEvent != null) state = state.copyWith(errorEvent: null);
}
Future<void> deleteAlarm(String alarmId) async {
state = state.copyWith(isSaving: true, errorMessage: '');
try {
await _repository.deleteAlarm(alarmId: alarmId);
final updatedAlarms = state.alarms.where((a) => a.id != alarmId).toList();
unawaited(_tracking.legacySettingsAlarmRemoved());
state = state.copyWith(
alarms: updatedAlarms,
isSaving: false,
successMessage: I18n.alarmDeleted,
);
} catch (e) {
state = state.copyWith(
isSaving: false,
errorMessage: formatErrorMessage(e),
);
}
}
void clearSuccess() {
state = state.copyWith(successMessage: '');
void clearSaveSuccess() {
if (state.saveSuccess) state = state.copyWith(saveSuccess: false);
}
}

View File

@@ -3,13 +3,16 @@ import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
part 'alarm_view_state.freezed.dart';
enum AlarmErrorEvent { load, save, maxAlarms }
@freezed
abstract class AlarmViewState with _$AlarmViewState {
const factory AlarmViewState({
@Default([]) List<AlarmEntity> alarms,
@Default(true) bool isLoading,
@Default(false) bool isSaving,
@Default('') String successMessage,
@Default('') String errorMessage,
@Default(3) int maxAlarms,
AlarmErrorEvent? errorEvent,
@Default(false) bool saveSuccess,
}) = _AlarmViewState;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AlarmViewState {
List<AlarmEntity> get alarms; bool get isLoading; bool get isSaving; String get successMessage; String get errorMessage;
List<AlarmEntity> get alarms; bool get isLoading; bool get isSaving; int get maxAlarms; AlarmErrorEvent? get errorEvent; bool get saveSuccess;
/// Create a copy of AlarmViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $AlarmViewStateCopyWith<AlarmViewState> get copyWith => _$AlarmViewStateCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AlarmViewState&&const DeepCollectionEquality().equals(other.alarms, alarms)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AlarmViewState&&const DeepCollectionEquality().equals(other.alarms, alarms)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.maxAlarms, maxAlarms) || other.maxAlarms == maxAlarms)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.saveSuccess, saveSuccess) || other.saveSuccess == saveSuccess));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(alarms),isLoading,isSaving,successMessage,errorMessage);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(alarms),isLoading,isSaving,maxAlarms,errorEvent,saveSuccess);
@override
String toString() {
return 'AlarmViewState(alarms: $alarms, isLoading: $isLoading, isSaving: $isSaving, successMessage: $successMessage, errorMessage: $errorMessage)';
return 'AlarmViewState(alarms: $alarms, isLoading: $isLoading, isSaving: $isSaving, maxAlarms: $maxAlarms, errorEvent: $errorEvent, saveSuccess: $saveSuccess)';
}
@@ -45,7 +45,7 @@ abstract mixin class $AlarmViewStateCopyWith<$Res> {
factory $AlarmViewStateCopyWith(AlarmViewState value, $Res Function(AlarmViewState) _then) = _$AlarmViewStateCopyWithImpl;
@useResult
$Res call({
List<AlarmEntity> alarms, bool isLoading, bool isSaving, String successMessage, String errorMessage
List<AlarmEntity> alarms, bool isLoading, bool isSaving, int maxAlarms, AlarmErrorEvent? errorEvent, bool saveSuccess
});
@@ -62,14 +62,15 @@ class _$AlarmViewStateCopyWithImpl<$Res>
/// 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? isLoading = null,Object? isSaving = null,Object? successMessage = null,Object? errorMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? alarms = null,Object? isLoading = null,Object? isSaving = null,Object? maxAlarms = null,Object? errorEvent = freezed,Object? saveSuccess = null,}) {
return _then(_self.copyWith(
alarms: null == alarms ? _self.alarms : alarms // ignore: cast_nullable_to_non_nullable
as List<AlarmEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
as bool,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,maxAlarms: null == maxAlarms ? _self.maxAlarms : maxAlarms // ignore: cast_nullable_to_non_nullable
as int,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as AlarmErrorEvent?,saveSuccess: null == saveSuccess ? _self.saveSuccess : saveSuccess // ignore: cast_nullable_to_non_nullable
as bool,
));
}
@@ -154,10 +155,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<AlarmEntity> alarms, bool isLoading, bool isSaving, String successMessage, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<AlarmEntity> alarms, bool isLoading, bool isSaving, int maxAlarms, AlarmErrorEvent? errorEvent, bool saveSuccess)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AlarmViewState() when $default != null:
return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.successMessage,_that.errorMessage);case _:
return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.maxAlarms,_that.errorEvent,_that.saveSuccess);case _:
return orElse();
}
@@ -175,10 +176,10 @@ return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.successMessage
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<AlarmEntity> alarms, bool isLoading, bool isSaving, String successMessage, String errorMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<AlarmEntity> alarms, bool isLoading, bool isSaving, int maxAlarms, AlarmErrorEvent? errorEvent, bool saveSuccess) $default,) {final _that = this;
switch (_that) {
case _AlarmViewState():
return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.successMessage,_that.errorMessage);case _:
return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.maxAlarms,_that.errorEvent,_that.saveSuccess);case _:
throw StateError('Unexpected subclass');
}
@@ -195,10 +196,10 @@ return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.successMessage
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<AlarmEntity> alarms, bool isLoading, bool isSaving, String successMessage, String errorMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<AlarmEntity> alarms, bool isLoading, bool isSaving, int maxAlarms, AlarmErrorEvent? errorEvent, bool saveSuccess)? $default,) {final _that = this;
switch (_that) {
case _AlarmViewState() when $default != null:
return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.successMessage,_that.errorMessage);case _:
return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.maxAlarms,_that.errorEvent,_that.saveSuccess);case _:
return null;
}
@@ -210,7 +211,7 @@ return $default(_that.alarms,_that.isLoading,_that.isSaving,_that.successMessage
class _AlarmViewState implements AlarmViewState {
const _AlarmViewState({final List<AlarmEntity> alarms = const [], this.isLoading = true, this.isSaving = false, this.successMessage = '', this.errorMessage = ''}): _alarms = alarms;
const _AlarmViewState({final List<AlarmEntity> alarms = const [], this.isLoading = true, this.isSaving = false, this.maxAlarms = 3, this.errorEvent, this.saveSuccess = false}): _alarms = alarms;
final List<AlarmEntity> _alarms;
@@ -222,8 +223,9 @@ class _AlarmViewState implements AlarmViewState {
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isSaving;
@override@JsonKey() final String successMessage;
@override@JsonKey() final String errorMessage;
@override@JsonKey() final int maxAlarms;
@override final AlarmErrorEvent? errorEvent;
@override@JsonKey() final bool saveSuccess;
/// Create a copy of AlarmViewState
/// with the given fields replaced by the non-null parameter values.
@@ -235,16 +237,16 @@ _$AlarmViewStateCopyWith<_AlarmViewState> get copyWith => __$AlarmViewStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AlarmViewState&&const DeepCollectionEquality().equals(other._alarms, _alarms)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AlarmViewState&&const DeepCollectionEquality().equals(other._alarms, _alarms)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.maxAlarms, maxAlarms) || other.maxAlarms == maxAlarms)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.saveSuccess, saveSuccess) || other.saveSuccess == saveSuccess));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_alarms),isLoading,isSaving,successMessage,errorMessage);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_alarms),isLoading,isSaving,maxAlarms,errorEvent,saveSuccess);
@override
String toString() {
return 'AlarmViewState(alarms: $alarms, isLoading: $isLoading, isSaving: $isSaving, successMessage: $successMessage, errorMessage: $errorMessage)';
return 'AlarmViewState(alarms: $alarms, isLoading: $isLoading, isSaving: $isSaving, maxAlarms: $maxAlarms, errorEvent: $errorEvent, saveSuccess: $saveSuccess)';
}
@@ -255,7 +257,7 @@ abstract mixin class _$AlarmViewStateCopyWith<$Res> implements $AlarmViewStateCo
factory _$AlarmViewStateCopyWith(_AlarmViewState value, $Res Function(_AlarmViewState) _then) = __$AlarmViewStateCopyWithImpl;
@override @useResult
$Res call({
List<AlarmEntity> alarms, bool isLoading, bool isSaving, String successMessage, String errorMessage
List<AlarmEntity> alarms, bool isLoading, bool isSaving, int maxAlarms, AlarmErrorEvent? errorEvent, bool saveSuccess
});
@@ -272,14 +274,15 @@ class __$AlarmViewStateCopyWithImpl<$Res>
/// 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? isLoading = null,Object? isSaving = null,Object? successMessage = null,Object? errorMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? alarms = null,Object? isLoading = null,Object? isSaving = null,Object? maxAlarms = null,Object? errorEvent = freezed,Object? saveSuccess = null,}) {
return _then(_AlarmViewState(
alarms: null == alarms ? _self._alarms : alarms // ignore: cast_nullable_to_non_nullable
as List<AlarmEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
as bool,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
as bool,maxAlarms: null == maxAlarms ? _self.maxAlarms : maxAlarms // ignore: cast_nullable_to_non_nullable
as int,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as AlarmErrorEvent?,saveSuccess: null == saveSuccess ? _self.saveSuccess : saveSuccess // ignore: cast_nullable_to_non_nullable
as bool,
));
}

View File

@@ -4,23 +4,27 @@ 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/entities/alarm_entity.dart';
import 'package:settings/src/features/alarm/presentation/state/alarm_view_model.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
void showAlarmForm(BuildContext context, {AlarmEntity? alarm}) {
void showAlarmForm(
BuildContext context, {
AlarmEntity? alarm,
required ValueChanged<AlarmEntity> onSave,
}) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => _AlarmFormSheet(alarm: alarm),
builder: (_) => _AlarmFormSheet(alarm: alarm, onSave: onSave),
);
}
class _AlarmFormSheet extends ConsumerStatefulWidget {
final AlarmEntity? alarm;
final ValueChanged<AlarmEntity> onSave;
const _AlarmFormSheet({this.alarm});
const _AlarmFormSheet({this.alarm, required this.onSave});
@override
ConsumerState<_AlarmFormSheet> createState() => _AlarmFormSheetState();
@@ -28,33 +32,30 @@ class _AlarmFormSheet extends ConsumerStatefulWidget {
class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
late Duration _duration;
late AlarmDateOption _dateOption;
late AlarmFrequency _frequency;
late List<bool> _days;
late TextEditingController _messageController;
bool get _isEditing => widget.alarm != null;
@override
void initState() {
super.initState();
final time = widget.alarm?.time ?? const TimeOfDay(hour: 8, minute: 0);
_duration = Duration(hours: time.hour, minutes: time.minute);
_dateOption = widget.alarm?.dateOption ?? AlarmDateOption.once;
_days = widget.alarm?.days.toList() ?? List.filled(7, false);
_messageController = TextEditingController(
text: widget.alarm?.message ?? '',
);
}
@override
void dispose() {
_messageController.dispose();
super.dispose();
if (widget.alarm != null) {
final parts = widget.alarm!.time.split(':');
final hour = int.tryParse(parts[0]) ?? 8;
final minute = int.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0;
_duration = Duration(hours: hour, minutes: minute);
_frequency = widget.alarm!.frequency;
_days = widget.alarm!.week != null
? widget.alarm!.week!.padRight(7, '0').split('').map((c) => c == '1').toList()
: List.filled(7, false);
} else {
_duration = const Duration(hours: 8);
_frequency = AlarmFrequency.once;
_days = List.filled(7, false);
}
}
bool get _canSave {
if (_messageController.text.trim().isEmpty) return false;
if (_dateOption == AlarmDateOption.custom && !_days.contains(true)) {
if (_frequency == AlarmFrequency.custom && !_days.contains(true)) {
return false;
}
return true;
@@ -63,28 +64,16 @@ class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
void _save() {
if (!_canSave) return;
final vm = ref.read(alarmViewModelProvider.notifier);
final time = TimeOfDay(
hour: _duration.inHours % 24,
minute: _duration.inMinutes % 60,
);
final hour = _duration.inHours % 24;
final minute = _duration.inMinutes % 60;
final time =
'${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
final entity = AlarmEntity(
id: widget.alarm?.id ?? '',
deviceId: widget.alarm?.deviceId ?? '',
time: time,
message: _messageController.text.trim(),
order: widget.alarm?.order ?? 0,
dateOption: _dateOption,
days: _days,
);
if (_isEditing) {
vm.updateAlarm(entity);
} else {
vm.createAlarm(entity);
}
final week = _frequency == AlarmFrequency.custom
? _days.map((day) => day ? '1' : '0').join()
: null;
widget.onSave(AlarmEntity(time: time, frequency: _frequency, week: week));
Navigator.pop(context);
}
@@ -113,7 +102,6 @@ class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle bar
Center(
child: Container(
width: 40,
@@ -125,15 +113,15 @@ class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
),
),
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 14)),
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_isEditing
? context.translate(I18n.editAlarm)
: context.translate(I18n.addAlarm),
context.translate(
widget.alarm != null
? I18n.editAlarm
: I18n.addAlarm,
),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 20, big: 21),
fontWeight: FontWeight.w600,
@@ -154,8 +142,6 @@ class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
],
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
// Time picker
Container(
height: SizeUtils.getByScreen(small: 160, big: 180),
decoration: BoxDecoration(
@@ -165,49 +151,12 @@ class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
child: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
initialTimerDuration: _duration,
onTimerDurationChanged: (d) {
setState(() => _duration = d);
onTimerDurationChanged: (duration) {
setState(() => _duration = duration);
},
),
),
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 22)),
// Message
Text(
context.translate(I18n.alarmMessage),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 8),
TextField(
controller: _messageController,
onChanged: (_) => setState(() {}),
decoration: InputDecoration(
hintText: context.translate(I18n.alarmMessageHint),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Colors.grey.shade300),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: primaryColor, width: 2),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 14,
vertical: 14,
),
),
maxLines: 1,
),
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 22)),
// Repetition
Text(
context.translate(I18n.selectDay),
style: TextStyle(
@@ -218,28 +167,26 @@ class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
SizedBox(height: 8),
_RadioOption(
label: context.translate(I18n.once),
isSelected: _dateOption == AlarmDateOption.once,
isSelected: _frequency == AlarmFrequency.once,
primaryColor: primaryColor,
onTap: () =>
setState(() => _dateOption = AlarmDateOption.once),
setState(() => _frequency = AlarmFrequency.once),
),
_RadioOption(
label: context.translate(I18n.daily),
isSelected: _dateOption == AlarmDateOption.daily,
isSelected: _frequency == AlarmFrequency.every,
primaryColor: primaryColor,
onTap: () =>
setState(() => _dateOption = AlarmDateOption.daily),
setState(() => _frequency = AlarmFrequency.every),
),
_RadioOption(
label: context.translate(I18n.selectDay),
isSelected: _dateOption == AlarmDateOption.custom,
isSelected: _frequency == AlarmFrequency.custom,
primaryColor: primaryColor,
onTap: () =>
setState(() => _dateOption = AlarmDateOption.custom),
setState(() => _frequency = AlarmFrequency.custom),
),
// Day chips
if (_dateOption == AlarmDateOption.custom) ...[
if (_frequency == AlarmFrequency.custom) ...[
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 14)),
WeekDayChips.multi(
selectedDays: _days,
@@ -252,7 +199,6 @@ class _AlarmFormSheetState extends ConsumerState<_AlarmFormSheet> {
theme: theme,
),
],
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
],
),

View File

@@ -535,6 +535,10 @@
"editAlarm": "Alarm bearbeiten",
"noAlarms": "Keine Alarme konfiguriert",
"alarmCreated": "Alarm erfolgreich erstellt",
"alarmError": "Fehler bei der Verwaltung der Alarme",
"alarmMaxReached": "Maximal {max} Alarme erlaubt",
"alarmSave": "Änderungen speichern",
"alarmSaved": "Alarme gespeichert",
"alarmUpdated": "Alarm erfolgreich aktualisiert",
"alarmDeleted": "Alarm erfolgreich gelöscht",
"noActivityData": "Keine Aktivitätsmessungen vorhanden",

View File

@@ -648,6 +648,10 @@
"editAlarm": "Edit alarm",
"noAlarms": "No alarms configured",
"alarmCreated": "Alarm created successfully",
"alarmError": "Error managing alarms",
"alarmMaxReached": "Maximum {max} alarms allowed",
"alarmSave": "Save changes",
"alarmSaved": "Alarms saved successfully",
"alarmUpdated": "Alarm updated successfully",
"alarmDeleted": "Alarm deleted successfully",
"remoteTurnOff": "Apagado Remoto",

View File

@@ -649,6 +649,10 @@
"editAlarm": "Editar alarma",
"noAlarms": "No hay alarmas configuradas",
"alarmCreated": "Alarma creada correctamente",
"alarmError": "Error al gestionar alarmas",
"alarmMaxReached": "Máximo {max} alarmas permitidas",
"alarmSave": "Guardar cambios",
"alarmSaved": "Alarmas guardadas correctamente",
"alarmUpdated": "Alarma actualizada correctamente",
"alarmDeleted": "Alarma eliminada correctamente",
"remoteTurnOff": "Apagado Remoto",

View File

@@ -535,6 +535,10 @@
"editAlarm": "Modifier l'alarme",
"noAlarms": "Aucune alarme configurée",
"alarmCreated": "Alarme créée avec succès",
"alarmError": "Erreur lors de la gestion des alarmes",
"alarmMaxReached": "Maximum {max} alarmes autorisées",
"alarmSave": "Enregistrer",
"alarmSaved": "Alarmes enregistrées",
"alarmUpdated": "Alarme mise à jour avec succès",
"alarmDeleted": "Alarme supprimée avec succès",
"noActivityData": "Aucune mesure d'activité à afficher",

View File

@@ -535,6 +535,10 @@
"editAlarm": "Modifica allarme",
"noAlarms": "Nessun allarme configurato",
"alarmCreated": "Allarme creato con successo",
"alarmError": "Errore nella gestione degli allarmi",
"alarmMaxReached": "Massimo {max} allarmi consentiti",
"alarmSave": "Salva modifiche",
"alarmSaved": "Allarmi salvati",
"alarmUpdated": "Allarme aggiornato con successo",
"alarmDeleted": "Allarme eliminato con successo",
"noActivityData": "Nessuna misurazione di attività da mostrare",

View File

@@ -535,6 +535,10 @@
"editAlarm": "Editar alarme",
"noAlarms": "Nenhum alarme configurado",
"alarmCreated": "Alarme criado com sucesso",
"alarmError": "Erro ao gerir alarmes",
"alarmMaxReached": "Máximo {max} alarmes permitidos",
"alarmSave": "Guardar alterações",
"alarmSaved": "Alarmes guardados",
"alarmUpdated": "Alarme atualizado com sucesso",
"alarmDeleted": "Alarme eliminado com sucesso",
"noActivityData": "Sem medições de atividade para exibir",

View File

@@ -38,8 +38,12 @@ class I18n {
static const String alarm = 'alarm';
static const String alarmCreated = 'alarmCreated';
static const String alarmDeleted = 'alarmDeleted';
static const String alarmError = 'alarmError';
static const String alarmMaxReached = 'alarmMaxReached';
static const String alarmMessage = 'alarmMessage';
static const String alarmMessageHint = 'alarmMessageHint';
static const String alarmSave = 'alarmSave';
static const String alarmSaved = 'alarmSaved';
static const String alarmSettings = 'alarmSettings';
static const String alarmsMessage = 'alarmsMessage';
static const String alarmUpdated = 'alarmUpdated';

View File

@@ -3,14 +3,8 @@ import 'package:sf_tracking/src/tracking.dart';
const _prefix = 'legacy_settings';
mixin SettingsTracking on Tracking {
Future<void> legacySettingsAlarmAdded({required String time}) =>
trackEvent('${_prefix}_alarm_added', {'time': time});
Future<void> legacySettingsAlarmUpdated({required String time}) =>
trackEvent('${_prefix}_alarm_updated', {'time': time});
Future<void> legacySettingsAlarmRemoved() =>
trackEvent('${_prefix}_alarm_removed');
Future<void> legacySettingsAlarmsSaved({required int alarmsCount}) =>
trackEvent('${_prefix}_alarms_saved', {'alarms_count': alarmsCount});
Future<void> legacySettingsSosContactAdded({required int totalCount}) =>
trackEvent('${_prefix}_sos_contact_added', {'total_count': totalCount});