feat: legacy mobile apps created

This commit is contained in:
Achintha Isuru
2025-12-02 23:51:04 -05:00
parent 850441ca64
commit 8e7753b324
1519 changed files with 0 additions and 16 deletions

View File

@@ -0,0 +1,337 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/common/date_time_extension.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/data/enums/state_status.dart';
import 'package:krow/features/profile/schedule/domain/entities/day_shedule.dart';
import 'package:krow/features/profile/schedule/domain/entities/schedule_slot.dart';
import 'package:krow/features/profile/schedule/domain/staff_schedule_repository.dart';
part 'schedule_event.dart';
part 'schedule_state.dart';
class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
ScheduleBloc() : super(const ScheduleState()) {
on<ScheduleInitEvent>(_onBlocInit);
on<ScheduleEventChangeMode>(_onChangeMode);
on<ScheduleEventSelectDates>(_onSelectDates);
on<ScheduleEventAddSlot>(_onAddSlot);
on<ScheduleEventRemoveSlot>(_onRemoveSlot);
on<ScheduleEventEditSlot>(_onEditSlot);
on<ScheduleEventSave>(_onConfirmChanges);
on<ScheduleEventCancel>(_onCancelChanges);
on<ScheduleEventDeleteSchedule>(_onDeleteSchedule);
}
final StaffScheduleRepository staffRepository =
getIt<StaffScheduleRepository>();
Future<void> _onBlocInit(
ScheduleInitEvent event,
Emitter<ScheduleState> emit,
) async {
emit(state.copyWith(status: StateStatus.loading));
try {
await for (final scheduleData in staffRepository.getStaffSchedule()) {
emit(
state.copyWith(
schedules: scheduleData,
status: StateStatus.idle,
),
);
}
} catch (except) {
log(except.toString());
}
if (state.status == StateStatus.loading) {
emit(state.copyWith(status: StateStatus.idle));
}
}
Future<void> _onSelectDates(
ScheduleEventSelectDates event,
Emitter<ScheduleState> emit,
) async {
if (event.dates.length == 1) {
emit(
state.copyWith(
scheduleErrorText: '',
selectedDates: [event.dates.first],
prevSelectedDates: [],
isInEditMode: false,
),
);
emit(
state.copyWith(
selectedSchedules: await state.findSchedulesForSelection(),
),
);
return;
}
var startDate = event.dates.first;
final endDate = event.dates.last;
emit(
state.copyWith(
scheduleErrorText: '',
selectedDates: [
for (var i = 0; i < endDate.difference(startDate).inDays; i++)
startDate.add(Duration(days: i)),
endDate,
],
prevSelectedDates: [],
isInEditMode: false,
),
);
emit(
state.copyWith(
selectedSchedules: await state.findSchedulesForSelection(),
),
);
}
void _onChangeMode(
ScheduleEventChangeMode event,
Emitter<ScheduleState> emit,
) {
if (event.isInScheduleAddMode != null && state.selectedDates.length == 1) {
emit(state.copyWith(
scheduleErrorText: '',
isScheduleAddMode: event.isInScheduleAddMode,
));
return;
}
if (event.editedDate != null) {
emit(
state.copyWith(
selectedDates: [event.editedDate!],
prevSelectedDates: List.from(state.selectedDates),
),
);
}
emit(
state.copyWith(
isInEditMode: event.isInEditScheduleMode,
scheduleErrorText: '',
isScheduleAddMode: false,
tempSchedules: state.selectedDates.length == 1
? [
state.getScheduleForDate(
date: state.selectedDates.first,
isWeekly: event.isWeekly,
),
]
: [
for (int i = 0; i < state.selectedDates.length; i++)
DaySchedule(
date: state.selectedDates[i],
slots: const [],
),
],
),
);
}
void _onAddSlot(
ScheduleEventAddSlot event,
Emitter<ScheduleState> emit,
) {
emit(
state.copyWith(
tempSchedules: state.tempSchedules.map(
(daySchedule) {
final startTime = daySchedule.getLatestSlot();
return daySchedule.copyWith(
slots: [
...daySchedule.slots,
ScheduleSlot.minFromStartTime(
start: startTime,
),
],
);
},
).toList(),
),
);
}
void _onRemoveSlot(
ScheduleEventRemoveSlot event,
Emitter<ScheduleState> emit,
) {
emit(
state.copyWith(
scheduleErrorText: '',
tempSchedules: state.tempSchedules.map(
(daySchedule) {
return daySchedule.removeSlotAtIndex(event.slotIndex);
},
).toList(),
),
);
}
void _onEditSlot(
ScheduleEventEditSlot event,
Emitter<ScheduleState> emit,
) {
emit(
state.copyWith(
scheduleErrorText: '',
tempSchedules: state.tempSchedules.map(
(daySchedule) {
final daySlots = List<ScheduleSlot>.from(daySchedule.slots);
daySlots[event.slotIndex] = daySlots[event.slotIndex].editTime(
startTime: event.start?.copyWith(
day: daySchedule.date.day,
month: daySchedule.date.month,
),
endTime: event.end?.copyWith(
day: daySchedule.date.day,
month: daySchedule.date.month,
),
);
return daySchedule.copyWith(
slots: daySlots,
);
},
).toList(),
),
);
}
Future<Map<String, DaySchedule>> _handleTemporaryScheduleSave() async {
final previousSchedules = state.getDailySchedulesForTemporary();
if (state.tempSchedules.length == 1) {
return previousSchedules.isNotEmpty
? staffRepository.updateStaffSchedule(
schedules: state.tempSchedules,
)
: staffRepository.createStaffSchedule(
schedules: state.tempSchedules,
);
}
if (previousSchedules.isNotEmpty) {
await staffRepository.deleteStaffSchedule(schedules: previousSchedules);
}
return staffRepository.createStaffSchedule(
schedules: state.tempSchedules,
);
}
Future<void> _onConfirmChanges(
ScheduleEventSave event,
Emitter<ScheduleState> emit,
) async {
emit(
state.copyWith(
status: StateStatus.loading,
selectedDates:
state.prevSelectedDates.isNotEmpty ? state.prevSelectedDates : null,
prevSelectedDates: [],
),
);
final isTempScheduleValid = await state.isValid;
if (!isTempScheduleValid) {
emit(
state.copyWith(
status: StateStatus.error,
scheduleErrorText: 'overlap_error'.tr(),
),
);
return;
}
Map<String, DaySchedule>? schedules;
try {
schedules = await _handleTemporaryScheduleSave();
} catch (except) {
log(except.toString());
}
emit(
state.copyWith(
status: StateStatus.idle,
isInEditMode: false,
schedules: schedules,
tempSchedules: [],
),
);
emit(
state.copyWith(
selectedSchedules: await state.findSchedulesForSelection(),
),
);
}
void _onCancelChanges(
ScheduleEventCancel event,
Emitter<ScheduleState> emit,
) {
emit(
state.copyWith(
isInEditMode: false,
scheduleErrorText: '',
tempSchedules: [],
selectedDates:
state.prevSelectedDates.isNotEmpty ? state.prevSelectedDates : null,
prevSelectedDates: [],
),
);
}
Future<void> _onDeleteSchedule(
ScheduleEventDeleteSchedule event,
Emitter<ScheduleState> emit,
) async {
emit(
state.copyWith(
isInEditMode: false,
status: StateStatus.loading,
),
);
Map<String, DaySchedule>? schedules;
try {
final result = staffRepository.deleteStaffSchedule(
schedules: [event.schedule],
);
schedules = Map<String, DaySchedule>.from(state.schedules);
await result;
schedules.remove(event.schedule.getIdKey());
} catch (except) {
log(except.toString());
}
emit(
state.copyWith(
status: StateStatus.idle,
schedules: schedules,
),
);
emit(
state.copyWith(
selectedSchedules: await state.findSchedulesForSelection(),
),
);
}
}

