feat(do-not-disturb): add DND schedule feature with capabilities-driven UI

This commit is contained in:
2026-04-16 13:54:13 +02:00
parent 51a3979c03
commit 514daf9c7c
38 changed files with 5433 additions and 176 deletions

View File

@@ -190,6 +190,11 @@ void configureAppRouter() {
name: 'volume_control',
pageBuilder: const VolumeControlBuilder().buildPage,
),
GoRoute(
path: 'do_not_disturb',
name: 'do_not_disturb',
pageBuilder: const DoNotDisturbBuilder().buildPage,
),
GoRoute(
path: 'call_history',
name: 'call_history',

View File

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

View File

@@ -15,6 +15,11 @@ class DeviceManagementScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final capabilities = ref.watch(
selectedDeviceProvider.select((device) => device.value?.capabilities),
);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
final gap = SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15));
return LegacyPageLayout(
theme: theme,
@@ -27,83 +32,93 @@ class DeviceManagementScreen extends ConsumerWidget {
),
child: Column(
children: [
if (capabilities?.commands?.enabled ?? false) ...[
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.remoteConnection),
icon: SFIcons.connection,
text: context.translate(I18n.remoteConnection),
),
gap,
],
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () =>
navigationContract.pushTo(AppRoutes.remoteConnection),
icon: SFIcons.connection,
text: context.translate(I18n.remoteConnection),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.scheduledActivities),
icon: SFIcons.calendarCircle,
negativeIcon: true,
text: context.translate(I18n.activityScheduleTitle),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
if (capabilities?.contacts?.enabled ?? false) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.contacts),
icon: SFIcons.contactsCircle,
negativeIcon: true,
text: context.translate(I18n.contactsAgendaTitle),
),
],
if (capabilities?.doNotDisturbs != null) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.doNotDisturb),
icon: SFIcons.doNotDisturbCircle,
negativeIcon: true,
text: context.translate(I18n.doNotDisturb),
),
],
if (capabilities?.settings != null &&
capabilities!.settings!.soundModes.isNotEmpty) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.volumeControl),
icon: Icons.volume_up_outlined,
iconSize: SizeUtils.getByScreen(small: 42, big: 40),
text: context.translate(I18n.volumeControl),
),
],
if ((capabilities?.heartbeats?.enabled ?? false) ||
(capabilities?.bloodPressure?.enabled ?? false)) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.health),
icon: SFIcons.healthCircle,
negativeIcon: true,
text: context.translate(I18n.healthTitle),
),
],
if (capabilities?.podometer?.enabled ?? false) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.activityMeter),
icon: SFIcons.healthCircle,
negativeIcon: true,
text: context.translate(I18n.activityMeter),
),
],
gap,
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () => navigationContract.pushTo(AppRoutes.contacts),
icon: SFIcons.contactsCircle,
negativeIcon: true,
text: context.translate(I18n.contactsAgendaTitle),
),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () {},
// icon: SFIcons.doNotDisturbCircle,
// negativeIcon: true,
// text: context.translate(I18n.doNotDisturb),
// ),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () {},
// icon: SFIcons.videoCallCircle,
// negativeIcon: true,
// text: context.translate(I18n.videoCall),
// ),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.volumeControl),
icon: Icons.volume_up_outlined,
iconSize: SizeUtils.getByScreen(small: 42, big: 40),
text: context.translate(I18n.volumeControl),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () => navigationContract.pushTo(AppRoutes.health),
icon: SFIcons.healthCircle,
negativeIcon: true,
text: context.translate(I18n.healthTitle),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () =>
navigationContract.pushTo(AppRoutes.activityMeter),
icon: SFIcons.healthCircle,
negativeIcon: true,
text: context.translate(I18n.activityMeter),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () => navigationContract.pushTo(AppRoutes.rewards),
navigationContract.pushTo(AppRoutes.rewards),
icon: SFIcons.rewardsCircle,
negativeIcon: true,
text: context.translate(I18n.rewards),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
gap,
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
color: primaryColor,
onPressed: () => showDialog(
context: context,
builder: (_) => Dialog(child: CallWatchDialog()),
@@ -112,56 +127,48 @@ class DeviceManagementScreen extends ConsumerWidget {
iconSize: SizeUtils.getByScreen(small: 42, big: 40),
text: context.translate(I18n.callWatch),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
gap,
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.callHistory),
icon: Icons.history_outlined,
iconSize: SizeUtils.getByScreen(small: 42, big: 40),
text: context.translate(I18n.callHistory),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () => navigationContract.pushTo(AppRoutes.appsUse),
icon: SFIcons.screenTime,
text: context.translate(I18n.appsUse),
),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () {},
// icon: Icons.app_registration_sharp,
// text: context.translate(I18n.appsSurveillance),
// ),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () {},
// icon: SFIcons.friendsCircle,
// negativeIcon: true,
// text: context.translate(I18n.makeFriends),
// ),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () =>
navigationContract.pushTo(AppRoutes.locateDevice),
icon: SFIcons.locateSfCircle,
negativeIcon: true,
text: context.translate(I18n.locateDevicePlaySoundButton),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () =>
navigationContract.pushTo(AppRoutes.backgroundImage),
icon: Icons.add_photo_alternate_outlined,
iconSize: SizeUtils.getByScreen(small: 32, big: 30),
negativeIcon: false,
text: context.translate(I18n.customBackground),
),
if (capabilities?.appUsageSchedules != null) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.appsUse),
icon: SFIcons.screenTime,
text: context.translate(I18n.appsUse),
),
],
if (capabilities?.commands?.types.contains('FIND_DEVICE') ?? false) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.locateDevice),
icon: SFIcons.locateSfCircle,
negativeIcon: true,
text: context.translate(I18n.locateDevicePlaySoundButton),
),
],
if (capabilities?.deviceBackground?.enabled ?? false) ...[
gap,
AppMenuButton(
color: primaryColor,
onPressed: () =>
navigationContract.pushTo(AppRoutes.backgroundImage),
icon: Icons.add_photo_alternate_outlined,
iconSize: SizeUtils.getByScreen(small: 32, big: 30),
negativeIcon: false,
text: context.translate(I18n.customBackground),
),
],
],
),
),

View File

@@ -0,0 +1,9 @@
import '../../domain/do_not_disturb_period.dart';
abstract class DoNotDisturbRemoteDatasource {
Future<DoNotDisturbSchedule> getSchedule({required String identificator});
Future<DoNotDisturbSchedule> updatePeriods({
required String identificator,
required List<DoNotDisturbPeriod> periods,
});
}

View File

@@ -0,0 +1,71 @@
import 'package:sf_infrastructure/sf_infrastructure.dart';
import '../../domain/do_not_disturb_period.dart';
import '../models/do_not_disturb_response_model.dart';
import 'do_not_disturb_remote_datasource.dart';
class DoNotDisturbRemoteDatasourceImpl implements DoNotDisturbRemoteDatasource {
final SaveFamilyRepository _repository;
DoNotDisturbRemoteDatasourceImpl(this._repository);
@override
Future<DoNotDisturbSchedule> getSchedule({
required String identificator,
}) async {
try {
final response = await _repository.get<Map<String, dynamic>>(
'/devices/identificator/$identificator/do-not-disturbs',
);
final model = DoNotDisturbResponseModel.fromJson(response.data!);
return _toEntity(model.item);
} catch (_) {
throw Exception('Error fetching do not disturb schedule');
}
}
@override
Future<DoNotDisturbSchedule> updatePeriods({
required String identificator,
required List<DoNotDisturbPeriod> periods,
}) async {
try {
final body = {
'periods': periods
.map(
(period) => {
'start': period.start,
'end': period.end,
'week': period.week,
},
)
.toList(),
};
final response = await _repository.put<Map<String, dynamic>>(
'/devices/identificator/$identificator/do-not-disturbs',
body: body,
);
final model = DoNotDisturbUpdateResponseModel.fromJson(response.data!);
return _toEntity(model.item);
} catch (_) {
throw Exception('Error updating do not disturb schedule');
}
}
DoNotDisturbSchedule _toEntity(DoNotDisturbItemModel item) =>
DoNotDisturbSchedule(
id: item.id,
deviceIdentificator: item.deviceIdentificator,
periods: item.periods
.map(
(period) => DoNotDisturbPeriod(
start: period.start,
end: period.end,
week: period.week,
),
)
.toList(),
createdAt: item.createdAt,
updatedAt: item.updatedAt,
);
}

View File

