feat(do-not-disturb): add enabled toggle, redesign UI and duplicate validation

This commit is contained in:
2026-04-26 05:12:34 +02:00
parent 82123a6d5f
commit 01cb4c9427
13 changed files with 260 additions and 166 deletions

View File

@@ -43,6 +43,7 @@ class DoNotDisturbRemoteDatasourceImpl implements DoNotDisturbRemoteDatasource {
'periods': periods
.map(
(period) => {
'enabled': period.enabled,
'start': period.start,
'end': period.end,
'week': period.week,
@@ -68,6 +69,7 @@ class DoNotDisturbRemoteDatasourceImpl implements DoNotDisturbRemoteDatasource {
periods: item.periods
.map(
(period) => DoNotDisturbPeriod(
enabled: period.enabled,
start: period.start,
end: period.end,
week: period.week,

View File

@@ -43,6 +43,7 @@ abstract class DoNotDisturbItemDto with _$DoNotDisturbItemDto {
@freezed
abstract class DoNotDisturbPeriodDto with _$DoNotDisturbPeriodDto {
const factory DoNotDisturbPeriodDto({
@Default(true) bool enabled,
required String start,
required String end,
required String week,

View File

@@ -861,7 +861,7 @@ as int?,
/// @nodoc
mixin _$DoNotDisturbPeriodDto {
String get start; String get end; String get week;
bool get enabled; String get start; String get end; String get week;
/// Create a copy of DoNotDisturbPeriodDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -874,16 +874,16 @@ $DoNotDisturbPeriodDtoCopyWith<DoNotDisturbPeriodDto> get copyWith => _$DoNotDis
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DoNotDisturbPeriodDto&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.week, week) || other.week == week));
return identical(this, other) || (other.runtimeType == runtimeType&&other is DoNotDisturbPeriodDto&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.week, week) || other.week == week));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,start,end,week);
int get hashCode => Object.hash(runtimeType,enabled,start,end,week);
@override
String toString() {
return 'DoNotDisturbPeriodDto(start: $start, end: $end, week: $week)';
return 'DoNotDisturbPeriodDto(enabled: $enabled, start: $start, end: $end, week: $week)';
}
@@ -894,7 +894,7 @@ abstract mixin class $DoNotDisturbPeriodDtoCopyWith<$Res> {
factory $DoNotDisturbPeriodDtoCopyWith(DoNotDisturbPeriodDto value, $Res Function(DoNotDisturbPeriodDto) _then) = _$DoNotDisturbPeriodDtoCopyWithImpl;
@useResult
$Res call({
String start, String end, String week
bool enabled, String start, String end, String week
});
@@ -911,9 +911,10 @@ class _$DoNotDisturbPeriodDtoCopyWithImpl<$Res>
/// Create a copy of DoNotDisturbPeriodDto
/// 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,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? enabled = null,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
enabled: null == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable
as bool,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,
@@ -1001,10 +1002,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String start, String end, String week)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enabled, String start, String end, String week)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DoNotDisturbPeriodDto() when $default != null:
return $default(_that.start,_that.end,_that.week);case _:
return $default(_that.enabled,_that.start,_that.end,_that.week);case _:
return orElse();
}
@@ -1022,10 +1023,10 @@ return $default(_that.start,_that.end,_that.week);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String start, String end, String week) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enabled, String start, String end, String week) $default,) {final _that = this;
switch (_that) {
case _DoNotDisturbPeriodDto():
return $default(_that.start,_that.end,_that.week);case _:
return $default(_that.enabled,_that.start,_that.end,_that.week);case _:
throw StateError('Unexpected subclass');
}
@@ -1042,10 +1043,10 @@ return $default(_that.start,_that.end,_that.week);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String start, String end, String week)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enabled, String start, String end, String week)? $default,) {final _that = this;
switch (_that) {
case _DoNotDisturbPeriodDto() when $default != null:
return $default(_that.start,_that.end,_that.week);case _:
return $default(_that.enabled,_that.start,_that.end,_that.week);case _:
return null;
}
@@ -1057,9 +1058,10 @@ return $default(_that.start,_that.end,_that.week);case _:
@JsonSerializable()
class _DoNotDisturbPeriodDto implements DoNotDisturbPeriodDto {
const _DoNotDisturbPeriodDto({required this.start, required this.end, required this.week});
const _DoNotDisturbPeriodDto({this.enabled = true, required this.start, required this.end, required this.week});
factory _DoNotDisturbPeriodDto.fromJson(Map<String, dynamic> json) => _$DoNotDisturbPeriodDtoFromJson(json);
@override@JsonKey() final bool enabled;
@override final String start;
@override final String end;
@override final String week;
@@ -1077,16 +1079,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DoNotDisturbPeriodDto&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.week, week) || other.week == week));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DoNotDisturbPeriodDto&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.week, week) || other.week == week));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,start,end,week);
int get hashCode => Object.hash(runtimeType,enabled,start,end,week);
@override
String toString() {
return 'DoNotDisturbPeriodDto(start: $start, end: $end, week: $week)';
return 'DoNotDisturbPeriodDto(enabled: $enabled, start: $start, end: $end, week: $week)';
}
@@ -1097,7 +1099,7 @@ abstract mixin class _$DoNotDisturbPeriodDtoCopyWith<$Res> implements $DoNotDist
factory _$DoNotDisturbPeriodDtoCopyWith(_DoNotDisturbPeriodDto value, $Res Function(_DoNotDisturbPeriodDto) _then) = __$DoNotDisturbPeriodDtoCopyWithImpl;
@override @useResult
$Res call({
String start, String end, String week
bool enabled, String start, String end, String week
});
@@ -1114,9 +1116,10 @@ class __$DoNotDisturbPeriodDtoCopyWithImpl<$Res>
/// Create a copy of DoNotDisturbPeriodDto
/// 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,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? enabled = null,Object? start = null,Object? end = null,Object? week = null,}) {
return _then(_DoNotDisturbPeriodDto(
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
enabled: null == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable
as bool,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,

View File

@@ -56,6 +56,7 @@ Map<String, dynamic> _$DoNotDisturbItemDtoToJson(
_DoNotDisturbPeriodDto _$DoNotDisturbPeriodDtoFromJson(
Map<String, dynamic> json,
) => _DoNotDisturbPeriodDto(
enabled: json['enabled'] as bool? ?? true,
start: json['start'] as String,
end: json['end'] as String,
week: json['week'] as String,
@@ -64,6 +65,7 @@ _DoNotDisturbPeriodDto _$DoNotDisturbPeriodDtoFromJson(
Map<String, dynamic> _$DoNotDisturbPeriodDtoToJson(
_DoNotDisturbPeriodDto instance,
) => <String, dynamic>{
'enabled': instance.enabled,
'start': instance.start,
'end': instance.end,
'week': instance.week,

View File

@@ -5,6 +5,7 @@ part 'do_not_disturb_period.freezed.dart';
@freezed
abstract class DoNotDisturbPeriod with _$DoNotDisturbPeriod {
const factory DoNotDisturbPeriod({
@Default(true) bool enabled,
required String start,
required String end,
required String week,

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$DoNotDisturbPeriod {
String get start; String get end; String get week;
bool get enabled; 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)
@@ -25,16 +25,16 @@ $DoNotDisturbPeriodCopyWith<DoNotDisturbPeriod> get copyWith => _$DoNotDisturbPe
@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));
return identical(this, other) || (other.runtimeType == runtimeType&&other is DoNotDisturbPeriod&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(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);
int get hashCode => Object.hash(runtimeType,enabled,start,end,week);
@override
String toString() {
return 'DoNotDisturbPeriod(start: $start, end: $end, week: $week)';
return 'DoNotDisturbPeriod(enabled: $enabled, start: $start, end: $end, week: $week)';
}
@@ -45,7 +45,7 @@ abstract mixin class $DoNotDisturbPeriodCopyWith<$Res> {
factory $DoNotDisturbPeriodCopyWith(DoNotDisturbPeriod value, $Res Function(DoNotDisturbPeriod) _then) = _$DoNotDisturbPeriodCopyWithImpl;
@useResult
$Res call({
String start, String end, String week
bool enabled, String start, String end, String week
});
@@ -62,9 +62,10 @@ class _$DoNotDisturbPeriodCopyWithImpl<$Res>
/// 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,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? enabled = null,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
enabled: null == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable
as bool,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,
@@ -152,10 +153,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String start, String end, String week)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enabled, 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 $default(_that.enabled,_that.start,_that.end,_that.week);case _:
return orElse();
}
@@ -173,10 +174,10 @@ return $default(_that.start,_that.end,_that.week);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String start, String end, String week) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enabled, String start, String end, String week) $default,) {final _that = this;
switch (_that) {
case _DoNotDisturbPeriod():
return $default(_that.start,_that.end,_that.week);case _:
return $default(_that.enabled,_that.start,_that.end,_that.week);case _:
throw StateError('Unexpected subclass');
}
@@ -193,10 +194,10 @@ return $default(_that.start,_that.end,_that.week);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String start, String end, String week)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enabled, 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 $default(_that.enabled,_that.start,_that.end,_that.week);case _:
return null;
}
@@ -208,9 +209,10 @@ return $default(_that.start,_that.end,_that.week);case _:
class _DoNotDisturbPeriod implements DoNotDisturbPeriod {
const _DoNotDisturbPeriod({required this.start, required this.end, required this.week});
const _DoNotDisturbPeriod({this.enabled = true, required this.start, required this.end, required this.week});
@override@JsonKey() final bool enabled;
@override final String start;
@override final String end;
@override final String week;
@@ -225,16 +227,16 @@ _$DoNotDisturbPeriodCopyWith<_DoNotDisturbPeriod> get copyWith => __$DoNotDistur
@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));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DoNotDisturbPeriod&&(identical(other.enabled, enabled) || other.enabled == enabled)&&(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);
int get hashCode => Object.hash(runtimeType,enabled,start,end,week);
@override
String toString() {
return 'DoNotDisturbPeriod(start: $start, end: $end, week: $week)';
return 'DoNotDisturbPeriod(enabled: $enabled, start: $start, end: $end, week: $week)';
}
@@ -245,7 +247,7 @@ abstract mixin class _$DoNotDisturbPeriodCopyWith<$Res> implements $DoNotDisturb
factory _$DoNotDisturbPeriodCopyWith(_DoNotDisturbPeriod value, $Res Function(_DoNotDisturbPeriod) _then) = __$DoNotDisturbPeriodCopyWithImpl;
@override @useResult
$Res call({
String start, String end, String week
bool enabled, String start, String end, String week
});
@@ -262,9 +264,10 @@ class __$DoNotDisturbPeriodCopyWithImpl<$Res>
/// 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,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? enabled = null,Object? start = null,Object? end = null,Object? week = null,}) {
return _then(_DoNotDisturbPeriod(
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
enabled: null == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable
as bool,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,

View File

@@ -28,7 +28,6 @@ class DoNotDisturbScreen extends ConsumerWidget {
await showErrorDialog(context, I18n.doNotDisturbError);
return;
}
ref.read(doNotDisturbEditorProvider.notifier).clear();
await showSuccessDialog(context, I18n.doNotDisturbSaved);
});
@@ -56,6 +55,8 @@ class DoNotDisturbScreen extends ConsumerWidget {
),
data: (serverPeriods) {
final periods = editorState ?? serverPeriods;
final remainingSlots = maxPeriods - periods.length;
return SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
@@ -75,74 +76,32 @@ class DoNotDisturbScreen extends ConsumerWidget {
height: 1.4,
),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 14)),
...periods.asMap().entries.map(
(entry) => Padding(
padding: EdgeInsets.only(
bottom: SizeUtils.getByScreen(small: 8, big: 6),
),
child: DoNotDisturbPeriodCard(
period: entry.value,
onEdit: () => _editPeriod(
context,
ref,
periods,
entry.key,
entry.value,
),
onDelete: () => _deletePeriod(
context,
ref,
periods,
entry.key,
),
),
),
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 18)),
for (int i = 0; i < periods.length; i++) ...[
DoNotDisturbPeriodCard(
period: periods[i],
onTap: () => _editPeriod(
context,
ref,
periods,
i,
periods[i],
),
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.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.4),
onToggle: (enabled) => ref
.read(doNotDisturbEditorProvider.notifier)
.toggle(
current: periods,
index: i,
enabled: enabled,
),
),
),
),
if (periods.length < maxPeriods)
Padding(
padding: EdgeInsets.only(
top: SizeUtils.getByScreen(small: 8, big: 6),
),
child: OutlinedButton.icon(
onPressed: () => _addPeriod(context, ref, periods),
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),
),
),
),
SizedBox(
height: SizeUtils.getByScreen(small: 8, big: 6),
),
],
if (remainingSlots > 0)
_AddPeriodTile(
onTap: () => _addPeriod(context, ref, periods),
),
],
),
@@ -174,6 +133,23 @@ class DoNotDisturbScreen extends ConsumerWidget {
);
}
bool _isDuplicate(
DoNotDisturbPeriod period,
List<DoNotDisturbPeriod> current, {
int? excludeIndex,
}) {
for (var i = 0; i < current.length; i++) {
if (i == excludeIndex) continue;
final existing = current[i];
if (existing.start == period.start &&
existing.end == period.end &&
existing.week == period.week) {
return true;
}
}
return false;
}
Future<void> _addPeriod(
BuildContext context,
WidgetRef ref,
@@ -184,11 +160,15 @@ class DoNotDisturbScreen extends ConsumerWidget {
isScrollControlled: true,
builder: (_) => const EditPeriodSheet(),
);
if (period != null) {
ref
.read(doNotDisturbEditorProvider.notifier)
.add(current: current, period: period);
if (period == null) return;
if (_isDuplicate(period, current)) {
if (!context.mounted) return;
await showErrorDialog(context, I18n.doNotDisturbDuplicate);
return;
}
ref
.read(doNotDisturbEditorProvider.notifier)
.add(current: current, period: period);
}
Future<void> _editPeriod(
@@ -201,13 +181,20 @@ class DoNotDisturbScreen extends ConsumerWidget {
final period = await showModalBottomSheet<DoNotDisturbPeriod>(
context: context,
isScrollControlled: true,
builder: (_) => EditPeriodSheet(initial: existing),
builder: (_) => EditPeriodSheet(
initial: existing,
onDelete: () => _deletePeriod(context, ref, current, index),
),
);
if (period != null) {
ref
.read(doNotDisturbEditorProvider.notifier)
.replace(current: current, index: index, period: period);
if (period == null) return;
if (_isDuplicate(period, current, excludeIndex: index)) {
if (!context.mounted) return;
await showErrorDialog(context, I18n.doNotDisturbDuplicate);
return;
}
ref
.read(doNotDisturbEditorProvider.notifier)
.replace(current: current, index: index, period: period);
}
Future<void> _deletePeriod(
@@ -216,7 +203,7 @@ class DoNotDisturbScreen extends ConsumerWidget {
List<DoNotDisturbPeriod> current,
int index,
) async {
final confirmed = await showDialog<bool>(
final confirmed = await showLegacyDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(context.translate(I18n.doNotDisturbDeletePeriod)),
@@ -243,3 +230,55 @@ class DoNotDisturbScreen extends ConsumerWidget {
.remove(current: current, index: index);
}
}
class _AddPeriodTile extends StatelessWidget {
final VoidCallback onTap;
const _AddPeriodTile({required this.onTap});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(14),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
vertical: SizeUtils.getByScreen(small: 16, big: 14),
),
child: Row(
children: [
Expanded(
child: Text(
context.translate(I18n.doNotDisturbAddPeriod),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
fontWeight: FontWeight.w500,
),
),
),
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: context.sfColors.legacyPrimary,
shape: BoxShape.circle,
),
child: const Icon(
Icons.add,
color: Colors.white,
size: 20,
),
),
],
),
),
),
);
}
}

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:device_management/src/core/providers/do_not_disturb_providers.dart';
import 'package:device_management/src/features/do_not_disturb/domain/do_not_disturb_period.dart';
import 'package:device_management/src/features/do_not_disturb/presentation/providers/do_not_disturb_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sf_tracking/sf_tracking.dart';
@@ -25,7 +24,6 @@ class DoNotDisturbController extends _$DoNotDisturbController {
identificator: deviceIdentificator,
periods: periods,
);
ref.invalidate(doNotDisturbPeriodsProvider(deviceIdentificator));
unawaited(
ref
.read(sfTrackingProvider)

View File

@@ -34,7 +34,7 @@ final class DoNotDisturbControllerProvider
}
String _$doNotDisturbControllerHash() =>
r'f84df0b051b53a5fadd8801412d45e870aee6637';
r'0943b2ecc8d1d443f0a520a9940009f9aa8fbacc';
abstract class _$DoNotDisturbController extends $AsyncNotifier<void> {
FutureOr<void> build();

View File

@@ -37,5 +37,17 @@ class DoNotDisturbEditor extends _$DoNotDisturbEditor {
state = [...base]..removeAt(index);
}
void toggle({
required List<DoNotDisturbPeriod> current,
required int index,
required bool enabled,
}) {
final base = state ?? current;
if (index < 0 || index >= base.length) return;
final updated = [...base];
updated[index] = updated[index].copyWith(enabled: enabled);
state = updated;
}
void clear() => state = null;
}