View File

@@ -0,0 +1,74 @@
part of 'schedule_bloc.dart';
@immutable
sealed class ScheduleEvent {
const ScheduleEvent();
}
class ScheduleInitEvent extends ScheduleEvent {
const ScheduleInitEvent();
}
class ScheduleEventChangeMode extends ScheduleEvent {
const ScheduleEventChangeMode({
this.isInEditScheduleMode,
this.isInScheduleAddMode,
this.editedDate,
this.isWeekly = false,
});
final bool? isInEditScheduleMode;
final bool? isInScheduleAddMode;
final DateTime? editedDate;
final bool isWeekly;
}
class ScheduleEventSelectDates extends ScheduleEvent {
const ScheduleEventSelectDates({required this.dates});
final List<DateTime> dates;
}
class ScheduleEventAddSlot extends ScheduleEvent {
const ScheduleEventAddSlot();
}
class ScheduleEventRemoveSlot extends ScheduleEvent {
const ScheduleEventRemoveSlot({
required this.slot,
required this.slotIndex,
});
final ScheduleSlot slot;
final int slotIndex;
}
class ScheduleEventDeleteSchedule extends ScheduleEvent {
const ScheduleEventDeleteSchedule({
required this.schedule,
});
final DaySchedule schedule;
}
class ScheduleEventEditSlot extends ScheduleEvent {
const ScheduleEventEditSlot({
required this.slot,
required this.slotIndex,
this.start,
this.end,
});
final ScheduleSlot slot;
final int slotIndex;
final DateTime? start;
final DateTime? end;
}
class ScheduleEventSave extends ScheduleEvent {
const ScheduleEventSave();
}
class ScheduleEventCancel extends ScheduleEvent {
const ScheduleEventCancel();
}

