import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/activity.dart'; import 'package:island/pods/network.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:table_calendar/table_calendar.dart'; part 'event_calendar.g.dart'; part 'event_calendar.freezed.dart'; @freezed abstract class EventCalendarQuery with _$EventCalendarQuery { const factory EventCalendarQuery({ required String? uname, required int year, required int month, }) = _EventCalendarQuery; } @riverpod Future> accountEventCalendar( Ref ref, EventCalendarQuery query, ) async { final client = ref.watch(apiClientProvider); final resp = await client.get('/accounts/${query.uname ?? 'me'}/calendar'); return resp.data .map((e) => SnEventCalendarEntry.fromJson(e)) .cast() .toList(); } @RoutePage() class MyselfEventCalendarScreen extends HookConsumerWidget { const MyselfEventCalendarScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final selectedMonth = useState(DateTime.now().month); final selectedYear = useState(DateTime.now().year); final selectedDay = useState(DateTime.now()); final events = ref.watch( accountEventCalendarProvider( EventCalendarQuery( uname: 'me', year: selectedYear.value, month: selectedMonth.value, ), ), ); return AppScaffold( appBar: AppBar( leading: const PageBackButton(), title: Text('eventCalander').tr(), ), body: SingleChildScrollView( child: Column( children: [ TableCalendar( locale: EasyLocalization.of(context)!.locale.toString(), firstDay: DateTime.now().add(Duration(days: -3650)), lastDay: DateTime.now().add(Duration(days: 3650)), focusedDay: DateTime.utc( selectedYear.value, selectedMonth.value, DateTime.now().day, ), calendarFormat: CalendarFormat.month, selectedDayPredicate: (day) { return isSameDay(selectedDay.value, day); }, onDaySelected: (value, _) { selectedDay.value = value; }, onPageChanged: (focusedDay) { selectedMonth.value = focusedDay.month; selectedYear.value = focusedDay.year; }, eventLoader: (day) { return events.value ?.where((e) => isSameDay(e.date, day)) .expand((e) => [...e.statuses, e.checkInResult]) .where((e) => e != null) .toList() ?? []; }, calendarBuilders: CalendarBuilders( dowBuilder: (context, day) { final text = DateFormat.EEEEE().format(day); return Center(child: Text(text)); }, markerBuilder: (context, day, events) { var checkInResult = events.whereType().firstOrNull; if (checkInResult != null) { return Positioned( top: 32, child: Text( ['大凶', '凶', '中平', '吉', '大吉'][checkInResult.level], style: TextStyle( fontSize: 9, color: isSameDay(selectedDay.value, day) ? Theme.of( context, ).colorScheme.onPrimaryContainer : isSameDay(DateTime.now(), day) ? Theme.of( context, ).colorScheme.onSecondaryContainer : Theme.of(context).colorScheme.onSurface, ), ), ); } return null; }, ), ), const Divider(height: 1).padding(top: 8), AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: Builder( builder: (context) { final event = events.value ?.where((e) => isSameDay(e.date, selectedDay.value)) .firstOrNull; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text(DateFormat.EEEE().format(selectedDay.value)) .fontSize(16) .bold() .textColor( Theme.of(context).colorScheme.onSecondaryContainer, ), Text(DateFormat.yMd().format(selectedDay.value)) .fontSize(12) .textColor( Theme.of(context).colorScheme.onSecondaryContainer, ), const Gap(16), if (event?.checkInResult != null) Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'checkInResultLevel${event!.checkInResult!.level}', ).tr().fontSize(16).bold(), for (final tip in event.checkInResult!.tips) Row( crossAxisAlignment: CrossAxisAlignment.start, spacing: 8, children: [ Icon( Symbols.circle, size: 12, fill: 1, ).padding(top: 4, right: 4), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(tip.title).bold(), Text(tip.content), ], ), ), ], ).padding(top: 8), ], ), if (event?.checkInResult == null && (event?.statuses.isEmpty ?? true)) Text('eventCalanderEmpty').tr(), ], ).padding(vertical: 24, horizontal: 24); }, ), ), ], ), ), ); } }