Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/home_banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 13 additions & 2 deletions lib/presentation/home/bloc/schedule_timer_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ part 'schedule_timer_state.dart';

class ScheduleTimerBloc extends Bloc<ScheduleTimerEvent, ScheduleTimerState> {
StreamSubscription<DateTime>? _tickerSubscription;
Timer? _initialTimer;
DateTime? _scheduleTime;

ScheduleTimerBloc() : super(const ScheduleTimerInitial()) {
Expand All @@ -21,13 +22,15 @@ class ScheduleTimerBloc extends Bloc<ScheduleTimerEvent, ScheduleTimerState> {
@override
Future<void> close() {
_tickerSubscription?.cancel();
_initialTimer?.cancel();
return super.close();
}

void _onTimerStarted(
ScheduleTimerStarted event, Emitter<ScheduleTimerState> emit) {
_scheduleTime = event.scheduleTime;
_tickerSubscription?.cancel();
_initialTimer?.cancel();

// Calculate initial time difference
final now = DateTime.now();
Expand All @@ -48,7 +51,10 @@ class ScheduleTimerBloc extends Bloc<ScheduleTimerEvent, ScheduleTimerState> {
final secondsUntilNextMinute = 60 - now.second;

// Create an initial timer to sync with minute boundaries
Timer(Duration(seconds: secondsUntilNextMinute), () {
_initialTimer = Timer(Duration(seconds: secondsUntilNextMinute), () {
// Check if bloc is still active before adding events
if (isClosed) return;

// After the initial sync, start the regular minute timer
add(ScheduleTimerTicked(DateTime.now()));

Expand All @@ -57,7 +63,10 @@ class ScheduleTimerBloc extends Bloc<ScheduleTimerEvent, ScheduleTimerState> {
const Duration(minutes: 1),
(_) => DateTime.now(),
).listen((currentTime) {
add(ScheduleTimerTicked(currentTime));
// Check if bloc is still active before adding events
if (!isClosed) {
add(ScheduleTimerTicked(currentTime));
}
});
});
}
Expand All @@ -83,6 +92,7 @@ class ScheduleTimerBloc extends Bloc<ScheduleTimerEvent, ScheduleTimerState> {
void _onTimerStopped(
ScheduleTimerStopped event, Emitter<ScheduleTimerState> emit) {
_tickerSubscription?.cancel();
_initialTimer?.cancel();
_scheduleTime = null;
emit(const ScheduleTimerInitial());
}
Expand All @@ -91,6 +101,7 @@ class ScheduleTimerBloc extends Bloc<ScheduleTimerEvent, ScheduleTimerState> {
ScheduleTimerUpdated event, Emitter<ScheduleTimerState> emit) {
if (event.scheduleTime == null) {
_tickerSubscription?.cancel();
_initialTimer?.cancel();
_scheduleTime = null;
emit(const ScheduleTimerInitial());
} else {
Expand Down
122 changes: 122 additions & 0 deletions lib/presentation/home/components/month_calendar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:on_time_front/presentation/calendar/bloc/monthly_schedules_bloc.dart';
import 'package:on_time_front/presentation/shared/components/calendar/centered_calendar_header.dart';
import 'package:on_time_front/presentation/shared/theme/calendar_theme.dart';
import 'package:table_calendar/table_calendar.dart';

class MonthCalendar extends StatefulWidget {
const MonthCalendar({
super.key,
required this.monthlySchedulesState,
this.dispatchBlocEvents = true,
});

final MonthlySchedulesState monthlySchedulesState;
final bool dispatchBlocEvents;

@override
State<MonthCalendar> createState() => _MonthCalendarState();
}

class _MonthCalendarState extends State<MonthCalendar> {
late DateTime _focusedDay;

@override
void initState() {
super.initState();
_focusedDay = DateTime.now();
}

void _onLeftArrowTap() {
final DateTime nextFocusedDay =
DateTime(_focusedDay.year, _focusedDay.month - 1, 1);
setState(() {
_focusedDay = nextFocusedDay;
});
if (widget.dispatchBlocEvents) {
context.read<MonthlySchedulesBloc>().add(MonthlySchedulesMonthAdded(
date: DateTime(nextFocusedDay.year, nextFocusedDay.month, 1)));
}
}

void _onRightArrowTap() {
final DateTime nextFocusedDay =
DateTime(_focusedDay.year, _focusedDay.month + 1, 1);
setState(() {
_focusedDay = nextFocusedDay;
});
if (widget.dispatchBlocEvents) {
context.read<MonthlySchedulesBloc>().add(MonthlySchedulesMonthAdded(
date: DateTime(nextFocusedDay.year, nextFocusedDay.month, 1)));
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final calendarTheme = theme.extension<CalendarTheme>()!;

return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(11),
),
child: TableCalendar(
locale: Localizations.localeOf(context).toString(),
eventLoader: (day) {
day = DateTime(day.year, day.month, day.day);
return widget.monthlySchedulesState.schedules[day] ?? [];
},
sixWeekMonthsEnforced: true,
rowHeight: 50,
availableGestures: AvailableGestures.none,
focusedDay: _focusedDay,
firstDay: DateTime(2024, 1, 1),
lastDay: DateTime(2025, 12, 31),
calendarFormat: CalendarFormat.month,
headerStyle: calendarTheme.headerStyle,
daysOfWeekStyle: calendarTheme.daysOfWeekStyle,
daysOfWeekHeight: 40,
calendarStyle: calendarTheme.calendarStyle,
onDaySelected: (selectedDay, focusedDay) {
// Handle day selection if needed
},
onPageChanged: (focusedDay) {
setState(() {
_focusedDay = focusedDay;
});
if (widget.dispatchBlocEvents) {
context.read<MonthlySchedulesBloc>().add(MonthlySchedulesMonthAdded(
date: DateTime(
focusedDay.year, focusedDay.month, focusedDay.day)));
}
},
calendarBuilders: CalendarBuilders(
headerTitleBuilder: (context, date) {
return CenteredCalendarHeader(
focusedMonth: date,
onLeftArrowTap: _onLeftArrowTap,
onRightArrowTap: _onRightArrowTap,
titleTextStyle: calendarTheme.headerStyle.titleTextStyle,
leftIcon: calendarTheme.headerStyle.leftChevronIcon,
rightIcon: calendarTheme.headerStyle.rightChevronIcon,
);
},
todayBuilder: (context, day, focusedDay) => Container(
margin: const EdgeInsets.all(4.0),
alignment: Alignment.center,
decoration: calendarTheme.todayDecoration,
child: Text(
day.day.toString(),
style: textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onPrimary,
),
),
),
),
),
);
}
}
Loading
Loading