@@ -0,0 +1,53 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'do_not_disturb_response_model.freezed.dart';
part 'do_not_disturb_response_model.g.dart';
@freezed
abstract class DoNotDisturbResponseModel with _$DoNotDisturbResponseModel {
const factory DoNotDisturbResponseModel({
required DoNotDisturbItemModel item,
}) = _DoNotDisturbResponseModel;
factory DoNotDisturbResponseModel.fromJson(Map<String, dynamic> json) =>
_$DoNotDisturbResponseModelFromJson(json);
}
@freezed
abstract class DoNotDisturbUpdateResponseModel
with _$DoNotDisturbUpdateResponseModel {
const factory DoNotDisturbUpdateResponseModel({
@Default(false) bool isUpdated,
required DoNotDisturbItemModel item,
}) = _DoNotDisturbUpdateResponseModel;
factory DoNotDisturbUpdateResponseModel.fromJson(
Map<String, dynamic> json,
) => _$DoNotDisturbUpdateResponseModelFromJson(json);
}
@freezed
abstract class DoNotDisturbItemModel with _$DoNotDisturbItemModel {
const factory DoNotDisturbItemModel({
required String id,
required String deviceIdentificator,
@Default([]) List<DoNotDisturbPeriodModel> periods,
int? createdAt,
int? updatedAt,
}) = _DoNotDisturbItemModel;
factory DoNotDisturbItemModel.fromJson(Map<String, dynamic> json) =>
_$DoNotDisturbItemModelFromJson(json);
}
@freezed
abstract class DoNotDisturbPeriodModel with _$DoNotDisturbPeriodModel {
const factory DoNotDisturbPeriodModel({
required String start,
required String end,
required String week,
}) = _DoNotDisturbPeriodModel;
factory DoNotDisturbPeriodModel.fromJson(Map<String, dynamic> json) =>
_$DoNotDisturbPeriodModelFromJson(json);
}

View File

@@ -0,0 +1,70 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'do_not_disturb_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_DoNotDisturbResponseModel _$DoNotDisturbResponseModelFromJson(
Map<String, dynamic> json,
) => _DoNotDisturbResponseModel(
item: DoNotDisturbItemModel.fromJson(json['item'] as Map<String, dynamic>),
);
Map<String, dynamic> _$DoNotDisturbResponseModelToJson(
_DoNotDisturbResponseModel instance,
) => <String, dynamic>{'item': instance.item};
_DoNotDisturbUpdateResponseModel _$DoNotDisturbUpdateResponseModelFromJson(
Map<String, dynamic> json,
) => _DoNotDisturbUpdateResponseModel(
isUpdated: json['isUpdated'] as bool? ?? false,
item: DoNotDisturbItemModel.fromJson(json['item'] as Map<String, dynamic>),
);
Map<String, dynamic> _$DoNotDisturbUpdateResponseModelToJson(
_DoNotDisturbUpdateResponseModel instance,
) => <String, dynamic>{'isUpdated': instance.isUpdated, 'item': instance.item};
_DoNotDisturbItemModel _$DoNotDisturbItemModelFromJson(
Map<String, dynamic> json,
) => _DoNotDisturbItemModel(
id: json['id'] as String,
deviceIdentificator: json['deviceIdentificator'] as String,
periods:
(json['periods'] as List<dynamic>?)
?.map(
(e) => DoNotDisturbPeriodModel.fromJson(e as Map<String, dynamic>),
)
.toList() ??
const [],
createdAt: (json['createdAt'] as num?)?.toInt(),
updatedAt: (json['updatedAt'] as num?)?.toInt(),
);
Map<String, dynamic> _$DoNotDisturbItemModelToJson(
_DoNotDisturbItemModel instance,
) => <String, dynamic>{
'id': instance.id,
'deviceIdentificator': instance.deviceIdentificator,
'periods': instance.periods,
'createdAt': instance.createdAt,
'updatedAt': instance.updatedAt,
};
_DoNotDisturbPeriodModel _$DoNotDisturbPeriodModelFromJson(
Map<String, dynamic> json,
) => _DoNotDisturbPeriodModel(
start: json['start'] as String,
end: json['end'] as String,
week: json['week'] as String,
);
Map<String, dynamic> _$DoNotDisturbPeriodModelToJson(
_DoNotDisturbPeriodModel instance,
) => <String, dynamic>{
'start': instance.start,
'end': instance.end,
'week': instance.week,
};

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'presentation/do_not_disturb_screen.dart';
class DoNotDisturbBuilder {
const DoNotDisturbBuilder();
Page<void> buildPage(BuildContext context, GoRouterState state) {
return MaterialPage<void>(
key: state.pageKey,
child: const DoNotDisturbScreen(),
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'do_not_disturb_period.freezed.dart';
@freezed
abstract class DoNotDisturbPeriod with _$DoNotDisturbPeriod {
const factory DoNotDisturbPeriod({
required String start,
required String end,
required String week,
}) = _DoNotDisturbPeriod;
}
@freezed
abstract class DoNotDisturbSchedule with _$DoNotDisturbSchedule {
const factory DoNotDisturbSchedule({
required String id,
required String deviceIdentificator,
@Default([]) List<DoNotDisturbPeriod> periods,
int? createdAt,
int? updatedAt,
}) = _DoNotDisturbSchedule;
}

View File

@@ -0,0 +1,552 @@
// 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 'do_not_disturb_period.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$DoNotDisturbPeriod {
String get start; String get end; String get week;
/// Create a copy of DoNotDisturbPeriod
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DoNotDisturbPeriodCopyWith<DoNotDisturbPeriod> get copyWith => _$DoNotDisturbPeriodCopyWithImpl<DoNotDisturbPeriod>(this as DoNotDisturbPeriod, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DoNotDisturbPeriod&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.week, week) || other.week == week));
}
@override
int get hashCode => Object.hash(runtimeType,start,end,week);
@override
String toString() {
return 'DoNotDisturbPeriod(start: $start, end: $end, week: $week)';
}
}
/// @nodoc
abstract mixin class $DoNotDisturbPeriodCopyWith<$Res> {
factory $DoNotDisturbPeriodCopyWith(DoNotDisturbPeriod value, $Res Function(DoNotDisturbPeriod) _then) = _$DoNotDisturbPeriodCopyWithImpl;
@useResult
$Res call({
String start, String end, String week
});
}
/// @nodoc
class _$DoNotDisturbPeriodCopyWithImpl<$Res>
implements $DoNotDisturbPeriodCopyWith<$Res> {
_$DoNotDisturbPeriodCopyWithImpl(this._self, this._then);
final DoNotDisturbPeriod _self;
final $Res Function(DoNotDisturbPeriod) _then;
/// Create a copy of DoNotDisturbPeriod
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? start = null,Object? end = null,Object? week = null,}) {
return _then(_self.copyWith(
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
as String,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
as String,week: null == week ? _self.week : week // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [DoNotDisturbPeriod].
extension DoNotDisturbPeriodPatterns on DoNotDisturbPeriod {
/// 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( _DoNotDisturbPeriod value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DoNotDisturbPeriod() 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( _DoNotDisturbPeriod value) $default,){
final _that = this;
switch (_that) {
case _DoNotDisturbPeriod():
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( _DoNotDisturbPeriod value)? $default,){
final _that = this;
switch (_that) {
case _DoNotDisturbPeriod() 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 start, String end, String week)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DoNotDisturbPeriod() when $default != null:
return $default(_that.start,_that.end,_that.week);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 start, String end, String week) $default,) {final _that = this;
switch (_that) {
case _DoNotDisturbPeriod():
return $default(_that.start,_that.end,_that.week);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 start, String end, String week)? $default,) {final _that = this;
switch (_that) {
case _DoNotDisturbPeriod() when $default != null:
return $default(_that.start,_that.end,_that.week);case _:
return null;
}
}
}
/// @nodoc
class _DoNotDisturbPeriod implements DoNotDisturbPeriod {
const _DoNotDisturbPeriod({required this.start, required this.end, required this.week});
@override final String start;
@override final String end;
@override final String week;
/// Create a copy of DoNotDisturbPeriod
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DoNotDisturbPeriodCopyWith<_DoNotDisturbPeriod> get copyWith => __$DoNotDisturbPeriodCopyWithImpl<_DoNotDisturbPeriod>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DoNotDisturbPeriod&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.week, week) || other.week == week));
}
@override
int get hashCode => Object.hash(runtimeType,start,end,week);
@override
String toString() {
return 'DoNotDisturbPeriod(start: $start, end: $end, week: $week)';
}
}
/// @nodoc
abstract mixin class _$DoNotDisturbPeriodCopyWith<$Res> implements $DoNotDisturbPeriodCopyWith<$Res> {
factory _$DoNotDisturbPeriodCopyWith(_DoNotDisturbPeriod value, $Res Function(_DoNotDisturbPeriod) _then) = __$DoNotDisturbPeriodCopyWithImpl;
@override @useResult
$Res call({
String start, String end, String week
});
}
/// @nodoc
class __$DoNotDisturbPeriodCopyWithImpl<$Res>
implements _$DoNotDisturbPeriodCopyWith<$Res> {
__$DoNotDisturbPeriodCopyWithImpl(this._self, this._then);
final _DoNotDisturbPeriod _self;
final $Res Function(_DoNotDisturbPeriod) _then;
/// Create a copy of DoNotDisturbPeriod
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? start = null,Object? end = null,Object? week = null,}) {
return _then(_DoNotDisturbPeriod(
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
as String,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
as String,week: null == week ? _self.week : week // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
mixin _$DoNotDisturbSchedule {
String get id; String get deviceIdentificator; List<DoNotDisturbPeriod> get periods; int? get createdAt; int? get updatedAt;
/// Create a copy of DoNotDisturbSchedule
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DoNotDisturbScheduleCopyWith<DoNotDisturbSchedule> get copyWith => _$DoNotDisturbScheduleCopyWithImpl<DoNotDisturbSchedule>(this as DoNotDisturbSchedule, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DoNotDisturbSchedule&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&const DeepCollectionEquality().equals(other.periods, periods)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,const DeepCollectionEquality().hash(periods),createdAt,updatedAt);
@override
String toString() {
return 'DoNotDisturbSchedule(id: $id, deviceIdentificator: $deviceIdentificator, periods: $periods, createdAt: $createdAt, updatedAt: $updatedAt)';
}
}
/// @nodoc
abstract mixin class $DoNotDisturbScheduleCopyWith<$Res> {
factory $DoNotDisturbScheduleCopyWith(DoNotDisturbSchedule value, $Res Function(DoNotDisturbSchedule) _then) = _$DoNotDisturbScheduleCopyWithImpl;
@useResult
$Res call({
String id, String deviceIdentificator, List<DoNotDisturbPeriod> periods, int? createdAt, int? updatedAt
});
}
/// @nodoc
class _$DoNotDisturbScheduleCopyWithImpl<$Res>
implements $DoNotDisturbScheduleCopyWith<$Res> {
_$DoNotDisturbScheduleCopyWithImpl(this._self, this._then);
final DoNotDisturbSchedule _self;
final $Res Function(DoNotDisturbSchedule) _then;
/// Create a copy of DoNotDisturbSchedule
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceIdentificator = null,Object? periods = null,Object? createdAt = freezed,Object? updatedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
as String,periods: null == periods ? _self.periods : periods // ignore: cast_nullable_to_non_nullable
as List<DoNotDisturbPeriod>,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?,
));
}
}
/// Adds pattern-matching-related methods to [DoNotDisturbSchedule].
extension DoNotDisturbSchedulePatterns on DoNotDisturbSchedule {
/// 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( _DoNotDisturbSchedule value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DoNotDisturbSchedule() 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( _DoNotDisturbSchedule value) $default,){
final _that = this;
switch (_that) {
case _DoNotDisturbSchedule():
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( _DoNotDisturbSchedule value)? $default,){
final _that = this;
switch (_that) {
case _DoNotDisturbSchedule() 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 deviceIdentificator, List<DoNotDisturbPeriod> periods, int? createdAt, int? updatedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DoNotDisturbSchedule() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.periods,_that.createdAt,_that.updatedAt);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 deviceIdentificator, List<DoNotDisturbPeriod> periods, int? createdAt, int? updatedAt) $default,) {final _that = this;
switch (_that) {
case _DoNotDisturbSchedule():
return $default(_that.id,_that.deviceIdentificator,_that.periods,_that.createdAt,_that.updatedAt);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 deviceIdentificator, List<DoNotDisturbPeriod> periods, int? createdAt, int? updatedAt)? $default,) {final _that = this;
switch (_that) {
case _DoNotDisturbSchedule() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.periods,_that.createdAt,_that.updatedAt);case _:
return null;
}
}
}
/// @nodoc
class _DoNotDisturbSchedule implements DoNotDisturbSchedule {
const _DoNotDisturbSchedule({required this.id, required this.deviceIdentificator, final List<DoNotDisturbPeriod> periods = const [], this.createdAt, this.updatedAt}): _periods = periods;
@override final String id;
@override final String deviceIdentificator;
final List<DoNotDisturbPeriod> _periods;
@override@JsonKey() List<DoNotDisturbPeriod> get periods {
if (_periods is EqualUnmodifiableListView) return _periods;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_periods);
}
@override final int? createdAt;
@override final int? updatedAt;
/// Create a copy of DoNotDisturbSchedule
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DoNotDisturbScheduleCopyWith<_DoNotDisturbSchedule> get copyWith => __$DoNotDisturbScheduleCopyWithImpl<_DoNotDisturbSchedule>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DoNotDisturbSchedule&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&const DeepCollectionEquality().equals(other._periods, _periods)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,const DeepCollectionEquality().hash(_periods),createdAt,updatedAt);
@override
String toString() {
return 'DoNotDisturbSchedule(id: $id, deviceIdentificator: $deviceIdentificator, periods: $periods, createdAt: $createdAt, updatedAt: $updatedAt)';
}
}
/// @nodoc
abstract mixin class _$DoNotDisturbScheduleCopyWith<$Res> implements $DoNotDisturbScheduleCopyWith<$Res> {
factory _$DoNotDisturbScheduleCopyWith(_DoNotDisturbSchedule value, $Res Function(_DoNotDisturbSchedule) _then) = __$DoNotDisturbScheduleCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceIdentificator, List<DoNotDisturbPeriod> periods, int? createdAt, int? updatedAt
});
}
/// @nodoc
class __$DoNotDisturbScheduleCopyWithImpl<$Res>
implements _$DoNotDisturbScheduleCopyWith<$Res> {
__$DoNotDisturbScheduleCopyWithImpl(this._self, this._then);
final _DoNotDisturbSchedule _self;
final $Res Function(_DoNotDisturbSchedule) _then;
/// Create a copy of DoNotDisturbSchedule
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceIdentificator = null,Object? periods = null,Object? createdAt = freezed,Object? updatedAt = freezed,}) {
return _then(_DoNotDisturbSchedule(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
as String,periods: null == periods ? _self._periods : periods // ignore: cast_nullable_to_non_nullable
as List<DoNotDisturbPeriod>,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?,
));
}
}
// dart format on