View File

@@ -42,7 +42,7 @@ final class DoNotDisturbEditorProvider
}
String _$doNotDisturbEditorHash() =>
r'a82e3bc2ca0eaf5d185d5e7624d2956acf883269';
r'406087481a5a0cb2fdcf9df745c40f104d77e2aa';
abstract class _$DoNotDisturbEditor
extends $Notifier<List<DoNotDisturbPeriod>?> {

View File

@@ -1,21 +1,21 @@
import 'package:legacy_theme/legacy_theme.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 DoNotDisturbPeriodCard extends ConsumerWidget {
final DoNotDisturbPeriod period;
final VoidCallback onEdit;
final VoidCallback onDelete;
final VoidCallback onTap;
final ValueChanged<bool> onToggle;
const DoNotDisturbPeriodCard({
super.key,
required this.period,
required this.onEdit,
required this.onDelete,
required this.onTap,
required this.onToggle,
});
@override
@@ -23,57 +23,73 @@ class DoNotDisturbPeriodCard extends ConsumerWidget {
final primaryColor = context.sfColors.legacyPrimary;
return Container(
padding: EdgeInsets.all(SizeUtils.getByScreen(small: 14, big: 12)),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(
SizeUtils.getByScreen(small: 12, big: 10),
),
borderRadius: BorderRadius.circular(14),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
vertical: SizeUtils.getByScreen(small: 14, big: 12),
),
child: 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.of(context).colorScheme.onSurface,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${period.start}-${period.end}',
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 16, big: 15),
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 2),
Text(
_weekDaysLabel(context, period.week),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
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: Theme.of(context).colorScheme.error,
),
Switch.adaptive(
value: period.enabled,
onChanged: onToggle,
activeTrackColor: primaryColor,
),
],
),
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),
WeekDayRow(week: period.week, activeColor: primaryColor),
],
),
),
);
}
String _weekDaysLabel(BuildContext context, String week) {
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 active = <String>[];
for (var i = 0; i < 7; i++) {
if (chars[i] == '1') active.add(labels[i]);
}
if (active.length == 7) {
return context.translate(I18n.doNotDisturbEveryDay);
}
return active.join(', ');
}
}

View File

@@ -10,8 +10,9 @@ import 'week_day_row.dart';
class EditPeriodSheet extends ConsumerStatefulWidget {
final DoNotDisturbPeriod? initial;
final VoidCallback? onDelete;
const EditPeriodSheet({super.key, this.initial});
const EditPeriodSheet({super.key, this.initial, this.onDelete});
@override
ConsumerState<EditPeriodSheet> createState() => _EditPeriodSheetState();
@@ -169,6 +170,7 @@ class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
Navigator.pop(
context,
DoNotDisturbPeriod(
enabled: widget.initial?.enabled ?? true,
start: _formatForApi(_start.value),
end: _formatForApi(_end.value),
week: _weekToString(days),
@@ -181,6 +183,21 @@ class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
);
},
),
if (isEditing && widget.onDelete != null) ...[
SizedBox(height: SizeUtils.getByScreen(small: 10, big: 8)),
TextButton(
onPressed: () {
Navigator.pop(context);
widget.onDelete!();
},
child: Text(
context.translate(I18n.doNotDisturbDeletePeriod),
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
),
),
],
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
],
),