refactor(device_management): migrate scheduled_activities to Riverpod
This commit is contained in:
@@ -0,0 +1,101 @@
|
|||||||
|
import 'package:device_management/src/features/scheduled_activities/domain/entities/scheduled_activity_entity.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'activity_form_provider.g.dart';
|
||||||
|
|
||||||
|
class ActivityFormState {
|
||||||
|
const ActivityFormState({
|
||||||
|
required this.weekDay,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
this.showStartPicker = false,
|
||||||
|
this.showEndPicker = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int weekDay;
|
||||||
|
final TimeOfDay startTime;
|
||||||
|
final TimeOfDay endTime;
|
||||||
|
final bool showStartPicker;
|
||||||
|
final bool showEndPicker;
|
||||||
|
|
||||||
|
bool get isTimeValid {
|
||||||
|
final startMinutes = startTime.hour * 60 + startTime.minute;
|
||||||
|
final endMinutes = endTime.hour * 60 + endTime.minute;
|
||||||
|
return startMinutes < endMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get period =>
|
||||||
|
'${startTime.hour.toString().padLeft(2, '0')}'
|
||||||
|
'${startTime.minute.toString().padLeft(2, '0')}'
|
||||||
|
'${endTime.hour.toString().padLeft(2, '0')}'
|
||||||
|
'${endTime.minute.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
ActivityFormState copyWith({
|
||||||
|
int? weekDay,
|
||||||
|
TimeOfDay? startTime,
|
||||||
|
TimeOfDay? endTime,
|
||||||
|
bool? showStartPicker,
|
||||||
|
bool? showEndPicker,
|
||||||
|
}) {
|
||||||
|
return ActivityFormState(
|
||||||
|
weekDay: weekDay ?? this.weekDay,
|
||||||
|
startTime: startTime ?? this.startTime,
|
||||||
|
endTime: endTime ?? this.endTime,
|
||||||
|
showStartPicker: showStartPicker ?? this.showStartPicker,
|
||||||
|
showEndPicker: showEndPicker ?? this.showEndPicker,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ActivityForm extends _$ActivityForm {
|
||||||
|
@override
|
||||||
|
ActivityFormState build(ScheduledActivityEntity? initial, int fallbackWeekDay) {
|
||||||
|
if (initial != null && initial.hasValidPeriod) {
|
||||||
|
return ActivityFormState(
|
||||||
|
weekDay: initial.weekDay,
|
||||||
|
startTime: TimeOfDay(
|
||||||
|
hour: int.parse(initial.period.substring(0, 2)),
|
||||||
|
minute: int.parse(initial.period.substring(2, 4)),
|
||||||
|
),
|
||||||
|
endTime: TimeOfDay(
|
||||||
|
hour: int.parse(initial.period.substring(4, 6)),
|
||||||
|
minute: int.parse(initial.period.substring(6, 8)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ActivityFormState(
|
||||||
|
weekDay: fallbackWeekDay,
|
||||||
|
startTime: const TimeOfDay(hour: 8, minute: 0),
|
||||||
|
endTime: const TimeOfDay(hour: 9, minute: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWeekDay(int value) {
|
||||||
|
if (value == state.weekDay) return;
|
||||||
|
state = state.copyWith(weekDay: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStartTime(TimeOfDay value) {
|
||||||
|
state = state.copyWith(startTime: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEndTime(TimeOfDay value) {
|
||||||
|
state = state.copyWith(endTime: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleStartPicker() {
|
||||||
|
state = state.copyWith(
|
||||||
|
showStartPicker: !state.showStartPicker,
|
||||||
|
showEndPicker: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleEndPicker() {
|
||||||
|
state = state.copyWith(
|
||||||
|
showEndPicker: !state.showEndPicker,
|
||||||
|
showStartPicker: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'activity_form_provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(ActivityForm)
|
||||||
|
const activityFormProvider = ActivityFormFamily._();
|
||||||
|
|
||||||
|
final class ActivityFormProvider
|
||||||
|
extends $NotifierProvider<ActivityForm, ActivityFormState> {
|
||||||
|
const ActivityFormProvider._({
|
||||||
|
required ActivityFormFamily super.from,
|
||||||
|
required (ScheduledActivityEntity?, int) super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'activityFormProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$activityFormHash();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'activityFormProvider'
|
||||||
|
''
|
||||||
|
'$argument';
|
||||||
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
ActivityForm create() => ActivityForm();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(ActivityFormState value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<ActivityFormState>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is ActivityFormProvider && other.argument == argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return argument.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$activityFormHash() => r'4b379b0412538ac5ac849c2e4fa4efa334dbdb3c';
|
||||||
|
|
||||||
|
final class ActivityFormFamily extends $Family
|
||||||
|
with
|
||||||
|
$ClassFamilyOverride<
|
||||||
|
ActivityForm,
|
||||||
|
ActivityFormState,
|
||||||
|
ActivityFormState,
|
||||||
|
ActivityFormState,
|
||||||
|
(ScheduledActivityEntity?, int)
|
||||||
|
> {
|
||||||
|
const ActivityFormFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'activityFormProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
ActivityFormProvider call(
|
||||||
|
ScheduledActivityEntity? initial,
|
||||||
|
int fallbackWeekDay,
|
||||||
|
) => ActivityFormProvider._(argument: (initial, fallbackWeekDay), from: this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => r'activityFormProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$ActivityForm extends $Notifier<ActivityFormState> {
|
||||||
|
late final _$args = ref.$arg as (ScheduledActivityEntity?, int);
|
||||||
|
ScheduledActivityEntity? get initial => _$args.$1;
|
||||||
|
int get fallbackWeekDay => _$args.$2;
|
||||||
|
|
||||||
|
ActivityFormState build(
|
||||||
|
ScheduledActivityEntity? initial,
|
||||||
|
int fallbackWeekDay,
|
||||||
|
);
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build(_$args.$1, _$args.$2);
|
||||||
|
final ref = this.ref as $Ref<ActivityFormState, ActivityFormState>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<ActivityFormState, ActivityFormState>,
|
||||||
|
ActivityFormState,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:device_management/src/core/data/models/create_scheduled_activity_request_dto.dart';
|
||||||
|
import 'package:device_management/src/core/data/models/update_scheduled_activity_request_dto.dart';
|
||||||
|
import 'package:device_management/src/core/providers/scheduled_activities_repository_provider.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/domain/entities/scheduled_activity_entity.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_provider.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:sf_tracking/sf_tracking.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
part 'scheduled_activities_controller.g.dart';
|
||||||
|
|
||||||
|
class ScheduledActivityOverlapException implements Exception {
|
||||||
|
const ScheduledActivityOverlapException(this.overlap);
|
||||||
|
final ScheduledActivityEntity overlap;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ScheduledActivityAction { create, update, delete }
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ScheduledActivitiesController extends _$ScheduledActivitiesController {
|
||||||
|
static const _uuid = Uuid();
|
||||||
|
|
||||||
|
ScheduledActivityAction? _lastAction;
|
||||||
|
|
||||||
|
ScheduledActivityAction? get lastAction => _lastAction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> build() {}
|
||||||
|
|
||||||
|
ScheduledActivityEntity? _findOverlap({
|
||||||
|
required List<ScheduledActivityEntity> current,
|
||||||
|
required int weekDay,
|
||||||
|
required String period,
|
||||||
|
String? excludeId,
|
||||||
|
}) {
|
||||||
|
final candidate = ScheduledActivityEntity(
|
||||||
|
id: '',
|
||||||
|
deviceId: '',
|
||||||
|
weekDay: weekDay,
|
||||||
|
name: '',
|
||||||
|
period: period,
|
||||||
|
createdAt: 0,
|
||||||
|
);
|
||||||
|
for (final activity in current) {
|
||||||
|
if (activity.id == excludeId) continue;
|
||||||
|
if (activity.overlapsWith(candidate)) return activity;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createActivity({
|
||||||
|
required String deviceId,
|
||||||
|
required String name,
|
||||||
|
required int weekDay,
|
||||||
|
required String period,
|
||||||
|
required List<ScheduledActivityEntity> current,
|
||||||
|
}) async {
|
||||||
|
_lastAction = ScheduledActivityAction.create;
|
||||||
|
state = const AsyncLoading();
|
||||||
|
final overlap =
|
||||||
|
_findOverlap(current: current, weekDay: weekDay, period: period);
|
||||||
|
if (overlap != null) {
|
||||||
|
state = AsyncError(
|
||||||
|
ScheduledActivityOverlapException(overlap),
|
||||||
|
StackTrace.current,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state = await AsyncValue.guard(() async {
|
||||||
|
await ref.read(scheduledActivitiesRepositoryProvider).createActivity(
|
||||||
|
request: CreateScheduledActivityRequestDto(
|
||||||
|
id: _uuid.v4(),
|
||||||
|
deviceId: deviceId,
|
||||||
|
weekDay: weekDay,
|
||||||
|
name: name,
|
||||||
|
period: period,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ref.invalidate(scheduledActivitiesProvider(deviceId));
|
||||||
|
unawaited(
|
||||||
|
ref
|
||||||
|
.read(sfTrackingProvider)
|
||||||
|
.legacyDeviceScheduledActivityAdded(
|
||||||
|
weekDay: weekDay,
|
||||||
|
period: period,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateActivity({
|
||||||
|
required String deviceId,
|
||||||
|
required String activityId,
|
||||||
|
required int weekDay,
|
||||||
|
required String name,
|
||||||
|
required String period,
|
||||||
|
required List<ScheduledActivityEntity> current,
|
||||||
|
}) async {
|
||||||
|
_lastAction = ScheduledActivityAction.update;
|
||||||
|
state = const AsyncLoading();
|
||||||
|
final overlap = _findOverlap(
|
||||||
|
current: current,
|
||||||
|
weekDay: weekDay,
|
||||||
|
period: period,
|
||||||
|
excludeId: activityId,
|
||||||
|
);
|
||||||
|
if (overlap != null) {
|
||||||
|
state = AsyncError(
|
||||||
|
ScheduledActivityOverlapException(overlap),
|
||||||
|
StackTrace.current,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state = await AsyncValue.guard(() async {
|
||||||
|
await ref.read(scheduledActivitiesRepositoryProvider).updateActivity(
|
||||||
|
activityId: activityId,
|
||||||
|
request: UpdateScheduledActivityRequestDto(
|
||||||
|
deviceId: deviceId,
|
||||||
|
name: name,
|
||||||
|
period: period,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ref.invalidate(scheduledActivitiesProvider(deviceId));
|
||||||
|
unawaited(
|
||||||
|
ref
|
||||||
|
.read(sfTrackingProvider)
|
||||||
|
.legacyDeviceScheduledActivityUpdated(
|
||||||
|
weekDay: weekDay,
|
||||||
|
period: period,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteActivity({
|
||||||
|
required String deviceId,
|
||||||
|
required String activityId,
|
||||||
|
}) async {
|
||||||
|
_lastAction = ScheduledActivityAction.delete;
|
||||||
|
state = const AsyncLoading();
|
||||||
|
state = await AsyncValue.guard(() async {
|
||||||
|
await ref
|
||||||
|
.read(scheduledActivitiesRepositoryProvider)
|
||||||
|
.deleteActivity(activityId: activityId);
|
||||||
|
ref.invalidate(scheduledActivitiesProvider(deviceId));
|
||||||
|
unawaited(
|
||||||
|
ref.read(sfTrackingProvider).legacyDeviceScheduledActivityRemoved(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'scheduled_activities_controller.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(ScheduledActivitiesController)
|
||||||
|
const scheduledActivitiesControllerProvider =
|
||||||
|
ScheduledActivitiesControllerProvider._();
|
||||||
|
|
||||||
|
final class ScheduledActivitiesControllerProvider
|
||||||
|
extends $AsyncNotifierProvider<ScheduledActivitiesController, void> {
|
||||||
|
const ScheduledActivitiesControllerProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'scheduledActivitiesControllerProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$scheduledActivitiesControllerHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
ScheduledActivitiesController create() => ScheduledActivitiesController();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$scheduledActivitiesControllerHash() =>
|
||||||
|
r'0add05c19aae539637b858f13168a886879042c9';
|
||||||
|
|
||||||
|
abstract class _$ScheduledActivitiesController extends $AsyncNotifier<void> {
|
||||||
|
FutureOr<void> build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
build();
|
||||||
|
final ref = this.ref as $Ref<AsyncValue<void>, void>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<AsyncValue<void>, void>,
|
||||||
|
AsyncValue<void>,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:device_management/src/core/providers/scheduled_activities_repository_provider.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/domain/entities/scheduled_activity_entity.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'scheduled_activities_provider.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<ScheduledActivityEntity>> scheduledActivities(
|
||||||
|
Ref ref,
|
||||||
|
String deviceId,
|
||||||
|
) async {
|
||||||
|
final activities = await ref
|
||||||
|
.read(scheduledActivitiesRepositoryProvider)
|
||||||
|
.getActivities(deviceId: deviceId);
|
||||||
|
return [...activities]..sort((a, b) {
|
||||||
|
final dayCompare = a.weekDay.compareTo(b.weekDay);
|
||||||
|
if (dayCompare != 0) return dayCompare;
|
||||||
|
return a.startMinutes.compareTo(b.startMinutes);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'scheduled_activities_provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(scheduledActivities)
|
||||||
|
const scheduledActivitiesProvider = ScheduledActivitiesFamily._();
|
||||||
|
|
||||||
|
final class ScheduledActivitiesProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<List<ScheduledActivityEntity>>,
|
||||||
|
List<ScheduledActivityEntity>,
|
||||||
|
FutureOr<List<ScheduledActivityEntity>>
|
||||||
|
>
|
||||||
|
with
|
||||||
|
$FutureModifier<List<ScheduledActivityEntity>>,
|
||||||
|
$FutureProvider<List<ScheduledActivityEntity>> {
|
||||||
|
const ScheduledActivitiesProvider._({
|
||||||
|
required ScheduledActivitiesFamily super.from,
|
||||||
|
required String super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'scheduledActivitiesProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$scheduledActivitiesHash();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'scheduledActivitiesProvider'
|
||||||
|
''
|
||||||
|
'($argument)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$FutureProviderElement<List<ScheduledActivityEntity>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<ScheduledActivityEntity>> create(Ref ref) {
|
||||||
|
final argument = this.argument as String;
|
||||||
|
return scheduledActivities(ref, argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is ScheduledActivitiesProvider && other.argument == argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return argument.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$scheduledActivitiesHash() =>
|
||||||
|
r'bf2e33cd408ea36967046d0c46323fc49ef628f1';
|
||||||
|
|
||||||
|
final class ScheduledActivitiesFamily extends $Family
|
||||||
|
with
|
||||||
|
$FunctionalFamilyOverride<
|
||||||
|
FutureOr<List<ScheduledActivityEntity>>,
|
||||||
|
String
|
||||||
|
> {
|
||||||
|
const ScheduledActivitiesFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'scheduledActivitiesProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
ScheduledActivitiesProvider call(String deviceId) =>
|
||||||
|
ScheduledActivitiesProvider._(argument: deviceId, from: this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => r'scheduledActivitiesProvider';
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_controller.dart';
|
||||||
import 'package:legacy_theme/legacy_theme.dart';
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_provider.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/widgets/activity_form_sheet.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/widgets/day_timeline.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
import 'package:legacy_ui/legacy_ui.dart';
|
import 'package:legacy_ui/legacy_ui.dart';
|
||||||
import 'package:navigation/navigation.dart';
|
import 'package:navigation/navigation.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
import 'state/scheduled_activities_view_model.dart';
|
|
||||||
import 'widgets/activity_form_sheet.dart';
|
|
||||||
import 'widgets/day_timeline.dart';
|
|
||||||
|
|
||||||
class ScheduledActivitiesScreen extends ConsumerStatefulWidget {
|
class ScheduledActivitiesScreen extends ConsumerStatefulWidget {
|
||||||
final NavigationContract navigationContract;
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
@@ -46,60 +46,90 @@ class _ScheduledActivitiesScreenState
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final state = ref.watch(scheduledActivitiesViewModelProvider);
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
final device = ref.watch(selectedDeviceProvider).value;
|
||||||
|
|
||||||
ref.listen(
|
ref.listen(scheduledActivitiesControllerProvider, (prev, next) async {
|
||||||
scheduledActivitiesViewModelProvider.select((s) => s.errorMessage),
|
if (prev == null || !prev.isLoading || next.isLoading) return;
|
||||||
(previous, next) {
|
if (next.hasError) {
|
||||||
if (next.isNotEmpty) {
|
final error = next.error;
|
||||||
showTopSnackbar(context, message: next, type: MessageType.error);
|
if (error is ScheduledActivityOverlapException) {
|
||||||
|
await showErrorDialog(
|
||||||
|
context,
|
||||||
|
I18n.scheduledActivityOverlap,
|
||||||
|
args: {
|
||||||
|
'name': error.overlap.name,
|
||||||
|
'time': '${error.overlap.startTime}-${error.overlap.endTime}',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await next.showErrorOn(context);
|
||||||
}
|
}
|
||||||
},
|
return;
|
||||||
);
|
}
|
||||||
|
final action = ref
|
||||||
|
.read(scheduledActivitiesControllerProvider.notifier)
|
||||||
|
.lastAction;
|
||||||
|
final key = switch (action) {
|
||||||
|
ScheduledActivityAction.create => I18n.scheduledActivityCreated,
|
||||||
|
ScheduledActivityAction.update => I18n.scheduledActivityUpdated,
|
||||||
|
ScheduledActivityAction.delete => I18n.scheduledActivityDeleted,
|
||||||
|
null => I18n.deviceUpdatedSuccess,
|
||||||
|
};
|
||||||
|
await showSuccessDialog(context, key);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (device == null) {
|
||||||
|
return LegacyPageLayout(
|
||||||
|
title: context.translate(I18n.activityScheduleTitle),
|
||||||
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final activitiesAsync =
|
||||||
|
ref.watch(scheduledActivitiesProvider(device.id));
|
||||||
|
|
||||||
return LegacyPageLayout(
|
return LegacyPageLayout(
|
||||||
title: context.translate(I18n.activityScheduleTitle),
|
title: context.translate(I18n.activityScheduleTitle),
|
||||||
body: state.isLoading
|
body: activitiesAsync.when(
|
||||||
? const Center(child: CircularProgressIndicator())
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
: Column(
|
error: (err, _) => Center(child: Text(err.toString())),
|
||||||
children: [
|
data: (activities) => Column(
|
||||||
TabBar(
|
children: [
|
||||||
controller: _tabController,
|
TabBar(
|
||||||
labelColor: context.sfColors.legacyPrimary,
|
controller: _tabController,
|
||||||
unselectedLabelColor:
|
labelColor: primaryColor,
|
||||||
Theme.of(context).colorScheme.onSurface,
|
unselectedLabelColor: Theme.of(context).colorScheme.onSurface,
|
||||||
indicatorColor: context.sfColors.legacyPrimary,
|
indicatorColor: primaryColor,
|
||||||
indicatorWeight: 3,
|
indicatorWeight: 3,
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
unselectedLabelStyle: TextStyle(
|
unselectedLabelStyle: TextStyle(
|
||||||
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
tabs: weekDayI18nKeys.entries
|
tabs: weekDayI18nKeys.entries
|
||||||
.map(
|
.map((e) => Tab(text: weekDayShortLabel(context, e.value)))
|
||||||
(e) => Tab(text: weekDayShortLabel(context, e.value)),
|
.toList(),
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
children: List.generate(7, (index) {
|
|
||||||
final weekDay = index + 1;
|
|
||||||
final dayActivities = state.activities
|
|
||||||
.where((a) => a.weekDay == weekDay)
|
|
||||||
.toList();
|
|
||||||
return DayTimeline(activities: dayActivities);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: List.generate(7, (index) {
|
||||||
|
final weekDay = index + 1;
|
||||||
|
final dayActivities =
|
||||||
|
activities.where((a) => a.weekDay == weekDay).toList();
|
||||||
|
return DayTimeline(activities: dayActivities);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
footer: Material(
|
footer: Material(
|
||||||
color: context.sfColors.legacyPrimary,
|
color: primaryColor,
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
customBorder: const CircleBorder(),
|
customBorder: const CircleBorder(),
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
|
||||||
import 'package:sf_tracking/sf_tracking.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
import '../../../../core/data/models/create_scheduled_activity_request_dto.dart';
|
|
||||||
import '../../../../core/data/models/update_scheduled_activity_request_dto.dart';
|
|
||||||
import '../../../../core/domain/repositories/scheduled_activities_repository.dart';
|
|
||||||
import '../../../../core/providers/scheduled_activities_repository_provider.dart';
|
|
||||||
import '../../domain/entities/scheduled_activity_entity.dart';
|
|
||||||
import 'scheduled_activities_view_state.dart';
|
|
||||||
|
|
||||||
final scheduledActivitiesViewModelProvider =
|
|
||||||
NotifierProvider.autoDispose<
|
|
||||||
ScheduledActivitiesViewModel,
|
|
||||||
ScheduledActivitiesViewState
|
|
||||||
>(ScheduledActivitiesViewModel.new);
|
|
||||||
|
|
||||||
class ScheduledActivitiesViewModel
|
|
||||||
extends Notifier<ScheduledActivitiesViewState> {
|
|
||||||
late final ScheduledActivitiesRepository _repository;
|
|
||||||
late final SfTrackingRepository _tracking;
|
|
||||||
static const _uuid = Uuid();
|
|
||||||
|
|
||||||
@override
|
|
||||||
ScheduledActivitiesViewState build() {
|
|
||||||
_repository = ref.read(scheduledActivitiesRepositoryProvider);
|
|
||||||
_tracking = ref.read(sfTrackingProvider);
|
|
||||||
_init();
|
|
||||||
return const ScheduledActivitiesViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
String? get _deviceId => ref.read(selectedDeviceProvider).value?.id;
|
|
||||||
|
|
||||||
Future<void> _init() async {
|
|
||||||
if (_deviceId == null) {
|
|
||||||
state = state.copyWith(isLoading: false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final activities = await _repository.getActivities(deviceId: _deviceId!);
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
|
|
||||||
state = state.copyWith(
|
|
||||||
activities: _sortActivities(activities),
|
|
||||||
isLoading: false,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledActivityEntity? _findOverlap({
|
|
||||||
required int weekDay,
|
|
||||||
required String period,
|
|
||||||
String? excludeId,
|
|
||||||
}) {
|
|
||||||
final candidate = ScheduledActivityEntity(
|
|
||||||
id: '',
|
|
||||||
deviceId: '',
|
|
||||||
weekDay: weekDay,
|
|
||||||
name: '',
|
|
||||||
period: period,
|
|
||||||
createdAt: 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (final activity in state.activities) {
|
|
||||||
if (activity.id == excludeId) continue;
|
|
||||||
if (activity.overlapsWith(candidate)) return activity;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createActivity({
|
|
||||||
required String name,
|
|
||||||
required int weekDay,
|
|
||||||
required String period,
|
|
||||||
}) async {
|
|
||||||
if (_deviceId == null) return;
|
|
||||||
|
|
||||||
final overlap = _findOverlap(weekDay: weekDay, period: period);
|
|
||||||
if (overlap != null) {
|
|
||||||
state = state.copyWith(errorMessage: _overlapMessage(overlap));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
state = state.copyWith(isLoading: true, errorMessage: '');
|
|
||||||
|
|
||||||
final request = CreateScheduledActivityRequestDto(
|
|
||||||
id: _uuid.v4(),
|
|
||||||
deviceId: _deviceId!,
|
|
||||||
weekDay: weekDay,
|
|
||||||
name: name,
|
|
||||||
period: period,
|
|
||||||
);
|
|
||||||
|
|
||||||
await _repository.createActivity(request: request);
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
|
|
||||||
unawaited(
|
|
||||||
_tracking.legacyDeviceScheduledActivityAdded(
|
|
||||||
weekDay: weekDay,
|
|
||||||
period: period,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await _reload();
|
|
||||||
} catch (e) {
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateActivity({
|
|
||||||
required String activityId,
|
|
||||||
required int weekDay,
|
|
||||||
required String name,
|
|
||||||
required String period,
|
|
||||||
}) async {
|
|
||||||
if (_deviceId == null) return;
|
|
||||||
|
|
||||||
final overlap = _findOverlap(
|
|
||||||
weekDay: weekDay,
|
|
||||||
period: period,
|
|
||||||
excludeId: activityId,
|
|
||||||
);
|
|
||||||
if (overlap != null) {
|
|
||||||
state = state.copyWith(errorMessage: _overlapMessage(overlap));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
state = state.copyWith(isLoading: true, errorMessage: '');
|
|
||||||
|
|
||||||
final request = UpdateScheduledActivityRequestDto(
|
|
||||||
deviceId: _deviceId!,
|
|
||||||
name: name,
|
|
||||||
period: period,
|
|
||||||
);
|
|
||||||
|
|
||||||
await _repository.updateActivity(
|
|
||||||
activityId: activityId,
|
|
||||||
request: request,
|
|
||||||
);
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
|
|
||||||
unawaited(
|
|
||||||
_tracking.legacyDeviceScheduledActivityUpdated(
|
|
||||||
weekDay: weekDay,
|
|
||||||
period: period,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await _reload();
|
|
||||||
} catch (e) {
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteActivity(String activityId) async {
|
|
||||||
try {
|
|
||||||
state = state.copyWith(isLoading: true, errorMessage: '');
|
|
||||||
|
|
||||||
await _repository.deleteActivity(activityId: activityId);
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
|
|
||||||
unawaited(_tracking.legacyDeviceScheduledActivityRemoved());
|
|
||||||
|
|
||||||
await _reload();
|
|
||||||
} catch (e) {
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _reload() async {
|
|
||||||
if (_deviceId == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final activities = await _repository.getActivities(deviceId: _deviceId!);
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
|
|
||||||
state = state.copyWith(
|
|
||||||
activities: _sortActivities(activities),
|
|
||||||
isLoading: false,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
state = state.copyWith(isLoading: false, errorMessage: _formatError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ScheduledActivityEntity> _sortActivities(
|
|
||||||
List<ScheduledActivityEntity> activities,
|
|
||||||
) {
|
|
||||||
return List.of(activities)..sort((a, b) {
|
|
||||||
final dayCompare = a.weekDay.compareTo(b.weekDay);
|
|
||||||
if (dayCompare != 0) return dayCompare;
|
|
||||||
return a.startMinutes.compareTo(b.startMinutes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String _overlapMessage(ScheduledActivityEntity overlap) {
|
|
||||||
return I18n.scheduledActivityOverlap.tr(
|
|
||||||
args: {
|
|
||||||
'name': overlap.name,
|
|
||||||
'time': '${overlap.startTime}-${overlap.endTime}',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatError(Object e) {
|
|
||||||
final msg = e.toString();
|
|
||||||
return msg.startsWith('Exception: ') ? msg.substring(11) : msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import 'package:device_management/src/features/scheduled_activities/domain/entities/scheduled_activity_entity.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
|
|
||||||
part 'scheduled_activities_view_state.freezed.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
abstract class ScheduledActivitiesViewState
|
|
||||||
with _$ScheduledActivitiesViewState {
|
|
||||||
const factory ScheduledActivitiesViewState({
|
|
||||||
@Default([]) List<ScheduledActivityEntity> activities,
|
|
||||||
@Default(true) bool isLoading,
|
|
||||||
@Default('') String errorMessage,
|
|
||||||
}) = _ScheduledActivitiesViewState;
|
|
||||||
}
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// coverage:ignore-file
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
|
||||||
|
|
||||||
part of 'scheduled_activities_view_state.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// FreezedGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$ScheduledActivitiesViewState {
|
|
||||||
|
|
||||||
List<ScheduledActivityEntity> get activities; bool get isLoading; String get errorMessage;
|
|
||||||
/// Create a copy of ScheduledActivitiesViewState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$ScheduledActivitiesViewStateCopyWith<ScheduledActivitiesViewState> get copyWith => _$ScheduledActivitiesViewStateCopyWithImpl<ScheduledActivitiesViewState>(this as ScheduledActivitiesViewState, _$identity);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ScheduledActivitiesViewState&&const DeepCollectionEquality().equals(other.activities, activities)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(activities),isLoading,errorMessage);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'ScheduledActivitiesViewState(activities: $activities, isLoading: $isLoading, errorMessage: $errorMessage)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $ScheduledActivitiesViewStateCopyWith<$Res> {
|
|
||||||
factory $ScheduledActivitiesViewStateCopyWith(ScheduledActivitiesViewState value, $Res Function(ScheduledActivitiesViewState) _then) = _$ScheduledActivitiesViewStateCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
List<ScheduledActivityEntity> activities, bool isLoading, String errorMessage
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$ScheduledActivitiesViewStateCopyWithImpl<$Res>
|
|
||||||
implements $ScheduledActivitiesViewStateCopyWith<$Res> {
|
|
||||||
_$ScheduledActivitiesViewStateCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final ScheduledActivitiesViewState _self;
|
|
||||||
final $Res Function(ScheduledActivitiesViewState) _then;
|
|
||||||
|
|
||||||
/// Create a copy of ScheduledActivitiesViewState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? activities = null,Object? isLoading = null,Object? errorMessage = null,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
activities: null == activities ? _self.activities : activities // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<ScheduledActivityEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [ScheduledActivitiesViewState].
|
|
||||||
extension ScheduledActivitiesViewStatePatterns on ScheduledActivitiesViewState {
|
|
||||||
/// 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( _ScheduledActivitiesViewState value)? $default,{required TResult orElse(),}){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _ScheduledActivitiesViewState() 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( _ScheduledActivitiesViewState value) $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _ScheduledActivitiesViewState():
|
|
||||||
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( _ScheduledActivitiesViewState value)? $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _ScheduledActivitiesViewState() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to an `orElse` callback.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<ScheduledActivityEntity> activities, bool isLoading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _ScheduledActivitiesViewState() when $default != null:
|
|
||||||
return $default(_that.activities,_that.isLoading,_that.errorMessage);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// As opposed to `map`, this offers destructuring.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case Subclass2(:final field2):
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<ScheduledActivityEntity> activities, bool isLoading, String errorMessage) $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _ScheduledActivitiesViewState():
|
|
||||||
return $default(_that.activities,_that.isLoading,_that.errorMessage);case _:
|
|
||||||
throw StateError('Unexpected subclass');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to returning `null`
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<ScheduledActivityEntity> activities, bool isLoading, String errorMessage)? $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _ScheduledActivitiesViewState() when $default != null:
|
|
||||||
return $default(_that.activities,_that.isLoading,_that.errorMessage);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
|
|
||||||
class _ScheduledActivitiesViewState implements ScheduledActivitiesViewState {
|
|
||||||
const _ScheduledActivitiesViewState({final List<ScheduledActivityEntity> activities = const [], this.isLoading = true, this.errorMessage = ''}): _activities = activities;
|
|
||||||
|
|
||||||
|
|
||||||
final List<ScheduledActivityEntity> _activities;
|
|
||||||
@override@JsonKey() List<ScheduledActivityEntity> get activities {
|
|
||||||
if (_activities is EqualUnmodifiableListView) return _activities;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableListView(_activities);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override@JsonKey() final bool isLoading;
|
|
||||||
@override@JsonKey() final String errorMessage;
|
|
||||||
|
|
||||||
/// Create a copy of ScheduledActivitiesViewState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$ScheduledActivitiesViewStateCopyWith<_ScheduledActivitiesViewState> get copyWith => __$ScheduledActivitiesViewStateCopyWithImpl<_ScheduledActivitiesViewState>(this, _$identity);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ScheduledActivitiesViewState&&const DeepCollectionEquality().equals(other._activities, _activities)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_activities),isLoading,errorMessage);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'ScheduledActivitiesViewState(activities: $activities, isLoading: $isLoading, errorMessage: $errorMessage)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$ScheduledActivitiesViewStateCopyWith<$Res> implements $ScheduledActivitiesViewStateCopyWith<$Res> {
|
|
||||||
factory _$ScheduledActivitiesViewStateCopyWith(_ScheduledActivitiesViewState value, $Res Function(_ScheduledActivitiesViewState) _then) = __$ScheduledActivitiesViewStateCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
List<ScheduledActivityEntity> activities, bool isLoading, String errorMessage
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$ScheduledActivitiesViewStateCopyWithImpl<$Res>
|
|
||||||
implements _$ScheduledActivitiesViewStateCopyWith<$Res> {
|
|
||||||
__$ScheduledActivitiesViewStateCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _ScheduledActivitiesViewState _self;
|
|
||||||
final $Res Function(_ScheduledActivitiesViewState) _then;
|
|
||||||
|
|
||||||
/// Create a copy of ScheduledActivitiesViewState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? activities = null,Object? isLoading = null,Object? errorMessage = null,}) {
|
|
||||||
return _then(_ScheduledActivitiesViewState(
|
|
||||||
activities: null == activities ? _self._activities : activities // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<ScheduledActivityEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
import 'package:legacy_theme/legacy_theme.dart';
|
import 'package:device_management/src/features/scheduled_activities/domain/entities/scheduled_activity_entity.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/activity_form_provider.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_controller.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_provider.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
import 'package:legacy_ui/legacy_ui.dart';
|
import 'package:legacy_ui/legacy_ui.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
import '../../domain/entities/scheduled_activity_entity.dart';
|
|
||||||
import '../state/scheduled_activities_view_model.dart';
|
|
||||||
|
|
||||||
void showActivityFormSheet(
|
void showActivityFormSheet(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
ScheduledActivityEntity? activity,
|
ScheduledActivityEntity? activity,
|
||||||
int? weekDay,
|
int? weekDay,
|
||||||
}) {
|
}) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
@@ -35,94 +37,82 @@ class ActivityFormSheet extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
|
class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
|
||||||
late final TextEditingController _nameController;
|
late final TextEditingController _nameController;
|
||||||
late int _selectedWeekDay;
|
|
||||||
late TimeOfDay _startTime;
|
|
||||||
late TimeOfDay _endTime;
|
|
||||||
bool _showStartPicker = false;
|
|
||||||
bool _showEndPicker = false;
|
|
||||||
|
|
||||||
bool get _isEditing => widget.activity != null;
|
bool get _isEditing => widget.activity != null;
|
||||||
|
|
||||||
bool get _isFormValid {
|
|
||||||
if (_nameController.text.trim().isEmpty) return false;
|
|
||||||
return _timeToMinutes(_startTime) < _timeToMinutes(_endTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final activity = widget.activity;
|
_nameController = TextEditingController(text: widget.activity?.name ?? '');
|
||||||
_nameController = TextEditingController(text: activity?.name ?? '');
|
_nameController.addListener(_onNameChanged);
|
||||||
_nameController.addListener(() => setState(() {}));
|
|
||||||
_selectedWeekDay = activity?.weekDay ?? widget.weekDay ?? 1;
|
|
||||||
|
|
||||||
if (activity != null && activity.hasValidPeriod) {
|
|
||||||
_startTime = TimeOfDay(
|
|
||||||
hour: int.parse(activity.period.substring(0, 2)),
|
|
||||||
minute: int.parse(activity.period.substring(2, 4)),
|
|
||||||
);
|
|
||||||
_endTime = TimeOfDay(
|
|
||||||
hour: int.parse(activity.period.substring(4, 6)),
|
|
||||||
minute: int.parse(activity.period.substring(6, 8)),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
_startTime = const TimeOfDay(hour: 8, minute: 0);
|
|
||||||
_endTime = const TimeOfDay(hour: 9, minute: 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_nameController.removeListener(_onNameChanged);
|
||||||
_nameController.dispose();
|
_nameController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _buildPeriod() {
|
void _onNameChanged() {
|
||||||
return '${_startTime.hour.toString().padLeft(2, '0')}'
|
if (mounted) setState(() {});
|
||||||
'${_startTime.minute.toString().padLeft(2, '0')}'
|
|
||||||
'${_endTime.hour.toString().padLeft(2, '0')}'
|
|
||||||
'${_endTime.minute.toString().padLeft(2, '0')}';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int _timeToMinutes(TimeOfDay time) => time.hour * 60 + time.minute;
|
Future<void> _submit({
|
||||||
|
required ActivityFormState formState,
|
||||||
void _togglePicker({required bool isStart}) {
|
required List<ScheduledActivityEntity> currentActivities,
|
||||||
setState(() {
|
}) async {
|
||||||
if (isStart) {
|
if (!formState.isTimeValid) return;
|
||||||
_showStartPicker = !_showStartPicker;
|
if (_nameController.text.trim().isEmpty) return;
|
||||||
_showEndPicker = false;
|
|
||||||
} else {
|
|
||||||
_showEndPicker = !_showEndPicker;
|
|
||||||
_showStartPicker = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _submit() async {
|
|
||||||
if (!_isFormValid) return;
|
|
||||||
if (!await guardDeviceCommand(context, ref)) return;
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final device = ref.read(selectedDeviceProvider).value;
|
||||||
|
if (device == null) return;
|
||||||
|
|
||||||
final name = _nameController.text.trim();
|
final name = _nameController.text.trim();
|
||||||
final period = _buildPeriod();
|
final controller =
|
||||||
final vm = ref.read(scheduledActivitiesViewModelProvider.notifier);
|
ref.read(scheduledActivitiesControllerProvider.notifier);
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
if (_isEditing) {
|
if (_isEditing) {
|
||||||
vm.updateActivity(
|
controller.updateActivity(
|
||||||
|
deviceId: device.id,
|
||||||
activityId: widget.activity!.id,
|
activityId: widget.activity!.id,
|
||||||
weekDay: widget.activity!.weekDay,
|
weekDay: widget.activity!.weekDay,
|
||||||
name: name,
|
name: name,
|
||||||
period: period,
|
period: formState.period,
|
||||||
|
current: currentActivities,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
vm.createActivity(name: name, weekDay: _selectedWeekDay, period: period);
|
controller.createActivity(
|
||||||
|
deviceId: device.id,
|
||||||
|
name: name,
|
||||||
|
weekDay: formState.weekDay,
|
||||||
|
period: formState.period,
|
||||||
|
current: currentActivities,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
final fallbackWeekDay = widget.weekDay ?? 1;
|
||||||
|
final formProvider = activityFormProvider(widget.activity, fallbackWeekDay);
|
||||||
|
final formState = ref.watch(formProvider);
|
||||||
|
final formNotifier = ref.read(formProvider.notifier);
|
||||||
|
|
||||||
|
final device = ref.watch(selectedDeviceProvider).value;
|
||||||
|
final currentActivities = device == null
|
||||||
|
? const <ScheduledActivityEntity>[]
|
||||||
|
: (ref.watch(scheduledActivitiesProvider(device.id)).value ??
|
||||||
|
const []);
|
||||||
|
|
||||||
|
final canSubmit = _nameController.text.trim().isNotEmpty &&
|
||||||
|
formState.isTimeValid;
|
||||||
|
|
||||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -136,7 +126,8 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
|
|||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: EdgeInsets.all(SizeUtils.getByScreen(small: 22, big: 20)),
|
padding:
|
||||||
|
EdgeInsets.all(SizeUtils.getByScreen(small: 22, big: 20)),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@@ -163,7 +154,7 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
|
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: context.sfColors.legacyPrimary,
|
color: primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 18)),
|
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 18)),
|
||||||
@@ -176,12 +167,9 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 14)),
|
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 14)),
|
||||||
WeekDayChips.single(
|
WeekDayChips.single(
|
||||||
selectedWeekDay: _isEditing
|
selectedWeekDay: formState.weekDay,
|
||||||
? widget.activity!.weekDay
|
|
||||||
: _selectedWeekDay,
|
|
||||||
enabled: !_isEditing,
|
enabled: !_isEditing,
|
||||||
onChanged: (value) =>
|
onChanged: formNotifier.setWeekDay,
|
||||||
setState(() => _selectedWeekDay = value),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 14)),
|
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 14)),
|
||||||
Row(
|
Row(
|
||||||
@@ -191,39 +179,45 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
|
|||||||
label: context.translate(
|
label: context.translate(
|
||||||
I18n.scheduledActivityStartTime,
|
I18n.scheduledActivityStartTime,
|
||||||
),
|
),
|
||||||
time: _startTime,
|
time: formState.startTime,
|
||||||
isExpanded: _showStartPicker,
|
isExpanded: formState.showStartPicker,
|
||||||
onTap: () => _togglePicker(isStart: true),
|
onTap: formNotifier.toggleStartPicker,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: SizeUtils.getByScreen(small: 12, big: 10)),
|
SizedBox(width: SizeUtils.getByScreen(small: 12, big: 10)),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _TimeSelector(
|
child: _TimeSelector(
|
||||||
label: context.translate(I18n.scheduledActivityEndTime),
|
label: context
|
||||||
time: _endTime,
|
.translate(I18n.scheduledActivityEndTime),
|
||||||
isExpanded: _showEndPicker,
|
time: formState.endTime,
|
||||||
onTap: () => _togglePicker(isStart: false),
|
isExpanded: formState.showEndPicker,
|
||||||
|
onTap: formNotifier.toggleEndPicker,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_showStartPicker)
|
if (formState.showStartPicker)
|
||||||
_InlineTimePicker(
|
_InlineTimePicker(
|
||||||
initialTime: _startTime,
|
initialTime: formState.startTime,
|
||||||
onChanged: (time) => setState(() => _startTime = time),
|
onChanged: formNotifier.setStartTime,
|
||||||
),
|
),
|
||||||
if (_showEndPicker)
|
if (formState.showEndPicker)
|
||||||
_InlineTimePicker(
|
_InlineTimePicker(
|
||||||
initialTime: _endTime,
|
initialTime: formState.endTime,
|
||||||
onChanged: (time) => setState(() => _endTime = time),
|
onChanged: formNotifier.setEndTime,
|
||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 22)),
|
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 22)),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: SizeUtils.getByScreen(small: 48, big: 46),
|
height: SizeUtils.getByScreen(small: 48, big: 46),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isFormValid ? _submit : null,
|
onPressed: canSubmit
|
||||||
|
? () => _submit(
|
||||||
|
formState: formState,
|
||||||
|
currentActivities: currentActivities,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: context.sfColors.legacyPrimary,
|
backgroundColor: primaryColor,
|
||||||
disabledBackgroundColor: Colors.grey[300],
|
disabledBackgroundColor: Colors.grey[300],
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
@@ -234,7 +228,7 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
context.translate(I18n.save),
|
context.translate(I18n.save),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: _isFormValid ? Colors.white : Colors.grey,
|
color: canSubmit ? Colors.white : Colors.grey,
|
||||||
fontSize: SizeUtils.getByScreen(small: 16, big: 15),
|
fontSize: SizeUtils.getByScreen(small: 16, big: 15),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
@@ -323,7 +317,10 @@ class _InlineTimePicker extends StatelessWidget {
|
|||||||
final TimeOfDay initialTime;
|
final TimeOfDay initialTime;
|
||||||
final ValueChanged<TimeOfDay> onChanged;
|
final ValueChanged<TimeOfDay> onChanged;
|
||||||
|
|
||||||
const _InlineTimePicker({required this.initialTime, required this.onChanged});
|
const _InlineTimePicker({
|
||||||
|
required this.initialTime,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
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/entities/scheduled_activity_entity.dart';
|
|
||||||
import '../state/scheduled_activities_view_model.dart';
|
|
||||||
import 'package:legacy_theme/legacy_theme.dart';
|
|
||||||
|
|
||||||
class ConfirmDeleteActivityDialog extends ConsumerWidget {
|
|
||||||
final ScheduledActivityEntity activity;
|
|
||||||
|
|
||||||
const ConfirmDeleteActivityDialog({super.key, required this.activity});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: SizeUtils.getByScreen(
|
|
||||||
small: const EdgeInsets.symmetric(horizontal: 32, vertical: 30),
|
|
||||||
big: const EdgeInsets.symmetric(horizontal: 30, vertical: 28),
|
|
||||||
),
|
|
||||||
width: SizeUtils.getByScreen(small: 360, big: 350),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
SizeUtils.getByScreen(small: 16, big: 14),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
I18n.scheduledActivityDeleteMessage.tr(
|
|
||||||
args: {'name': activity.name},
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: SizeUtils.getByScreen(small: 19, big: 18),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 28, big: 27)),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: PrimaryButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
text: context.translate(I18n.cancel),
|
|
||||||
color: context.sfColors.legacyPrimary,
|
|
||||||
height: SizeUtils.getByScreen(small: 38, big: 36),
|
|
||||||
radius: SizeUtils.getByScreen(small: 32, big: 34),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: SizeUtils.getByScreen(small: 4, big: 16)),
|
|
||||||
Expanded(
|
|
||||||
child: PrimaryButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
ref
|
|
||||||
.read(scheduledActivitiesViewModelProvider.notifier)
|
|
||||||
.deleteActivity(activity.id);
|
|
||||||
},
|
|
||||||
text: context.translate(I18n.delete),
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
height: SizeUtils.getByScreen(small: 38, big: 36),
|
|
||||||
radius: SizeUtils.getByScreen(small: 32, big: 34),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:legacy_theme/legacy_theme.dart';
|
import 'package:device_management/src/features/scheduled_activities/domain/entities/scheduled_activity_entity.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_controller.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/widgets/activity_form_sheet.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
import '../../domain/entities/scheduled_activity_entity.dart';
|
|
||||||
import 'activity_form_sheet.dart';
|
|
||||||
import 'confirm_delete_activity_dialog.dart';
|
|
||||||
|
|
||||||
class DayTimeline extends ConsumerWidget {
|
class DayTimeline extends ConsumerWidget {
|
||||||
final List<ScheduledActivityEntity> activities;
|
final List<ScheduledActivityEntity> activities;
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ class DayTimeline extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|
||||||
if (activities.isEmpty) {
|
if (activities.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -70,9 +69,7 @@ class DayTimeline extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
_TimelineDotAndLine(isLast: isLast, color: primaryColor),
|
_TimelineDotAndLine(isLast: isLast, color: primaryColor),
|
||||||
SizedBox(width: SizeUtils.getByScreen(small: 10, big: 8)),
|
SizedBox(width: SizeUtils.getByScreen(small: 10, big: 8)),
|
||||||
Expanded(
|
Expanded(child: _ActivityTimelineCard(activity: activity)),
|
||||||
child: _ActivityTimelineCard(activity: activity),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -85,10 +82,7 @@ class _TimeLabels extends StatelessWidget {
|
|||||||
final String startTime;
|
final String startTime;
|
||||||
final String endTime;
|
final String endTime;
|
||||||
|
|
||||||
const _TimeLabels({
|
const _TimeLabels({required this.startTime, required this.endTime});
|
||||||
required this.startTime,
|
|
||||||
required this.endTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -105,7 +99,7 @@ class _TimeLabels extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 10, big: 8)),
|
SizedBox(height: SizeUtils.getByScreen(small: 10, big: 8)),
|
||||||
Text(startTime, style: style),
|
Text(startTime, style: style),
|
||||||
Spacer(),
|
const Spacer(),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: SizeUtils.getByScreen(small: 14, big: 12),
|
bottom: SizeUtils.getByScreen(small: 14, big: 12),
|
||||||
@@ -113,7 +107,9 @@ class _TimeLabels extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
endTime,
|
endTime,
|
||||||
style: style.copyWith(
|
style: style.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface
|
||||||
.withValues(alpha: 0.5),
|
.withValues(alpha: 0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -160,7 +156,8 @@ class _ActivityTimelineCard extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(bottom: SizeUtils.getByScreen(small: 10, big: 8)),
|
margin:
|
||||||
|
EdgeInsets.only(bottom: SizeUtils.getByScreen(small: 10, big: 8)),
|
||||||
padding: SizeUtils.getByScreen(
|
padding: SizeUtils.getByScreen(
|
||||||
small: const EdgeInsets.fromLTRB(14, 10, 6, 10),
|
small: const EdgeInsets.fromLTRB(14, 10, 6, 10),
|
||||||
big: const EdgeInsets.fromLTRB(12, 8, 4, 8),
|
big: const EdgeInsets.fromLTRB(12, 8, 4, 8),
|
||||||
@@ -193,20 +190,47 @@ class _ActivityTimelineCard extends ConsumerWidget {
|
|||||||
color: context.sfColors.legacyPrimary,
|
color: context.sfColors.legacyPrimary,
|
||||||
size: SizeUtils.getByScreen(small: 20, big: 18),
|
size: SizeUtils.getByScreen(small: 20, big: 18),
|
||||||
),
|
),
|
||||||
constraints: BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
padding: EdgeInsets.all(SizeUtils.getByScreen(small: 8, big: 6)),
|
padding: EdgeInsets.all(SizeUtils.getByScreen(small: 8, big: 6)),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (!await guardDeviceCommand(context, ref)) return;
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
showDialog(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => Dialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
backgroundColor: Colors.transparent,
|
title: Text(
|
||||||
child: ConfirmDeleteActivityDialog(activity: activity),
|
context.translate(
|
||||||
|
I18n.scheduledActivityDeleteMessage,
|
||||||
|
args: {'name': activity.name},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext, false),
|
||||||
|
child: Text(context.translate(I18n.cancel)),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext, true),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
child: Text(context.translate(I18n.delete)),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (confirmed != true) return;
|
||||||
|
final device = ref.read(selectedDeviceProvider).value;
|
||||||
|
if (device == null) return;
|
||||||
|
ref
|
||||||
|
.read(scheduledActivitiesControllerProvider.notifier)
|
||||||
|
.deleteActivity(
|
||||||
|
deviceId: device.id,
|
||||||
|
activityId: activity.id,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.delete_outlined,
|
Icons.delete_outlined,
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import 'package:device_management/src/core/data/models/create_scheduled_activity_request_dto.dart';
|
||||||
|
import 'package:device_management/src/core/data/models/update_scheduled_activity_request_dto.dart';
|
||||||
|
import 'package:device_management/src/core/domain/repositories/scheduled_activities_repository.dart';
|
||||||
|
import 'package:device_management/src/core/providers/scheduled_activities_repository_provider.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/domain/entities/scheduled_activity_entity.dart';
|
||||||
|
import 'package:device_management/src/features/scheduled_activities/presentation/providers/scheduled_activities_controller.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
|
import 'package:sf_shared/testing.dart';
|
||||||
|
import 'package:sf_tracking/sf_tracking.dart';
|
||||||
|
|
||||||
|
class MockScheduledActivitiesRepository extends Mock
|
||||||
|
implements ScheduledActivitiesRepository {}
|
||||||
|
|
||||||
|
const _a = ScheduledActivityEntity(
|
||||||
|
id: 'a',
|
||||||
|
deviceId: 'd1',
|
||||||
|
weekDay: 1,
|
||||||
|
name: 'English class',
|
||||||
|
period: '08000900',
|
||||||
|
createdAt: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUpAll(() {
|
||||||
|
registerFallbackValue(
|
||||||
|
const CreateScheduledActivityRequestDto(
|
||||||
|
id: 'x',
|
||||||
|
deviceId: 'd',
|
||||||
|
weekDay: 1,
|
||||||
|
name: 'n',
|
||||||
|
period: 'p',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
registerFallbackValue(
|
||||||
|
const UpdateScheduledActivityRequestDto(
|
||||||
|
deviceId: 'd',
|
||||||
|
name: 'n',
|
||||||
|
period: 'p',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ProviderContainer buildContainer(ScheduledActivitiesRepository repo) {
|
||||||
|
return makeContainer(
|
||||||
|
overrides: [
|
||||||
|
scheduledActivitiesRepositoryProvider.overrideWithValue(repo),
|
||||||
|
sfTrackingProvider.overrideWithValue(
|
||||||
|
SfTrackingRepository(clients: const []),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group('ScheduledActivitiesController.createActivity', () {
|
||||||
|
test('blocks on overlap', () async {
|
||||||
|
final repo = MockScheduledActivitiesRepository();
|
||||||
|
final container = buildContainer(repo);
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
await container
|
||||||
|
.read(scheduledActivitiesControllerProvider.notifier)
|
||||||
|
.createActivity(
|
||||||
|
deviceId: 'd1',
|
||||||
|
name: 'Math',
|
||||||
|
weekDay: 1,
|
||||||
|
period: '08300930',
|
||||||
|
current: const [_a],
|
||||||
|
);
|
||||||
|
|
||||||
|
final state = container.read(scheduledActivitiesControllerProvider);
|
||||||
|
expect(state, isA<AsyncError<void>>());
|
||||||
|
expect(state.error, isA<ScheduledActivityOverlapException>());
|
||||||
|
verifyNever(() => repo.createActivity(request: any(named: 'request')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates when no overlap', () async {
|
||||||
|
final repo = MockScheduledActivitiesRepository();
|
||||||
|
when(() => repo.createActivity(request: any(named: 'request')))
|
||||||
|
.thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final container = buildContainer(repo);
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
await container
|
||||||
|
.read(scheduledActivitiesControllerProvider.notifier)
|
||||||
|
.createActivity(
|
||||||
|
deviceId: 'd1',
|
||||||
|
name: 'Math',
|
||||||
|
weekDay: 1,
|
||||||
|
period: '10001100',
|
||||||
|
current: const [_a],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.read(scheduledActivitiesControllerProvider),
|
||||||
|
isA<AsyncData<void>>(),
|
||||||
|
);
|
||||||
|
verify(() => repo.createActivity(request: any(named: 'request')))
|
||||||
|
.called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ScheduledActivitiesController.updateActivity', () {
|
||||||
|
test('allows updating same activity (excluded from overlap)', () async {
|
||||||
|
final repo = MockScheduledActivitiesRepository();
|
||||||
|
when(() => repo.updateActivity(
|
||||||
|
activityId: any(named: 'activityId'),
|
||||||
|
request: any(named: 'request'),
|
||||||
|
)).thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final container = buildContainer(repo);
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
await container
|
||||||
|
.read(scheduledActivitiesControllerProvider.notifier)
|
||||||
|
.updateActivity(
|
||||||
|
deviceId: 'd1',
|
||||||
|
activityId: 'a',
|
||||||
|
weekDay: 1,
|
||||||
|
name: 'English rename',
|
||||||
|
period: '08000900',
|
||||||
|
current: const [_a],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.read(scheduledActivitiesControllerProvider),
|
||||||
|
isA<AsyncData<void>>(),
|
||||||
|
);
|
||||||
|
verify(() => repo.updateActivity(
|
||||||
|
activityId: 'a',
|
||||||
|
request: any(named: 'request'),
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ScheduledActivitiesController.deleteActivity', () {
|
||||||
|
test('deletes via repository on success', () async {
|
||||||
|
final repo = MockScheduledActivitiesRepository();
|
||||||
|
when(() => repo.deleteActivity(activityId: any(named: 'activityId')))
|
||||||
|
.thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final container = buildContainer(repo);
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
await container
|
||||||
|
.read(scheduledActivitiesControllerProvider.notifier)
|
||||||
|
.deleteActivity(deviceId: 'd1', activityId: 'a');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.read(scheduledActivitiesControllerProvider),
|
||||||
|
isA<AsyncData<void>>(),
|
||||||
|
);
|
||||||
|
verify(() => repo.deleteActivity(activityId: 'a')).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exposes AsyncError when repository fails', () async {
|
||||||
|
final repo = MockScheduledActivitiesRepository();
|
||||||
|
when(() => repo.deleteActivity(activityId: any(named: 'activityId')))
|
||||||
|
.thenThrow(const ApiException(message: 'boom', isNetworkError: true));
|
||||||
|
|
||||||
|
final container = buildContainer(repo);
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
await container
|
||||||
|
.read(scheduledActivitiesControllerProvider.notifier)
|
||||||
|
.deleteActivity(deviceId: 'd1', activityId: 'a');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.read(scheduledActivitiesControllerProvider),
|
||||||
|
isA<AsyncError<void>>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -529,6 +529,9 @@
|
|||||||
"scheduledActivityEndTime": "Ende",
|
"scheduledActivityEndTime": "Ende",
|
||||||
"scheduledActivityStartBeforeEnd": "Die Startzeit muss vor der Endzeit liegen",
|
"scheduledActivityStartBeforeEnd": "Die Startzeit muss vor der Endzeit liegen",
|
||||||
"scheduledActivityOverlap": "Überschneidung mit „{name}\" ({time})",
|
"scheduledActivityOverlap": "Überschneidung mit „{name}\" ({time})",
|
||||||
|
"scheduledActivityCreated": "Aktivität erfolgreich erstellt",
|
||||||
|
"scheduledActivityUpdated": "Aktivität erfolgreich aktualisiert",
|
||||||
|
"scheduledActivityDeleted": "Aktivität erfolgreich gelöscht",
|
||||||
"scheduledActivityNewTitle": "Neue Aktivität",
|
"scheduledActivityNewTitle": "Neue Aktivität",
|
||||||
"scheduledActivityEditTitle": "Aktivität bearbeiten",
|
"scheduledActivityEditTitle": "Aktivität bearbeiten",
|
||||||
"scheduledActivityDeleteTitle": "Aktivität löschen",
|
"scheduledActivityDeleteTitle": "Aktivität löschen",
|
||||||
|
|||||||
@@ -726,6 +726,9 @@
|
|||||||
"scheduledActivityEndTime": "End",
|
"scheduledActivityEndTime": "End",
|
||||||
"scheduledActivityStartBeforeEnd": "Start time must be before end time",
|
"scheduledActivityStartBeforeEnd": "Start time must be before end time",
|
||||||
"scheduledActivityOverlap": "Overlaps with \"{name}\" ({time})",
|
"scheduledActivityOverlap": "Overlaps with \"{name}\" ({time})",
|
||||||
|
"scheduledActivityCreated": "Activity created successfully",
|
||||||
|
"scheduledActivityUpdated": "Activity updated successfully",
|
||||||
|
"scheduledActivityDeleted": "Activity deleted successfully",
|
||||||
"scheduledActivityNewTitle": "New activity",
|
"scheduledActivityNewTitle": "New activity",
|
||||||
"scheduledActivityEditTitle": "Edit activity",
|
"scheduledActivityEditTitle": "Edit activity",
|
||||||
"scheduledActivityDeleteTitle": "Delete activity",
|
"scheduledActivityDeleteTitle": "Delete activity",
|
||||||
|
|||||||
@@ -727,6 +727,9 @@
|
|||||||
"scheduledActivityEndTime": "Fin",
|
"scheduledActivityEndTime": "Fin",
|
||||||
"scheduledActivityStartBeforeEnd": "La hora de inicio debe ser anterior a la hora de fin",
|
"scheduledActivityStartBeforeEnd": "La hora de inicio debe ser anterior a la hora de fin",
|
||||||
"scheduledActivityOverlap": "Se superpone con \"{name}\" ({time})",
|
"scheduledActivityOverlap": "Se superpone con \"{name}\" ({time})",
|
||||||
|
"scheduledActivityCreated": "Actividad creada correctamente",
|
||||||
|
"scheduledActivityUpdated": "Actividad actualizada correctamente",
|
||||||
|
"scheduledActivityDeleted": "Actividad eliminada correctamente",
|
||||||
"scheduledActivityNewTitle": "Nueva actividad",
|
"scheduledActivityNewTitle": "Nueva actividad",
|
||||||
"scheduledActivityEditTitle": "Editar actividad",
|
"scheduledActivityEditTitle": "Editar actividad",
|
||||||
"scheduledActivityDeleteTitle": "Eliminar actividad",
|
"scheduledActivityDeleteTitle": "Eliminar actividad",
|
||||||
|
|||||||
@@ -529,6 +529,9 @@
|
|||||||
"scheduledActivityEndTime": "Fin",
|
"scheduledActivityEndTime": "Fin",
|
||||||
"scheduledActivityStartBeforeEnd": "L'heure de début doit être antérieure à l'heure de fin",
|
"scheduledActivityStartBeforeEnd": "L'heure de début doit être antérieure à l'heure de fin",
|
||||||
"scheduledActivityOverlap": "Chevauche « {name} » ({time})",
|
"scheduledActivityOverlap": "Chevauche « {name} » ({time})",
|
||||||
|
"scheduledActivityCreated": "Activité créée avec succès",
|
||||||
|
"scheduledActivityUpdated": "Activité mise à jour avec succès",
|
||||||
|
"scheduledActivityDeleted": "Activité supprimée avec succès",
|
||||||
"scheduledActivityNewTitle": "Nouvelle activité",
|
"scheduledActivityNewTitle": "Nouvelle activité",
|
||||||
"scheduledActivityEditTitle": "Modifier l'activité",
|
"scheduledActivityEditTitle": "Modifier l'activité",
|
||||||
"scheduledActivityDeleteTitle": "Supprimer l'activité",
|
"scheduledActivityDeleteTitle": "Supprimer l'activité",
|
||||||
|
|||||||
@@ -529,6 +529,9 @@
|
|||||||
"scheduledActivityEndTime": "Fine",
|
"scheduledActivityEndTime": "Fine",
|
||||||
"scheduledActivityStartBeforeEnd": "L'orario di inizio deve essere precedente all'orario di fine",
|
"scheduledActivityStartBeforeEnd": "L'orario di inizio deve essere precedente all'orario di fine",
|
||||||
"scheduledActivityOverlap": "Si sovrappone con \"{name}\" ({time})",
|
"scheduledActivityOverlap": "Si sovrappone con \"{name}\" ({time})",
|
||||||
|
"scheduledActivityCreated": "Attività creata con successo",
|
||||||
|
"scheduledActivityUpdated": "Attività aggiornata con successo",
|
||||||
|
"scheduledActivityDeleted": "Attività eliminata con successo",
|
||||||
"scheduledActivityNewTitle": "Nuova attività",
|
"scheduledActivityNewTitle": "Nuova attività",
|
||||||
"scheduledActivityEditTitle": "Modifica attività",
|
"scheduledActivityEditTitle": "Modifica attività",
|
||||||
"scheduledActivityDeleteTitle": "Elimina attività",
|
"scheduledActivityDeleteTitle": "Elimina attività",
|
||||||
|
|||||||
@@ -529,6 +529,9 @@
|
|||||||
"scheduledActivityEndTime": "Fim",
|
"scheduledActivityEndTime": "Fim",
|
||||||
"scheduledActivityStartBeforeEnd": "A hora de início deve ser anterior à hora de fim",
|
"scheduledActivityStartBeforeEnd": "A hora de início deve ser anterior à hora de fim",
|
||||||
"scheduledActivityOverlap": "Sobrepõe-se com \"{name}\" ({time})",
|
"scheduledActivityOverlap": "Sobrepõe-se com \"{name}\" ({time})",
|
||||||
|
"scheduledActivityCreated": "Atividade criada com sucesso",
|
||||||
|
"scheduledActivityUpdated": "Atividade atualizada com sucesso",
|
||||||
|
"scheduledActivityDeleted": "Atividade eliminada com sucesso",
|
||||||
"scheduledActivityNewTitle": "Nova atividade",
|
"scheduledActivityNewTitle": "Nova atividade",
|
||||||
"scheduledActivityEditTitle": "Editar atividade",
|
"scheduledActivityEditTitle": "Editar atividade",
|
||||||
"scheduledActivityDeleteTitle": "Eliminar atividade",
|
"scheduledActivityDeleteTitle": "Eliminar atividade",
|
||||||
|
|||||||
@@ -819,9 +819,12 @@ class I18n {
|
|||||||
static const String scheduledActivityEditTitle = 'scheduledActivityEditTitle';
|
static const String scheduledActivityEditTitle = 'scheduledActivityEditTitle';
|
||||||
static const String scheduledActivityEmpty = 'scheduledActivityEmpty';
|
static const String scheduledActivityEmpty = 'scheduledActivityEmpty';
|
||||||
static const String scheduledActivityEmptyHint = 'scheduledActivityEmptyHint';
|
static const String scheduledActivityEmptyHint = 'scheduledActivityEmptyHint';
|
||||||
|
static const String scheduledActivityCreated = 'scheduledActivityCreated';
|
||||||
|
static const String scheduledActivityDeleted = 'scheduledActivityDeleted';
|
||||||
static const String scheduledActivityEndTime = 'scheduledActivityEndTime';
|
static const String scheduledActivityEndTime = 'scheduledActivityEndTime';
|
||||||
static const String scheduledActivityNewTitle = 'scheduledActivityNewTitle';
|
static const String scheduledActivityNewTitle = 'scheduledActivityNewTitle';
|
||||||
static const String scheduledActivityOverlap = 'scheduledActivityOverlap';
|
static const String scheduledActivityOverlap = 'scheduledActivityOverlap';
|
||||||
|
static const String scheduledActivityUpdated = 'scheduledActivityUpdated';
|
||||||
static const String scheduledActivityStartBeforeEnd = 'scheduledActivityStartBeforeEnd';
|
static const String scheduledActivityStartBeforeEnd = 'scheduledActivityStartBeforeEnd';
|
||||||
static const String scheduledActivityStartTime = 'scheduledActivityStartTime';
|
static const String scheduledActivityStartTime = 'scheduledActivityStartTime';
|
||||||
static const String secretCodeConfigure = 'secretCodeConfigure';
|
static const String secretCodeConfigure = 'secretCodeConfigure';
|
||||||
|
|||||||
Reference in New Issue
Block a user