View File

@@ -0,0 +1,192 @@
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:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
import '../domain/do_not_disturb_period.dart';
import 'state/do_not_disturb_view_model.dart';
import 'state/do_not_disturb_view_state.dart';
import 'widgets/do_not_disturb_period_card.dart';
import 'widgets/edit_period_sheet.dart';
class DoNotDisturbScreen extends ConsumerWidget {
const DoNotDisturbScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final vm = ref.read(doNotDisturbViewModelProvider.notifier);
final (periods, maxPeriods, isLoading, isSaving) = ref.watch(
doNotDisturbViewModelProvider.select(
(s) => (s.periods, s.maxPeriods, s.isLoading, s.isSaving),
),
);
ref.listen(doNotDisturbViewModelProvider.select((s) => s.errorEvent), (
_,
next,
) {
if (next == null) return;
final message = switch (next) {
DoNotDisturbErrorEvent.load || DoNotDisturbErrorEvent.save => context.translate(
I18n.doNotDisturbError,
),
DoNotDisturbErrorEvent.maxPeriods => context
.translate(I18n.doNotDisturbMaxPeriods)
.replaceAll('{max}', maxPeriods.toString()),
};
showTopSnackbar(context, message: message, type: MessageType.error);
vm.clearError();
});
ref.listen(doNotDisturbViewModelProvider.select((s) => s.saveSuccess), (
_,
success,
) {
if (success) {
showTopSnackbar(
context,
message: context.translate(I18n.doNotDisturbSaved),
type: MessageType.success,
);
vm.clearSaveSuccess();
}
});
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
return LegacyPageLayout(
theme: theme,
title: context.translate(I18n.doNotDisturb),
body: isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
vertical: SizeUtils.getByScreen(small: 12, big: 10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
context.translate(I18n.doNotDisturbDescription),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
color: theme
.getColorFor(ThemeCode.textPrimary)
.withValues(alpha: 0.5),
height: 1.4,
),
),
SizedBox(
height: SizeUtils.getByScreen(small: 16, big: 14),
),
...periods.asMap().entries.map((entry) {
return Padding(
padding: EdgeInsets.only(
bottom: SizeUtils.getByScreen(small: 8, big: 6),
),
child: DoNotDisturbPeriodCard(
period: entry.value,
onEdit: () => _editPeriod(
context,
vm,
entry.key,
entry.value,
),
onDelete: () => vm.removePeriod(entry.key),
),
);
}),
if (periods.isEmpty)
Padding(
padding: EdgeInsets.symmetric(
vertical: SizeUtils.getByScreen(small: 24, big: 20),
),
child: Center(
child: Text(
context.translate(I18n.doNotDisturbEmpty),
style: TextStyle(
fontSize: SizeUtils.getByScreen(
small: 14,
big: 13,
),
color: theme
.getColorFor(ThemeCode.textPrimary)
.withValues(alpha: 0.4),
),
),
),
),
if (periods.length < maxPeriods)
Padding(
padding: EdgeInsets.only(
top: SizeUtils.getByScreen(small: 8, big: 6),
),
child: OutlinedButton.icon(
onPressed: () => _addPeriod(context, vm),
icon: const Icon(Icons.add),
label: Text(
context.translate(I18n.doNotDisturbAddPeriod),
),
style: OutlinedButton.styleFrom(
foregroundColor: primaryColor,
side: BorderSide(color: primaryColor),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
SizeUtils.getByScreen(small: 12, big: 10),
),
),
padding: EdgeInsets.symmetric(
vertical: SizeUtils.getByScreen(
small: 12,
big: 10,
),
),
),
),
),
],
),
),
footer: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: isSaving
? const Center(child: CircularProgressIndicator())
: PrimaryButton(
onPressed: vm.save,
text: context.translate(I18n.doNotDisturbSave),
color: primaryColor,
),
),
);
}
Future<void> _addPeriod(
BuildContext context,
DoNotDisturbViewModel vm,
) async {
final period = await showModalBottomSheet<DoNotDisturbPeriod>(
context: context,
isScrollControlled: true,
builder: (_) => const EditPeriodSheet(),
);
if (period != null) vm.addPeriod(period);
}
Future<void> _editPeriod(
BuildContext context,
DoNotDisturbViewModel vm,
int index,
DoNotDisturbPeriod existing,
) async {
final period = await showModalBottomSheet<DoNotDisturbPeriod>(
context: context,
isScrollControlled: true,
builder: (_) => EditPeriodSheet(initial: existing),
);
if (period != null) vm.updatePeriod(index, period);
}
}