View File

@@ -0,0 +1,122 @@
part of 'schedule_bloc.dart';
@immutable
class ScheduleState {
const ScheduleState({
this.schedules = const {},
this.tempSchedules = const [],
this.selectedDates = const [],
this.prevSelectedDates = const [],
this.selectedSchedules = const [],
this.status = StateStatus.idle,
this.isInEditMode = false,
this.isScheduleAddMode = false,
this.scheduleErrorText = '',
});
final Map<String, DaySchedule> schedules;
final List<DaySchedule> tempSchedules;
final List<DateTime> selectedDates;
final List<DateTime> prevSelectedDates;
final List<DaySchedule> selectedSchedules;
final StateStatus status;
final bool isInEditMode;
final bool isScheduleAddMode;
final String scheduleErrorText;
Future<bool> get isValid async {
if (tempSchedules.isEmpty) return false;
bool isValid = true;
for (int i = 0; i < tempSchedules.length; i++) {
isValid = tempSchedules[i].isValid;
if (!isValid) return isValid;
}
return isValid;
}
ScheduleState copyWith({
Map<String, DaySchedule>? schedules,
List<DaySchedule>? tempSchedules,
List<DateTime>? selectedDates,
List<DateTime>? prevSelectedDates,
List<DaySchedule>? selectedSchedules,
StateStatus? status,
bool? isInEditMode,
bool? isScheduleAddMode,
String? scheduleErrorText,
}) {
return ScheduleState(
schedules: schedules ?? this.schedules,
tempSchedules: tempSchedules ?? this.tempSchedules,
selectedDates: selectedDates ?? this.selectedDates,
prevSelectedDates: prevSelectedDates ?? this.prevSelectedDates,
selectedSchedules: selectedSchedules ?? this.selectedSchedules,
status: status ?? this.status,
isInEditMode: isInEditMode ?? this.isInEditMode,
isScheduleAddMode: isScheduleAddMode ?? this.isScheduleAddMode,
scheduleErrorText: scheduleErrorText ?? this.scheduleErrorText,
);
}
DaySchedule? findScheduleForDate(DateTime date) {
return schedules[date.getDayDateId()] ?? schedules[date.getWeekdayId()];
}
bool containsScheduleForDate(DateTime date) {
return schedules.containsKey(date.getDayDateId()) ||
schedules.containsKey(date.getWeekdayId());
}
Future<List<DaySchedule>> findSchedulesForSelection() async {
return [
for (final date in selectedDates)
if (schedules.containsKey(date.getDayDateId()))
schedules[date.getDayDateId()]!
else if (schedules.containsKey(date.getWeekdayId()))
schedules[date.getWeekdayId()]!.copyWith(
date: date,
),
];
}
List<DaySchedule> getDailySchedulesForTemporary() {
return [
for (final day in tempSchedules)
if (schedules.containsKey(day.getIdKey())) schedules[day.getIdKey()]!
];
}
bool isRangeContainsSchedules(List<DateTime> dates) {
return dates.any(
(date) => schedules.containsKey(date.getDayDateId()),
);
}
DaySchedule getScheduleForDate({
required DateTime date,
required bool isWeekly,
}) {
final daySchedule =
schedules[isWeekly ? date.getWeekdayId() : date.getDayDateId()];
return daySchedule ??
DaySchedule(
date: date,
slots: const [],
isWeekly: isWeekly,
);
}
Map<String, DaySchedule> mergeSchedules() {
final schedules = Map<String, DaySchedule>.from(this.schedules);
for (final schedule in tempSchedules) {
schedules[schedule.getIdKey()] = schedule;
}
return schedules;
}
}

View File