View File

@@ -0,0 +1,132 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_tracking/sf_tracking.dart';
import '../../data/datasource/do_not_disturb_remote_datasource.dart';
import '../../domain/do_not_disturb_period.dart';
import '../../providers/do_not_disturb_providers.dart';
import 'do_not_disturb_view_state.dart';
final doNotDisturbViewModelProvider =
NotifierProvider.autoDispose<
DoNotDisturbViewModel,
DoNotDisturbViewState
>(DoNotDisturbViewModel.new);
class DoNotDisturbViewModel extends Notifier<DoNotDisturbViewState> {
late final DoNotDisturbRemoteDatasource _datasource;
late final SfTrackingRepository _tracking;
@override
DoNotDisturbViewState build() {
_datasource = ref.read(doNotDisturbRemoteDatasourceProvider);
_tracking = ref.read(sfTrackingProvider);
Future.microtask(_load);
return const DoNotDisturbViewState();
}
String? get _identificator =>
ref.read(selectedDeviceProvider).value?.identificator;
Future<void> _load() async {
final identificator = _identificator;
if (identificator == null) {
state = state.copyWith(isLoading: false);
return;
}
try {
final schedule = await _datasource.getSchedule(
identificator: identificator,
);
if (!ref.mounted) return;
final device = ref.read(selectedDeviceProvider).value;
final capabilities = device?.capabilities;
final maxPeriods = capabilities?.doNotDisturbs?.maxPeriods ?? 4;
state = state.copyWith(
isLoading: false,
periods: schedule.periods,
maxPeriods: maxPeriods,
);
} catch (_) {
if (!ref.mounted) return;
state = state.copyWith(
isLoading: false,
errorEvent: DoNotDisturbErrorEvent.load,
);
}
}
void addPeriod(DoNotDisturbPeriod period) {
if (state.periods.length >= state.maxPeriods) {
state = state.copyWith(errorEvent: DoNotDisturbErrorEvent.maxPeriods);
return;
}
state = state.copyWith(
periods: [...state.periods, period],
errorEvent: null,
);
}
void updatePeriod(int index, DoNotDisturbPeriod period) {
if (index < 0 || index >= state.periods.length) return;
final updated = [...state.periods];
updated[index] = period;
state = state.copyWith(periods: updated, errorEvent: null);
}
void removePeriod(int index) {
if (index < 0 || index >= state.periods.length) return;
final updated = [...state.periods]..removeAt(index);
state = state.copyWith(periods: updated, errorEvent: null);
}
void clearError() {
if (state.errorEvent != null) state = state.copyWith(errorEvent: null);
}
void clearSaveSuccess() {
if (state.saveSuccess) state = state.copyWith(saveSuccess: false);
}
Future<void> save() async {
final identificator = _identificator;
if (identificator == null) return;
state = state.copyWith(
isSaving: true,
saveSuccess: false,
errorEvent: null,
);
try {
final schedule = await _datasource.updatePeriods(
identificator: identificator,
periods: state.periods,
);
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
periods: schedule.periods,
saveSuccess: true,
);
unawaited(
_tracking.legacyDeviceDoNotDisturbScheduleSaved(
schedule.periods.length,
),
);
} catch (_) {
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
errorEvent: DoNotDisturbErrorEvent.save,
);
}
}
}

View File

@@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/do_not_disturb_period.dart';
part 'do_not_disturb_view_state.freezed.dart';
enum DoNotDisturbErrorEvent { load, save, maxPeriods }
@freezed
abstract class DoNotDisturbViewState with _$DoNotDisturbViewState {
const factory DoNotDisturbViewState({
@Default(true) bool isLoading,
@Default(false) bool isSaving,
@Default([]) List<DoNotDisturbPeriod> periods,
@Default(4) int maxPeriods,
DoNotDisturbErrorEvent? errorEvent,
@Default(false) bool saveSuccess,
}) = _DoNotDisturbViewState;
}

View File

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

View File

@@ -0,0 +1,80 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:utils/utils.dart';
import '../../domain/do_not_disturb_period.dart';
import 'week_day_row.dart';
class DoNotDisturbPeriodCard extends ConsumerWidget {
final DoNotDisturbPeriod period;
final VoidCallback onEdit;
final VoidCallback onDelete;
const DoNotDisturbPeriodCard({
super.key,
required this.period,
required this.onEdit,
required this.onDelete,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
return Container(
padding: EdgeInsets.all(SizeUtils.getByScreen(small: 14, big: 12)),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundSecondary),
borderRadius: BorderRadius.circular(
SizeUtils.getByScreen(small: 12, big: 10),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.schedule,
color: primaryColor,
size: SizeUtils.getByScreen(small: 20, big: 18),
),
SizedBox(width: SizeUtils.getByScreen(small: 8, big: 6)),
Expanded(
child: Text(
'${period.start}${period.end}',
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 16, big: 15),
fontWeight: FontWeight.w600,
color: theme.getColorFor(ThemeCode.textPrimary),
),
),
),
GestureDetector(
onTap: onEdit,
child: Icon(
Icons.edit_outlined,
size: SizeUtils.getByScreen(small: 20, big: 18),
color: primaryColor,
),
),
SizedBox(width: SizeUtils.getByScreen(small: 12, big: 10)),
GestureDetector(
onTap: onDelete,
child: Icon(
Icons.delete_outline,
size: SizeUtils.getByScreen(small: 20, big: 18),
color: Colors.red.shade400,
),
),
],
),
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),
WeekDayRow(week: period.week, activeColor: primaryColor),
],
),
);
}
}

View File

@@ -0,0 +1,215 @@
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';
import 'package:utils/utils.dart';
import '../../domain/do_not_disturb_period.dart';
import 'week_day_row.dart';
class EditPeriodSheet extends ConsumerStatefulWidget {
final DoNotDisturbPeriod? initial;
const EditPeriodSheet({super.key, this.initial});
@override
ConsumerState<EditPeriodSheet> createState() => _EditPeriodSheetState();
}
class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
late TimeOfDay _start;
late TimeOfDay _end;
late List<bool> _weekDays;
@override
void initState() {
super.initState();
if (widget.initial != null) {
_start = _parseTime(widget.initial!.start);
_end = _parseTime(widget.initial!.end);
_weekDays = widget.initial!.week
.padRight(7, '0')
.split('')
.map((c) => c == '1')
.toList();
} else {
_start = const TimeOfDay(hour: 22, minute: 0);
_end = const TimeOfDay(hour: 7, minute: 0);
_weekDays = [true, true, true, true, true, true, true];
}
}
TimeOfDay _parseTime(String time) {
final parts = time.split(':');
return TimeOfDay(
hour: int.tryParse(parts[0]) ?? 0,
minute: int.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0,
);
}
String _formatForApi(TimeOfDay t) =>
'${t.hour}:${t.minute.toString().padLeft(2, '0')}';
String _formatForDisplay(TimeOfDay t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
String _weekToString() => _weekDays.map((d) => d ? '1' : '0').join();
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
final isEditing = widget.initial != null;
final hasSelectedDays = _weekDays.any((d) => d);
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.viewInsetsOf(context).bottom,
left: SizeUtils.getByScreen(small: 20, big: 18),
right: SizeUtils.getByScreen(small: 20, big: 18),
top: SizeUtils.getByScreen(small: 20, big: 18),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
context.translate(
isEditing
? I18n.doNotDisturbEditPeriod
: I18n.doNotDisturbAddPeriod,
),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 18, big: 16),
fontWeight: FontWeight.w700,
color: theme.getColorFor(ThemeCode.textPrimary),
),
),
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 18)),
Row(
children: [
Expanded(
child: _TimePickerTile(
label: context.translate(I18n.doNotDisturbStart),
value: _formatForDisplay(_start),
color: primaryColor,
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: _start,
);
if (picked != null) setState(() => _start = picked);
},
),
),
SizedBox(width: SizeUtils.getByScreen(small: 12, big: 10)),
Expanded(
child: _TimePickerTile(
label: context.translate(I18n.doNotDisturbEnd),
value: _formatForDisplay(_end),
color: primaryColor,
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: _end,
);
if (picked != null) setState(() => _end = picked);
},
),
),
],
),
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 18)),
Text(
context.translate(I18n.doNotDisturbDays),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 14, big: 13),
fontWeight: FontWeight.w600,
color: theme.getColorFor(ThemeCode.textPrimary),
),
),
SizedBox(height: SizeUtils.getByScreen(small: 10, big: 8)),
WeekDayRow(
week: _weekToString(),
activeColor: primaryColor,
readOnly: false,
onToggle: (index) =>
setState(() => _weekDays[index] = !_weekDays[index]),
),
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 20)),
PrimaryButton(
onPressed: hasSelectedDays
? () {
Navigator.pop(
context,
DoNotDisturbPeriod(
start: _formatForApi(_start),
end: _formatForApi(_end),
week: _weekToString(),
),
);
}
: null,
text: context.translate(I18n.doNotDisturbConfirm),
color: primaryColor,
),
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
],
),
);
}
}
class _TimePickerTile extends StatelessWidget {
final String label;
final String value;
final Color color;
final VoidCallback onTap;
const _TimePickerTile({
required this.label,
required this.value,
required this.color,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 14, big: 12),
vertical: SizeUtils.getByScreen(small: 12, big: 10),
),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(
SizeUtils.getByScreen(small: 12, big: 10),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 11, big: 10),
color: Colors.grey.shade500,
),
),
SizedBox(height: SizeUtils.getByScreen(small: 4, big: 3)),
Text(
value,
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 22, big: 20),
fontWeight: FontWeight.w700,
color: color,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
class WeekDayRow extends StatelessWidget {
final String week;
final Color activeColor;
final bool readOnly;
final ValueChanged<int>? onToggle;
const WeekDayRow({
super.key,
required this.week,
required this.activeColor,
this.readOnly = true,
this.onToggle,
});
@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 size = readOnly
? SizeUtils.getByScreen<double>(small: 32, big: 28)
: SizeUtils.getByScreen<double>(small: 40, big: 36);
return Row(
mainAxisAlignment: readOnly
? MainAxisAlignment.start
: MainAxisAlignment.spaceBetween,
children: List.generate(7, (index) {
final active = chars[index] == '1';
final child = Container(
width: size,
height: readOnly
? SizeUtils.getByScreen<double>(small: 28, big: 24)
: size,
margin: readOnly
? EdgeInsets.only(
right: index < 6
? SizeUtils.getByScreen<double>(small: 4, big: 3)
: 0,
)
: null,
decoration: BoxDecoration(
color: active ? activeColor : Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: active ? null : Border.all(color: Colors.grey.shade300),
),
child: Center(
child: Text(
labels[index],
style: TextStyle(
fontSize: readOnly
? SizeUtils.getByScreen(small: 11, big: 10)
: SizeUtils.getByScreen(small: 13, big: 12),
fontWeight: FontWeight.w600,
color: active ? Colors.white : Colors.grey.shade500,
),
),
),
);
if (readOnly || onToggle == null) return child;
return GestureDetector(onTap: () => onToggle!(index), child: child);
}),
);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import '../data/datasource/do_not_disturb_remote_datasource.dart';
import '../data/datasource/do_not_disturb_remote_datasource_impl.dart';
final doNotDisturbRemoteDatasourceProvider =
Provider<DoNotDisturbRemoteDatasource>(
(_) => DoNotDisturbRemoteDatasourceImpl(GetIt.I<SaveFamilyRepository>()),
);

View File

@@ -235,7 +235,7 @@ class _HeartRateFrequencySelector extends StatelessWidget {
(opt) => Padding(
padding: const EdgeInsets.only(left: 6),
child: ChoiceChip(
label: Text('${opt ~/ 60}m'),
label: Text(formatSecondsCompact(opt)),
selected: opt == currentFrequency,
selectedColor: primaryColor,
labelStyle: TextStyle(

View File

@@ -16,6 +16,8 @@ class RemoteConnectionScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final device = ref.watch(selectedDeviceProvider).value;
final cameraEnabled = device?.capabilities?.camera?.enabled ?? false;
return LegacyPageLayout(
theme: theme,
@@ -28,21 +30,23 @@ class RemoteConnectionScreen extends ConsumerWidget {
),
child: Column(
children: [
_SectionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => RemoteCameraScreen(
navigationContract: navigationContract,
if (cameraEnabled) ...[
_SectionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => RemoteCameraScreen(
navigationContract: navigationContract,
),
),
),
);
},
icon: Icons.photo_camera_outlined,
text: I18n.remoteCamera,
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
);
},
icon: Icons.photo_camera_outlined,
text: I18n.remoteCamera,
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
],
_SectionButton(
onPressed: () {
showDialog(

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:location/src/features/location/presentation/state/location_map_view_model.dart';
import 'package:utils/utils.dart';
import 'map_action_button.dart';
class MapActionsPanel extends StatelessWidget {
@@ -95,10 +96,7 @@ class FrequencySelector extends ConsumerWidget {
required this.onChanged,
});
String _formatSeconds(int seconds) {
if (seconds >= 60) return '${seconds ~/ 60}m';
return '${seconds}s';
}
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -149,7 +147,7 @@ class FrequencySelector extends ConsumerWidget {
),
child: Center(
child: Text(
_formatSeconds(opt),
formatSecondsCompact(opt),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,

View File

@@ -70,6 +70,7 @@ class AppRoutes {
static const activityMeter = '$deviceManagement/activity_meter';
static const appsUse = '$deviceManagement/apps_use';
static const volumeControl = '$deviceManagement/volume_control';
static const doNotDisturb = '$deviceManagement/do_not_disturb';
static const callHistory = '$deviceManagement/call_history';
static const backgroundImage = '$deviceManagement/background_image';

View File

@@ -668,7 +668,8 @@
"errorActivityData": "Aktivitätsdaten konnten nicht geladen werden",
"errorPedometer": "Der Schrittzähler konnte nicht aktualisiert werden",
"errorContactsMin": "Das Gerät muss mindestens einen Kontakt haben",
"errorContactsMax": "Das Gerät kann nicht mehr als 10 Kontakte haben",
"errorContactsMax": "Maximale Kontaktanzahl für dieses Gerät erreicht",
"errorSosContactsMax": "Maximale SOS-Kontaktanzahl für dieses Gerät erreicht",
"customBackground": "Benutzerdefiniertes Hintergrundbild",
"backgroundImageDescription": "Legen Sie ein Foto als benutzerdefinierten Bildschirmschoner für das Gerät fest",
"backgroundImageTapToSelect": "Tippen Sie, um ein Foto auszuwählen",
@@ -773,6 +774,29 @@
"deviceSettings": "Einstellungen",
"disableFunctions": "Funktionen deaktivieren",
"doNotDisturb": "Nicht stören",
"doNotDisturbDescription": "Wenn aktiviert, werden alle Benachrichtigungen, Anrufe und Alarme auf der Uhr stummgeschaltet. Nur Anrufe von SOS-Kontakten werden zugelassen.",
"doNotDisturbStatusOn": "Aktiviert",
"doNotDisturbStatusOff": "Deaktiviert",
"doNotDisturbEnabled": "Nicht stören aktiviert",
"doNotDisturbDisabled": "Nicht stören deaktiviert",
"doNotDisturbError": "Fehler beim Ändern des Nicht-stören-Modus",
"doNotDisturbEmpty": "Keine Zeiträume konfiguriert",
"doNotDisturbAddPeriod": "Zeitraum hinzufügen",
"doNotDisturbEditPeriod": "Zeitraum bearbeiten",
"doNotDisturbMaxPeriods": "Maximal {max} Zeiträume erlaubt",
"doNotDisturbSave": "Änderungen speichern",
"doNotDisturbSaved": "Zeiträume gespeichert",
"doNotDisturbConfirm": "Bestätigen",
"doNotDisturbStart": "Beginn",
"doNotDisturbEnd": "Ende",
"doNotDisturbDays": "Wochentage",
"weekdayMonShort": "M",
"weekdayTueShort": "D",
"weekdayWedShort": "M",
"weekdayThuShort": "D",
"weekdayFriShort": "F",
"weekdaySatShort": "S",
"weekdaySunShort": "S",
"editDeviceTitle": "Gerät bearbeiten",
"enterEmail": "Geben Sie Ihre E-Mail ein",
"enterMessage": "Ihre Nachricht",

View File

@@ -531,6 +531,29 @@
"activityScheduleTitle": "Calendar",
"contactsAgendaTitle": "Contacts",
"doNotDisturb": "Do not disturb",
"doNotDisturbDescription": "When enabled, the watch will silence all notifications, calls, and alerts. Only SOS contacts calls will be allowed.",
"doNotDisturbStatusOn": "Enabled",
"doNotDisturbStatusOff": "Disabled",
"doNotDisturbEnabled": "Do not disturb enabled",
"doNotDisturbDisabled": "Do not disturb disabled",
"doNotDisturbError": "Error changing do not disturb mode",
"doNotDisturbEmpty": "No periods configured",
"doNotDisturbAddPeriod": "Add period",
"doNotDisturbEditPeriod": "Edit period",
"doNotDisturbMaxPeriods": "Maximum {max} periods allowed",
"doNotDisturbSave": "Save changes",
"doNotDisturbSaved": "Periods saved successfully",
"doNotDisturbConfirm": "Confirm",
"doNotDisturbStart": "Start",
"doNotDisturbEnd": "End",
"doNotDisturbDays": "Days of the week",
"weekdayMonShort": "M",
"weekdayTueShort": "T",
"weekdayWedShort": "W",
"weekdayThuShort": "T",
"weekdayFriShort": "F",
"weekdaySatShort": "S",
"weekdaySunShort": "S",
"videoCall": "Video call",
"healthTitle": "Health",
"healthEmpty": "No health data",
@@ -797,7 +820,8 @@
"errorActivityData": "Could not load activity data",
"errorPedometer": "Could not update pedometer",
"errorContactsMin": "The device must have at least one contact",
"errorContactsMax": "The device cannot have more than 10 contacts",
"errorContactsMax": "Maximum contacts reached for this device",
"errorSosContactsMax": "Maximum SOS contacts reached for this device",
"customBackground": "Custom background image",
"backgroundImageDescription": "Set a photo as a custom screensaver for the device",
"backgroundImageTapToSelect": "Tap to select a photo",

View File

@@ -532,6 +532,29 @@
"activityScheduleTitle": "Horario de actividades",
"contactsAgendaTitle": "Agenda",
"doNotDisturb": "No molestar",
"doNotDisturbDescription": "Cuando está activado, el reloj silenciará todas las notificaciones, llamadas y alertas. Solo se permitirán llamadas de contactos SOS.",
"doNotDisturbStatusOn": "Activado",
"doNotDisturbStatusOff": "Desactivado",
"doNotDisturbEnabled": "No molestar activado",
"doNotDisturbDisabled": "No molestar desactivado",
"doNotDisturbError": "Error al cambiar modo no molestar",
"doNotDisturbEmpty": "Sin periodos configurados",
"doNotDisturbAddPeriod": "Añadir periodo",
"doNotDisturbEditPeriod": "Editar periodo",
"doNotDisturbMaxPeriods": "Máximo {max} periodos permitidos",
"doNotDisturbSave": "Guardar cambios",
"doNotDisturbSaved": "Periodos guardados correctamente",
"doNotDisturbConfirm": "Confirmar",
"doNotDisturbStart": "Inicio",
"doNotDisturbEnd": "Fin",
"doNotDisturbDays": "Días de la semana",
"weekdayMonShort": "L",
"weekdayTueShort": "M",
"weekdayWedShort": "X",
"weekdayThuShort": "J",
"weekdayFriShort": "V",
"weekdaySatShort": "S",
"weekdaySunShort": "D",
"videoCall": "Video llamada",
"healthTitle": "Salud",
"healthEmpty": "Sin datos de salud",
@@ -798,7 +821,8 @@
"errorActivityData": "No se pudieron cargar los datos de actividad",
"errorPedometer": "No se pudo actualizar el podómetro",
"errorContactsMin": "El dispositivo debe tener al menos un contacto",
"errorContactsMax": "El dispositivo no puede tener más de 10 contactos",
"errorContactsMax": "Se ha alcanzado el máximo de contactos para este dispositivo",
"errorSosContactsMax": "Se ha alcanzado el máximo de contactos SOS para este dispositivo",
"customBackground": "Fondo de pantalla personalizado",
"backgroundImageDescription": "Configura una foto como protector de pantalla exclusivo para el dispositivo",
"backgroundImageTapToSelect": "Pulsa para seleccionar una foto",

View File

@@ -668,7 +668,8 @@
"errorActivityData": "Impossible de charger les données d'activité",
"errorPedometer": "Impossible de mettre à jour le podomètre",
"errorContactsMin": "L'appareil doit avoir au moins un contact",
"errorContactsMax": "L'appareil ne peut pas avoir plus de 10 contacts",
"errorContactsMax": "Nombre maximum de contacts atteint pour cet appareil",
"errorSosContactsMax": "Nombre maximum de contacts SOS atteint pour cet appareil",
"customBackground": "Image de fond personnalisée",
"backgroundImageDescription": "Définissez une photo comme écran de veille personnalisé pour l'appareil",
"backgroundImageTapToSelect": "Appuyez pour sélectionner une photo",
@@ -773,6 +774,29 @@
"deviceSettings": "Paramètres",
"disableFunctions": "Désactiver les fonctions",
"doNotDisturb": "Ne pas déranger",
"doNotDisturbDescription": "Lorsqu'il est activé, la montre désactivera toutes les notifications, appels et alertes. Seuls les appels des contacts SOS seront autorisés.",
"doNotDisturbStatusOn": "Activé",
"doNotDisturbStatusOff": "Désactivé",
"doNotDisturbEnabled": "Ne pas déranger activé",
"doNotDisturbDisabled": "Ne pas déranger désactivé",
"doNotDisturbError": "Erreur lors du changement du mode ne pas déranger",
"doNotDisturbEmpty": "Aucune période configurée",
"doNotDisturbAddPeriod": "Ajouter une période",
"doNotDisturbEditPeriod": "Modifier la période",
"doNotDisturbMaxPeriods": "Maximum {max} périodes autorisées",
"doNotDisturbSave": "Enregistrer",
"doNotDisturbSaved": "Périodes enregistrées",
"doNotDisturbConfirm": "Confirmer",
"doNotDisturbStart": "Début",
"doNotDisturbEnd": "Fin",
"doNotDisturbDays": "Jours de la semaine",
"weekdayMonShort": "L",
"weekdayTueShort": "M",
"weekdayWedShort": "M",
"weekdayThuShort": "J",
"weekdayFriShort": "V",
"weekdaySatShort": "S",
"weekdaySunShort": "D",
"editDeviceTitle": "Modifier l'appareil",
"enterEmail": "Entrez votre e-mail",
"enterMessage": "Votre message",

View File

@@ -668,7 +668,8 @@
"errorActivityData": "Impossibile caricare i dati di attività",
"errorPedometer": "Impossibile aggiornare il contapassi",
"errorContactsMin": "Il dispositivo deve avere almeno un contatto",
"errorContactsMax": "Il dispositivo non può avere più di 10 contatti",
"errorContactsMax": "Numero massimo di contatti raggiunto per questo dispositivo",
"errorSosContactsMax": "Numero massimo di contatti SOS raggiunto per questo dispositivo",
"customBackground": "Immagine di sfondo personalizzata",
"backgroundImageDescription": "Imposta una foto come screensaver personalizzato per il dispositivo",
"backgroundImageTapToSelect": "Tocca per selezionare una foto",
@@ -773,6 +774,29 @@
"deviceSettings": "Impostazioni",
"disableFunctions": "Disattiva funzioni",
"doNotDisturb": "Non disturbare",
"doNotDisturbDescription": "Quando attivato, l'orologio disattiverà tutte le notifiche, chiamate e avvisi. Saranno consentite solo le chiamate dai contatti SOS.",
"doNotDisturbStatusOn": "Attivato",
"doNotDisturbStatusOff": "Disattivato",
"doNotDisturbEnabled": "Non disturbare attivato",
"doNotDisturbDisabled": "Non disturbare disattivato",
"doNotDisturbError": "Errore nel cambiare la modalità non disturbare",
"doNotDisturbEmpty": "Nessun periodo configurato",
"doNotDisturbAddPeriod": "Aggiungi periodo",
"doNotDisturbEditPeriod": "Modifica periodo",
"doNotDisturbMaxPeriods": "Massimo {max} periodi consentiti",
"doNotDisturbSave": "Salva modifiche",
"doNotDisturbSaved": "Periodi salvati",
"doNotDisturbConfirm": "Conferma",
"doNotDisturbStart": "Inizio",
"doNotDisturbEnd": "Fine",
"doNotDisturbDays": "Giorni della settimana",
"weekdayMonShort": "L",
"weekdayTueShort": "M",
"weekdayWedShort": "M",
"weekdayThuShort": "G",
"weekdayFriShort": "V",
"weekdaySatShort": "S",
"weekdaySunShort": "D",
"editDeviceTitle": "Modifica dispositivo",
"enterEmail": "Inserisci la tua email",
"enterMessage": "Il tuo messaggio",

View File

@@ -668,7 +668,8 @@
"errorActivityData": "Não foi possível carregar os dados de atividade",
"errorPedometer": "Não foi possível atualizar o pedómetro",
"errorContactsMin": "O dispositivo deve ter pelo menos um contacto",
"errorContactsMax": "O dispositivo não pode ter mais de 10 contactos",
"errorContactsMax": "Número máximo de contactos atingido para este dispositivo",
"errorSosContactsMax": "Número máximo de contactos SOS atingido para este dispositivo",
"customBackground": "Imagem de fundo personalizada",
"backgroundImageDescription": "Defina uma foto como protetor de ecrã personalizado para o dispositivo",
"backgroundImageTapToSelect": "Toque para selecionar uma foto",
@@ -773,6 +774,29 @@
"deviceSettings": "Configurações",
"disableFunctions": "Desativar funções",
"doNotDisturb": "Não perturbe",
"doNotDisturbDescription": "Quando ativado, o relógio silenciará todas as notificações, chamadas e alertas. Apenas chamadas de contactos SOS serão permitidas.",
"doNotDisturbStatusOn": "Ativado",
"doNotDisturbStatusOff": "Desativado",
"doNotDisturbEnabled": "Não perturbe ativado",
"doNotDisturbDisabled": "Não perturbe desativado",
"doNotDisturbError": "Erro ao alterar o modo não perturbe",
"doNotDisturbEmpty": "Sem períodos configurados",
"doNotDisturbAddPeriod": "Adicionar período",
"doNotDisturbEditPeriod": "Editar período",
"doNotDisturbMaxPeriods": "Máximo {max} períodos permitidos",
"doNotDisturbSave": "Guardar alterações",
"doNotDisturbSaved": "Períodos guardados",
"doNotDisturbConfirm": "Confirmar",
"doNotDisturbStart": "Início",
"doNotDisturbEnd": "Fim",
"doNotDisturbDays": "Dias da semana",
"weekdayMonShort": "S",
"weekdayTueShort": "T",
"weekdayWedShort": "Q",
"weekdayThuShort": "Q",
"weekdayFriShort": "S",
"weekdaySatShort": "S",
"weekdaySunShort": "D",
"editDeviceTitle": "Editar dispositivo",
"enterEmail": "Insira seu e-mail",
"enterMessage": "Sua mensagem",

View File

@@ -317,6 +317,29 @@ class I18n {
static const String documentTypeNie = 'documentTypeNie';
static const String documentTypePassport = 'documentTypePassport';
static const String doNotDisturb = 'doNotDisturb';
static const String doNotDisturbDescription = 'doNotDisturbDescription';
static const String doNotDisturbStatusOn = 'doNotDisturbStatusOn';
static const String doNotDisturbStatusOff = 'doNotDisturbStatusOff';
static const String doNotDisturbEnabled = 'doNotDisturbEnabled';
static const String doNotDisturbDisabled = 'doNotDisturbDisabled';
static const String doNotDisturbError = 'doNotDisturbError';
static const String doNotDisturbEmpty = 'doNotDisturbEmpty';
static const String doNotDisturbAddPeriod = 'doNotDisturbAddPeriod';
static const String doNotDisturbEditPeriod = 'doNotDisturbEditPeriod';
static const String doNotDisturbMaxPeriods = 'doNotDisturbMaxPeriods';
static const String doNotDisturbSave = 'doNotDisturbSave';
static const String doNotDisturbSaved = 'doNotDisturbSaved';
static const String doNotDisturbConfirm = 'doNotDisturbConfirm';
static const String doNotDisturbStart = 'doNotDisturbStart';
static const String doNotDisturbEnd = 'doNotDisturbEnd';
static const String doNotDisturbDays = 'doNotDisturbDays';
static const String weekdayMonShort = 'weekdayMonShort';
static const String weekdayTueShort = 'weekdayTueShort';
static const String weekdayWedShort = 'weekdayWedShort';
static const String weekdayThuShort = 'weekdayThuShort';
static const String weekdayFriShort = 'weekdayFriShort';
static const String weekdaySatShort = 'weekdaySatShort';
static const String weekdaySunShort = 'weekdaySunShort';
static const String dontHaveAccount = 'dontHaveAccount';
static const String download = 'download';
static const String editAlarm = 'editAlarm';
@@ -350,6 +373,7 @@ class I18n {
static const String errorBirthDateRequired = 'errorBirthDateRequired';
static const String errorCall = 'errorCall';
static const String errorContactsMax = 'errorContactsMax';
static const String errorSosContactsMax = 'errorSosContactsMax';
static const String errorContactsMin = 'errorContactsMin';
static const String errorDisableFunctions = 'errorDisableFunctions';
static const String errorDocumentNumberRequired =

View File

@@ -18,6 +18,13 @@ abstract class DeviceCapabilitiesModel with _$DeviceCapabilitiesModel {
DeviceCapabilityTakepillsModel? takepills,
DeviceCapabilityOptionModel? location,
DeviceCapabilityCameraModel? camera,
DeviceCapabilityDoNotDisturbModel? doNotDisturbs,
DeviceCapabilityAlarmsModel? alarms,
DeviceCapabilityAppUsageModel? appUsageSchedules,
DeviceCapabilityEnabledModel? keyboard,
DeviceCapabilityEnabledModel? nightMode,
DeviceCapabilityEnabledModel? wifi,
DeviceCapabilityEnabledModel? deviceBackground,
}) = _DeviceCapabilitiesModel;
factory DeviceCapabilitiesModel.fromJson(Map<String, dynamic> json) =>
@@ -131,6 +138,39 @@ abstract class DeviceCapabilityCameraModel with _$DeviceCapabilityCameraModel {
_$DeviceCapabilityCameraModelFromJson(json);
}
@freezed
abstract class DeviceCapabilityDoNotDisturbModel with _$DeviceCapabilityDoNotDisturbModel {
const factory DeviceCapabilityDoNotDisturbModel({
@Default(4) int maxPeriods,
}) = _DeviceCapabilityDoNotDisturbModel;
factory DeviceCapabilityDoNotDisturbModel.fromJson(Map<String, dynamic> json) =>
_$DeviceCapabilityDoNotDisturbModelFromJson(json);
}
@freezed
abstract class DeviceCapabilityAlarmsModel
with _$DeviceCapabilityAlarmsModel {
const factory DeviceCapabilityAlarmsModel({
@Default(false) bool enabled,
@Default(3) int maxAlarms,
}) = _DeviceCapabilityAlarmsModel;
factory DeviceCapabilityAlarmsModel.fromJson(Map<String, dynamic> json) =>
_$DeviceCapabilityAlarmsModelFromJson(json);
}
@freezed
abstract class DeviceCapabilityAppUsageModel
with _$DeviceCapabilityAppUsageModel {
const factory DeviceCapabilityAppUsageModel({
@Default(3) int maxPeriods,
}) = _DeviceCapabilityAppUsageModel;
factory DeviceCapabilityAppUsageModel.fromJson(Map<String, dynamic> json) =>
_$DeviceCapabilityAppUsageModelFromJson(json);
}
extension DeviceCapabilitiesModelMapper on DeviceCapabilitiesModel {
DeviceCapabilitiesEntity toEntity() => DeviceCapabilitiesEntity(
heartbeats: heartbeats != null
@@ -198,5 +238,31 @@ extension DeviceCapabilitiesModelMapper on DeviceCapabilitiesModel {
enabled: camera!.enabled,
)
: null,
doNotDisturbs: doNotDisturbs != null
? DeviceCapabilityDoNotDisturbEntity(maxPeriods: doNotDisturbs!.maxPeriods)
: null,
alarms: alarms != null
? DeviceCapabilityAlarmsEntity(
enabled: alarms!.enabled,
maxAlarms: alarms!.maxAlarms,
)
: null,
appUsageSchedules: appUsageSchedules != null
? DeviceCapabilityAppUsageEntity(
maxPeriods: appUsageSchedules!.maxPeriods,
)
: null,
keyboard: keyboard != null
? DeviceCapabilityEnabledEntity(enabled: keyboard!.enabled)
: null,
nightMode: nightMode != null
? DeviceCapabilityEnabledEntity(enabled: nightMode!.enabled)
: null,
wifi: wifi != null
? DeviceCapabilityEnabledEntity(enabled: wifi!.enabled)
: null,
deviceBackground: deviceBackground != null
? DeviceCapabilityEnabledEntity(enabled: deviceBackground!.enabled)
: null,
);
}

View File

@@ -59,6 +59,41 @@ _DeviceCapabilitiesModel _$DeviceCapabilitiesModelFromJson(
: DeviceCapabilityCameraModel.fromJson(
json['camera'] as Map<String, dynamic>,
),
doNotDisturbs: json['doNotDisturbs'] == null
? null
: DeviceCapabilityDoNotDisturbModel.fromJson(
json['doNotDisturbs'] as Map<String, dynamic>,
),
alarms: json['alarms'] == null
? null
: DeviceCapabilityAlarmsModel.fromJson(
json['alarms'] as Map<String, dynamic>,
),
appUsageSchedules: json['appUsageSchedules'] == null
? null
: DeviceCapabilityAppUsageModel.fromJson(
json['appUsageSchedules'] as Map<String, dynamic>,
),
keyboard: json['keyboard'] == null
? null
: DeviceCapabilityEnabledModel.fromJson(
json['keyboard'] as Map<String, dynamic>,
),
nightMode: json['nightMode'] == null
? null
: DeviceCapabilityEnabledModel.fromJson(
json['nightMode'] as Map<String, dynamic>,
),
wifi: json['wifi'] == null
? null
: DeviceCapabilityEnabledModel.fromJson(
json['wifi'] as Map<String, dynamic>,
),
deviceBackground: json['deviceBackground'] == null
? null
: DeviceCapabilityEnabledModel.fromJson(
json['deviceBackground'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$DeviceCapabilitiesModelToJson(
@@ -74,6 +109,13 @@ Map<String, dynamic> _$DeviceCapabilitiesModelToJson(
'takepills': instance.takepills,
'location': instance.location,
'camera': instance.camera,
'doNotDisturbs': instance.doNotDisturbs,
'alarms': instance.alarms,
'appUsageSchedules': instance.appUsageSchedules,
'keyboard': instance.keyboard,
'nightMode': instance.nightMode,
'wifi': instance.wifi,
'deviceBackground': instance.deviceBackground,
};
_DeviceCapabilityEnabledModel _$DeviceCapabilityEnabledModelFromJson(
@@ -209,3 +251,37 @@ _DeviceCapabilityCameraModel _$DeviceCapabilityCameraModelFromJson(
Map<String, dynamic> _$DeviceCapabilityCameraModelToJson(
_DeviceCapabilityCameraModel instance,
) => <String, dynamic>{'format': instance.format, 'enabled': instance.enabled};
_DeviceCapabilityDoNotDisturbModel _$DeviceCapabilityDoNotDisturbModelFromJson(
Map<String, dynamic> json,
) => _DeviceCapabilityDoNotDisturbModel(
maxPeriods: (json['maxPeriods'] as num?)?.toInt() ?? 4,
);
Map<String, dynamic> _$DeviceCapabilityDoNotDisturbModelToJson(
_DeviceCapabilityDoNotDisturbModel instance,
) => <String, dynamic>{'maxPeriods': instance.maxPeriods};
_DeviceCapabilityAlarmsModel _$DeviceCapabilityAlarmsModelFromJson(
Map<String, dynamic> json,
) => _DeviceCapabilityAlarmsModel(
enabled: json['enabled'] as bool? ?? false,
maxAlarms: (json['maxAlarms'] as num?)?.toInt() ?? 3,
);
Map<String, dynamic> _$DeviceCapabilityAlarmsModelToJson(
_DeviceCapabilityAlarmsModel instance,
) => <String, dynamic>{
'enabled': instance.enabled,
'maxAlarms': instance.maxAlarms,
};
_DeviceCapabilityAppUsageModel _$DeviceCapabilityAppUsageModelFromJson(
Map<String, dynamic> json,
) => _DeviceCapabilityAppUsageModel(
maxPeriods: (json['maxPeriods'] as num?)?.toInt() ?? 3,
);
Map<String, dynamic> _$DeviceCapabilityAppUsageModelToJson(
_DeviceCapabilityAppUsageModel instance,
) => <String, dynamic>{'maxPeriods': instance.maxPeriods};

View File

@@ -15,6 +15,13 @@ abstract class DeviceCapabilitiesEntity with _$DeviceCapabilitiesEntity {
DeviceCapabilityTakepillsEntity? takepills,
DeviceCapabilityOptionEntity? location,
DeviceCapabilityCameraEntity? camera,
DeviceCapabilityDoNotDisturbEntity? doNotDisturbs,
DeviceCapabilityAlarmsEntity? alarms,
DeviceCapabilityAppUsageEntity? appUsageSchedules,
DeviceCapabilityEnabledEntity? keyboard,
DeviceCapabilityEnabledEntity? nightMode,
DeviceCapabilityEnabledEntity? wifi,
DeviceCapabilityEnabledEntity? deviceBackground,
}) = _DeviceCapabilitiesEntity;
}
@@ -63,6 +70,11 @@ abstract class DeviceCapabilityContactTypeEntity
}) = _DeviceCapabilityContactTypeEntity;
}
extension DeviceCapabilityContactsX on DeviceCapabilityContactsEntity {
int maxForType(String type, {int fallback = 10}) =>
types.where((t) => t.type == type).firstOrNull?.amount ?? fallback;
}
@freezed
abstract class DeviceCapabilitySettingsEntity
with _$DeviceCapabilitySettingsEntity {
@@ -99,3 +111,27 @@ abstract class DeviceCapabilityCameraEntity
@Default(false) bool enabled,
}) = _DeviceCapabilityCameraEntity;
}
@freezed
abstract class DeviceCapabilityDoNotDisturbEntity with _$DeviceCapabilityDoNotDisturbEntity {
const factory DeviceCapabilityDoNotDisturbEntity({
@Default(4) int maxPeriods,
}) = _DeviceCapabilityDoNotDisturbEntity;
}
@freezed
abstract class DeviceCapabilityAlarmsEntity
with _$DeviceCapabilityAlarmsEntity {
const factory DeviceCapabilityAlarmsEntity({
@Default(false) bool enabled,
@Default(3) int maxAlarms,
}) = _DeviceCapabilityAlarmsEntity;
}
@freezed
abstract class DeviceCapabilityAppUsageEntity
with _$DeviceCapabilityAppUsageEntity {
const factory DeviceCapabilityAppUsageEntity({
@Default(3) int maxPeriods,
}) = _DeviceCapabilityAppUsageEntity;
}

View File

@@ -34,6 +34,11 @@ mixin DeviceTracking on Tracking {
'level': level,
});
Future<void> legacyDeviceDoNotDisturbScheduleSaved(int periodsCount) =>
trackEvent('${_prefix}_do_not_disturb_schedule_saved', {
'periods_count': periodsCount,
});
Future<void> legacyDeviceBackgroundImageChanged() =>
trackEvent('${_prefix}_background_image_changed');

View File

@@ -0,0 +1,5 @@
String formatSecondsCompact(int seconds) {
if (seconds >= 3600) return '${seconds ~/ 3600}h';
if (seconds >= 60) return '${seconds ~/ 60}m';
return '${seconds}s';
}

View File

@@ -1,3 +1,4 @@
export 'src/date_utils.dart';
export 'src/duration_format.dart';
export 'src/size_utils.dart';
export 'src/test.dart';