@@ -0,0 +1,75 @@
import 'package:flutter/foundation.dart' show immutable;
import 'package:krow/core/application/common/date_time_extension.dart';
import 'package:krow/features/profile/schedule/domain/entities/schedule_slot.dart';
@immutable
class DaySchedule {
const DaySchedule({
required this.date,
required this.slots,
this.isWeekly = false,
this.deletedSlots,
});
final DateTime date;
final List<ScheduleSlot> slots;
final bool isWeekly;
final List<ScheduleSlot>? deletedSlots;
bool get isValid {
bool isValid = true;
for (int i = 0; i < slots.length; i++) {
for (int k = i; k < slots.length; k++) {
if (k == i) continue; // Skip validation for same slot
isValid = slots[i].isNotOverlappingWith(slots[k]);
if (!isValid) return isValid;
}
}
return isValid;
}
bool get isViableForSaving {
return slots.isNotEmpty || deletedSlots != null;
}
DaySchedule copyWith({
DateTime? date,
List<ScheduleSlot>? slots,
bool? isWeekly,
List<ScheduleSlot>? deletedSlots,
}) {
return DaySchedule(
date: date ?? this.date,
slots: slots ?? this.slots,
isWeekly: isWeekly ?? this.isWeekly,
deletedSlots: deletedSlots ?? this.deletedSlots,
);
}
String getIdKey() => isWeekly ? date.getWeekdayId() : date.getDayDateId();
DateTime getLatestSlot() {
if (slots.isEmpty) return date.copyWith(hour: 8, minute: 0);
var lastSlot = slots.first;
for (int i = 0; i < slots.length; i++) {
if (lastSlot.endTime.isBefore(slots[i].endTime)) {
lastSlot = slots[i];
}
}
return lastSlot.endTime;
}
DaySchedule removeSlotAtIndex(int slotIndex) {
return copyWith(
deletedSlots: slots[slotIndex].remoteId != null
? [...?deletedSlots, slots[slotIndex]]
: null,
slots: List.from(slots)..removeAt(slotIndex),
);
}
}

View File

@@ -0,0 +1,120 @@
import 'package:flutter/foundation.dart' show immutable;
@immutable
class ScheduleSlot {
const ScheduleSlot({
required this.id,
required this.startTime,
required this.endTime,
this.remoteId,
});
factory ScheduleSlot.minFromStartTime({required DateTime start}) {
if (start.hour > 19) start = start.copyWith(hour: 8);
return ScheduleSlot(
id: start.millisecondsSinceEpoch.toString(),
startTime: start.copyWith(minute: 0, second: 0),
endTime: start.copyWith(minute: 0, second: 0).add(
const Duration(hours: 5),
),
);
}
final String id;
final DateTime startTime;
final DateTime endTime;
final String? remoteId;
ScheduleSlot copyWith({
String? id,
DateTime? startTime,
DateTime? endTime,
String? remoteId,
}) {
return ScheduleSlot(
id: id ?? this.id,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
remoteId: remoteId ?? this.remoteId,
);
}
ScheduleSlot editTime({
DateTime? startTime,
DateTime? endTime,
}) {
ScheduleSlot? slot;
if (endTime != null) {
slot = copyWith(
startTime: endTime.difference(this.startTime).inHours.abs() < 5 ||
endTime.isBefore(this.startTime)
? endTime.subtract(const Duration(hours: 5))
: this.startTime,
endTime: endTime,
);
if (slot.startTime.day != slot.endTime.day) {
final DateTime midnight = DateTime(
endTime.year,
endTime.month,
endTime.day,
0,
0,
);
slot = copyWith(
startTime: midnight,
endTime: midnight.add(const Duration(hours: 5)),
);
}
return slot;
} else if (startTime == null) {
slot = this;
} else {
slot = copyWith(
startTime: startTime,
endTime: startTime.difference(this.endTime).inHours.abs() < 5 ||
startTime.isAfter(this.endTime)
? startTime.add(const Duration(hours: 5))
: this.endTime,
);
if (slot.startTime.day != slot.endTime.day) {
final DateTime midnight = DateTime(
startTime.year,
startTime.month,
startTime.day + 1,
0,
0,
);
slot = copyWith(
startTime: midnight.subtract(const Duration(hours: 5)),
endTime: midnight,
);
}
}
return slot;
}
bool isNotOverlappingWith(ScheduleSlot other) {
// End and start time should be before other slot's start time
if (((endTime.isBefore(other.startTime) ||
endTime.isAtSameMomentAs(other.startTime)) &&
startTime.isBefore(other.startTime))) {
return true;
}
// Or start and end time should be after other slot's end time
return ((startTime.isAfter(other.endTime) ||
startTime.isAtSameMomentAs(other.endTime)) &&
endTime.isAfter(other.endTime));
}
@override
String toString() {
return 'Slot data: ID $id\n'
'Start time: $startTime\n'
'End time: $endTime';
}
}

View File

@@ -0,0 +1,17 @@
import 'package:krow/features/profile/schedule/domain/entities/day_shedule.dart';
abstract class StaffScheduleRepository {
Stream<Map<String, DaySchedule>> getStaffSchedule();
Future<Map<String, DaySchedule>> createStaffSchedule({
required List<DaySchedule> schedules,
});
Future<Map<String, DaySchedule>> updateStaffSchedule({
required List<DaySchedule> schedules,
});
Future<DaySchedule?> deleteStaffSchedule({
required List<DaySchedule> schedules,
});
}