From 50fb25ef12a9698fddd6d6417e8d3c2e05e465ee Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 9 May 2025 01:40:56 +0800 Subject: [PATCH] :sparkles: Event Calander --- assets/i18n/en-US.json | 4 +- lib/models/activity.dart | 12 ++ lib/models/activity.freezed.dart | 169 +++++++++++++++++++ lib/models/activity.g.dart | 21 +++ lib/route.dart | 4 + lib/route.gr.dart | 118 ++++++------- lib/screens/account/me/event_calendar.dart | 167 +++++++++++++++++- lib/screens/account/me/event_calendar.g.dart | 31 ++++ lib/widgets/check_in.dart | 23 ++- pubspec.lock | 32 +++- pubspec.yaml | 1 + 11 files changed, 508 insertions(+), 74 deletions(-) create mode 100644 lib/screens/account/me/event_calendar.g.dart diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index b5dc536..2890874 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -113,5 +113,7 @@ "checkInResultLevel1": "Bad Luck", "checkInResultLevel2": "A Normal Day", "checkInResultLevel3": "Good Luck", - "checkInResultLevel4": "Best Luck" + "checkInResultLevel4": "Best Luck", + "eventCalander": "Event Calander", + "eventCalanderEmpty": "No events on that day." } diff --git a/lib/models/activity.dart b/lib/models/activity.dart index da8a34c..d93a461 100644 --- a/lib/models/activity.dart +++ b/lib/models/activity.dart @@ -51,3 +51,15 @@ abstract class SnFortuneTip with _$SnFortuneTip { factory SnFortuneTip.fromJson(Map json) => _$SnFortuneTipFromJson(json); } + +@freezed +abstract class SnEventCalendarEntry with _$SnEventCalendarEntry { + const factory SnEventCalendarEntry({ + required DateTime date, + required SnCheckInResult? checkInResult, + required List statuses, + }) = _SnEventCalendarEntry; + + factory SnEventCalendarEntry.fromJson(Map json) => + _$SnEventCalendarEntryFromJson(json); +} diff --git a/lib/models/activity.freezed.dart b/lib/models/activity.freezed.dart index 7465008..518a8f7 100644 --- a/lib/models/activity.freezed.dart +++ b/lib/models/activity.freezed.dart @@ -513,4 +513,173 @@ as String, } + +/// @nodoc +mixin _$SnEventCalendarEntry { + + DateTime get date; SnCheckInResult? get checkInResult; List get statuses; +/// Create a copy of SnEventCalendarEntry +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnEventCalendarEntryCopyWith get copyWith => _$SnEventCalendarEntryCopyWithImpl(this as SnEventCalendarEntry, _$identity); + + /// Serializes this SnEventCalendarEntry to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEventCalendarEntry&&(identical(other.date, date) || other.date == date)&&(identical(other.checkInResult, checkInResult) || other.checkInResult == checkInResult)&&const DeepCollectionEquality().equals(other.statuses, statuses)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,date,checkInResult,const DeepCollectionEquality().hash(statuses)); + +@override +String toString() { + return 'SnEventCalendarEntry(date: $date, checkInResult: $checkInResult, statuses: $statuses)'; +} + + +} + +/// @nodoc +abstract mixin class $SnEventCalendarEntryCopyWith<$Res> { + factory $SnEventCalendarEntryCopyWith(SnEventCalendarEntry value, $Res Function(SnEventCalendarEntry) _then) = _$SnEventCalendarEntryCopyWithImpl; +@useResult +$Res call({ + DateTime date, SnCheckInResult? checkInResult, List statuses +}); + + +$SnCheckInResultCopyWith<$Res>? get checkInResult; + +} +/// @nodoc +class _$SnEventCalendarEntryCopyWithImpl<$Res> + implements $SnEventCalendarEntryCopyWith<$Res> { + _$SnEventCalendarEntryCopyWithImpl(this._self, this._then); + + final SnEventCalendarEntry _self; + final $Res Function(SnEventCalendarEntry) _then; + +/// Create a copy of SnEventCalendarEntry +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? checkInResult = freezed,Object? statuses = null,}) { + return _then(_self.copyWith( +date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable +as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable +as SnCheckInResult?,statuses: null == statuses ? _self.statuses : statuses // ignore: cast_nullable_to_non_nullable +as List, + )); +} +/// Create a copy of SnEventCalendarEntry +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnCheckInResultCopyWith<$Res>? get checkInResult { + if (_self.checkInResult == null) { + return null; + } + + return $SnCheckInResultCopyWith<$Res>(_self.checkInResult!, (value) { + return _then(_self.copyWith(checkInResult: value)); + }); +} +} + + +/// @nodoc +@JsonSerializable() + +class _SnEventCalendarEntry implements SnEventCalendarEntry { + const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List statuses}): _statuses = statuses; + factory _SnEventCalendarEntry.fromJson(Map json) => _$SnEventCalendarEntryFromJson(json); + +@override final DateTime date; +@override final SnCheckInResult? checkInResult; + final List _statuses; +@override List get statuses { + if (_statuses is EqualUnmodifiableListView) return _statuses; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_statuses); +} + + +/// Create a copy of SnEventCalendarEntry +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnEventCalendarEntryCopyWith<_SnEventCalendarEntry> get copyWith => __$SnEventCalendarEntryCopyWithImpl<_SnEventCalendarEntry>(this, _$identity); + +@override +Map toJson() { + return _$SnEventCalendarEntryToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEventCalendarEntry&&(identical(other.date, date) || other.date == date)&&(identical(other.checkInResult, checkInResult) || other.checkInResult == checkInResult)&&const DeepCollectionEquality().equals(other._statuses, _statuses)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,date,checkInResult,const DeepCollectionEquality().hash(_statuses)); + +@override +String toString() { + return 'SnEventCalendarEntry(date: $date, checkInResult: $checkInResult, statuses: $statuses)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnEventCalendarEntryCopyWith<$Res> implements $SnEventCalendarEntryCopyWith<$Res> { + factory _$SnEventCalendarEntryCopyWith(_SnEventCalendarEntry value, $Res Function(_SnEventCalendarEntry) _then) = __$SnEventCalendarEntryCopyWithImpl; +@override @useResult +$Res call({ + DateTime date, SnCheckInResult? checkInResult, List statuses +}); + + +@override $SnCheckInResultCopyWith<$Res>? get checkInResult; + +} +/// @nodoc +class __$SnEventCalendarEntryCopyWithImpl<$Res> + implements _$SnEventCalendarEntryCopyWith<$Res> { + __$SnEventCalendarEntryCopyWithImpl(this._self, this._then); + + final _SnEventCalendarEntry _self; + final $Res Function(_SnEventCalendarEntry) _then; + +/// Create a copy of SnEventCalendarEntry +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? date = null,Object? checkInResult = freezed,Object? statuses = null,}) { + return _then(_SnEventCalendarEntry( +date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable +as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable +as SnCheckInResult?,statuses: null == statuses ? _self._statuses : statuses // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +/// Create a copy of SnEventCalendarEntry +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnCheckInResultCopyWith<$Res>? get checkInResult { + if (_self.checkInResult == null) { + return null; + } + + return $SnCheckInResultCopyWith<$Res>(_self.checkInResult!, (value) { + return _then(_self.copyWith(checkInResult: value)); + }); +} +} + // dart format on diff --git a/lib/models/activity.g.dart b/lib/models/activity.g.dart index c0786df..82d69df 100644 --- a/lib/models/activity.g.dart +++ b/lib/models/activity.g.dart @@ -79,3 +79,24 @@ Map _$SnFortuneTipToJson(_SnFortuneTip instance) => 'title': instance.title, 'content': instance.content, }; + +_SnEventCalendarEntry _$SnEventCalendarEntryFromJson( + Map json, +) => _SnEventCalendarEntry( + date: DateTime.parse(json['date'] as String), + checkInResult: + json['check_in_result'] == null + ? null + : SnCheckInResult.fromJson( + json['check_in_result'] as Map, + ), + statuses: json['statuses'] as List, +); + +Map _$SnEventCalendarEntryToJson( + _SnEventCalendarEntry instance, +) => { + 'date': instance.date.toIso8601String(), + 'check_in_result': instance.checkInResult?.toJson(), + 'statuses': instance.statuses, +}; diff --git a/lib/route.dart b/lib/route.dart index ebb236d..1d268d0 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -22,6 +22,10 @@ class AppRouter extends RootStackRouter { AutoRoute(page: LoginRoute.page, path: '/auth/login'), AutoRoute(page: CreateAccountRoute.page, path: '/auth/create-account'), AutoRoute(page: MyselfProfileRoute.page, path: '/account/me'), + AutoRoute( + page: MyselfEventCalendarRoute.page, + path: '/account/me/calendar', + ), AutoRoute(page: UpdateProfileRoute.page, path: '/account/me/update'), AutoRoute(page: ManagedPublisherRoute.page, path: '/account/me/publishers'), AutoRoute(page: NewPublisherRoute.page, path: '/account/me/publishers/new'), diff --git a/lib/route.gr.dart b/lib/route.gr.dart index 0c82255..324ee94 100644 --- a/lib/route.gr.dart +++ b/lib/route.gr.dart @@ -12,42 +12,26 @@ import 'package:auto_route/auto_route.dart' as _i18; import 'package:flutter/material.dart' as _i19; import 'package:island/models/post.dart' as _i20; -import 'package:island/screens/account.dart' as _i3; +import 'package:island/screens/account.dart' as _i2; import 'package:island/screens/account/me.dart' as _i12; -import 'package:island/screens/account/me/event_calendar.dart' as _i1; -import 'package:island/screens/account/me/publishers.dart' as _i8; +import 'package:island/screens/account/me/event_calendar.dart' as _i11; +import 'package:island/screens/account/me/publishers.dart' as _i7; import 'package:island/screens/account/me/update.dart' as _i17; -import 'package:island/screens/account/profile.dart' as _i2; -import 'package:island/screens/auth/create_account.dart' as _i7; -import 'package:island/screens/auth/login.dart' as _i11; +import 'package:island/screens/account/profile.dart' as _i1; +import 'package:island/screens/auth/create_account.dart' as _i6; +import 'package:island/screens/auth/login.dart' as _i10; import 'package:island/screens/auth/tabs.dart' as _i16; -import 'package:island/screens/chat/chat.dart' as _i5; -import 'package:island/screens/chat/room.dart' as _i6; -import 'package:island/screens/chat/room_detail.dart' as _i4; -import 'package:island/screens/explore.dart' as _i10; +import 'package:island/screens/chat/chat.dart' as _i4; +import 'package:island/screens/chat/room.dart' as _i5; +import 'package:island/screens/chat/room_detail.dart' as _i3; +import 'package:island/screens/explore.dart' as _i9; import 'package:island/screens/posts/compose.dart' as _i13; import 'package:island/screens/posts/detail.dart' as _i14; import 'package:island/screens/realm/detail.dart' as _i15; -import 'package:island/screens/realm/realms.dart' as _i9; +import 'package:island/screens/realm/realms.dart' as _i8; /// generated route for -/// [_i1.AccountEventCalendar] -class AccountEventCalendar extends _i18.PageRouteInfo { - const AccountEventCalendar({List<_i18.PageRouteInfo>? children}) - : super(AccountEventCalendar.name, initialChildren: children); - - static const String name = 'AccountEventCalendar'; - - static _i18.PageInfo page = _i18.PageInfo( - name, - builder: (data) { - return const _i1.AccountEventCalendar(); - }, - ); -} - -/// generated route for -/// [_i2.AccountProfileScreen] +/// [_i1.AccountProfileScreen] class AccountProfileRoute extends _i18.PageRouteInfo { AccountProfileRoute({ _i19.Key? key, @@ -70,7 +54,7 @@ class AccountProfileRoute extends _i18.PageRouteInfo { orElse: () => AccountProfileRouteArgs(name: pathParams.getString('name')), ); - return _i2.AccountProfileScreen(key: args.key, name: args.name); + return _i1.AccountProfileScreen(key: args.key, name: args.name); }, ); } @@ -89,7 +73,7 @@ class AccountProfileRouteArgs { } /// generated route for -/// [_i3.AccountScreen] +/// [_i2.AccountScreen] class AccountRoute extends _i18.PageRouteInfo { const AccountRoute({List<_i18.PageRouteInfo>? children}) : super(AccountRoute.name, initialChildren: children); @@ -99,13 +83,13 @@ class AccountRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i3.AccountScreen(); + return const _i2.AccountScreen(); }, ); } /// generated route for -/// [_i4.ChatDetailScreen] +/// [_i3.ChatDetailScreen] class ChatDetailRoute extends _i18.PageRouteInfo { ChatDetailRoute({ _i19.Key? key, @@ -127,7 +111,7 @@ class ChatDetailRoute extends _i18.PageRouteInfo { final args = data.argsAs( orElse: () => ChatDetailRouteArgs(id: pathParams.getInt('id')), ); - return _i4.ChatDetailScreen(key: args.key, id: args.id); + return _i3.ChatDetailScreen(key: args.key, id: args.id); }, ); } @@ -146,7 +130,7 @@ class ChatDetailRouteArgs { } /// generated route for -/// [_i5.ChatListScreen] +/// [_i4.ChatListScreen] class ChatListRoute extends _i18.PageRouteInfo { const ChatListRoute({List<_i18.PageRouteInfo>? children}) : super(ChatListRoute.name, initialChildren: children); @@ -156,13 +140,13 @@ class ChatListRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i5.ChatListScreen(); + return const _i4.ChatListScreen(); }, ); } /// generated route for -/// [_i6.ChatRoomScreen] +/// [_i5.ChatRoomScreen] class ChatRoomRoute extends _i18.PageRouteInfo { ChatRoomRoute({ _i19.Key? key, @@ -184,7 +168,7 @@ class ChatRoomRoute extends _i18.PageRouteInfo { final args = data.argsAs( orElse: () => ChatRoomRouteArgs(id: pathParams.getInt('id')), ); - return _i6.ChatRoomScreen(key: args.key, id: args.id); + return _i5.ChatRoomScreen(key: args.key, id: args.id); }, ); } @@ -203,7 +187,7 @@ class ChatRoomRouteArgs { } /// generated route for -/// [_i7.CreateAccountScreen] +/// [_i6.CreateAccountScreen] class CreateAccountRoute extends _i18.PageRouteInfo { const CreateAccountRoute({List<_i18.PageRouteInfo>? children}) : super(CreateAccountRoute.name, initialChildren: children); @@ -213,13 +197,13 @@ class CreateAccountRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i7.CreateAccountScreen(); + return const _i6.CreateAccountScreen(); }, ); } /// generated route for -/// [_i5.EditChatScreen] +/// [_i4.EditChatScreen] class EditChatRoute extends _i18.PageRouteInfo { EditChatRoute({_i19.Key? key, int? id, List<_i18.PageRouteInfo>? children}) : super( @@ -238,7 +222,7 @@ class EditChatRoute extends _i18.PageRouteInfo { final args = data.argsAs( orElse: () => EditChatRouteArgs(id: pathParams.optInt('id')), ); - return _i5.EditChatScreen(key: args.key, id: args.id); + return _i4.EditChatScreen(key: args.key, id: args.id); }, ); } @@ -257,7 +241,7 @@ class EditChatRouteArgs { } /// generated route for -/// [_i8.EditPublisherScreen] +/// [_i7.EditPublisherScreen] class EditPublisherRoute extends _i18.PageRouteInfo { EditPublisherRoute({ _i19.Key? key, @@ -279,7 +263,7 @@ class EditPublisherRoute extends _i18.PageRouteInfo { final args = data.argsAs( orElse: () => EditPublisherRouteArgs(name: pathParams.optString('id')), ); - return _i8.EditPublisherScreen(key: args.key, name: args.name); + return _i7.EditPublisherScreen(key: args.key, name: args.name); }, ); } @@ -298,7 +282,7 @@ class EditPublisherRouteArgs { } /// generated route for -/// [_i9.EditRealmScreen] +/// [_i8.EditRealmScreen] class EditRealmRoute extends _i18.PageRouteInfo { EditRealmRoute({ _i19.Key? key, @@ -320,7 +304,7 @@ class EditRealmRoute extends _i18.PageRouteInfo { final args = data.argsAs( orElse: () => EditRealmRouteArgs(slug: pathParams.optString('slug')), ); - return _i9.EditRealmScreen(key: args.key, slug: args.slug); + return _i8.EditRealmScreen(key: args.key, slug: args.slug); }, ); } @@ -339,7 +323,7 @@ class EditRealmRouteArgs { } /// generated route for -/// [_i10.ExploreScreen] +/// [_i9.ExploreScreen] class ExploreRoute extends _i18.PageRouteInfo { const ExploreRoute({List<_i18.PageRouteInfo>? children}) : super(ExploreRoute.name, initialChildren: children); @@ -349,13 +333,13 @@ class ExploreRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i10.ExploreScreen(); + return const _i9.ExploreScreen(); }, ); } /// generated route for -/// [_i11.LoginScreen] +/// [_i10.LoginScreen] class LoginRoute extends _i18.PageRouteInfo { const LoginRoute({List<_i18.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); @@ -365,13 +349,13 @@ class LoginRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i11.LoginScreen(); + return const _i10.LoginScreen(); }, ); } /// generated route for -/// [_i8.ManagedPublisherScreen] +/// [_i7.ManagedPublisherScreen] class ManagedPublisherRoute extends _i18.PageRouteInfo { const ManagedPublisherRoute({List<_i18.PageRouteInfo>? children}) : super(ManagedPublisherRoute.name, initialChildren: children); @@ -381,7 +365,23 @@ class ManagedPublisherRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i8.ManagedPublisherScreen(); + return const _i7.ManagedPublisherScreen(); + }, + ); +} + +/// generated route for +/// [_i11.MyselfEventCalendarScreen] +class MyselfEventCalendarRoute extends _i18.PageRouteInfo { + const MyselfEventCalendarRoute({List<_i18.PageRouteInfo>? children}) + : super(MyselfEventCalendarRoute.name, initialChildren: children); + + static const String name = 'MyselfEventCalendarRoute'; + + static _i18.PageInfo page = _i18.PageInfo( + name, + builder: (data) { + return const _i11.MyselfEventCalendarScreen(); }, ); } @@ -403,7 +403,7 @@ class MyselfProfileRoute extends _i18.PageRouteInfo { } /// generated route for -/// [_i5.NewChatScreen] +/// [_i4.NewChatScreen] class NewChatRoute extends _i18.PageRouteInfo { const NewChatRoute({List<_i18.PageRouteInfo>? children}) : super(NewChatRoute.name, initialChildren: children); @@ -413,13 +413,13 @@ class NewChatRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i5.NewChatScreen(); + return const _i4.NewChatScreen(); }, ); } /// generated route for -/// [_i8.NewPublisherScreen] +/// [_i7.NewPublisherScreen] class NewPublisherRoute extends _i18.PageRouteInfo { const NewPublisherRoute({List<_i18.PageRouteInfo>? children}) : super(NewPublisherRoute.name, initialChildren: children); @@ -429,13 +429,13 @@ class NewPublisherRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i8.NewPublisherScreen(); + return const _i7.NewPublisherScreen(); }, ); } /// generated route for -/// [_i9.NewRealmScreen] +/// [_i8.NewRealmScreen] class NewRealmRoute extends _i18.PageRouteInfo { const NewRealmRoute({List<_i18.PageRouteInfo>? children}) : super(NewRealmRoute.name, initialChildren: children); @@ -445,7 +445,7 @@ class NewRealmRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i9.NewRealmScreen(); + return const _i8.NewRealmScreen(); }, ); } @@ -616,7 +616,7 @@ class RealmDetailRouteArgs { } /// generated route for -/// [_i9.RealmListScreen] +/// [_i8.RealmListScreen] class RealmListRoute extends _i18.PageRouteInfo { const RealmListRoute({List<_i18.PageRouteInfo>? children}) : super(RealmListRoute.name, initialChildren: children); @@ -626,7 +626,7 @@ class RealmListRoute extends _i18.PageRouteInfo { static _i18.PageInfo page = _i18.PageInfo( name, builder: (data) { - return const _i9.RealmListScreen(); + return const _i8.RealmListScreen(); }, ); } diff --git a/lib/screens/account/me/event_calendar.dart b/lib/screens/account/me/event_calendar.dart index 1b2aea8..9909cb3 100644 --- a/lib/screens/account/me/event_calendar.dart +++ b/lib/screens/account/me/event_calendar.dart @@ -1,13 +1,172 @@ +import 'dart:convert'; + import 'package:auto_route/auto_route.dart'; -import 'package:flutter/widgets.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.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'; + +@riverpod +Future> myselfAccountEventCalendar(Ref ref) async { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/accounts/me/calendar'); + return resp.data + .map((e) => SnEventCalendarEntry.fromJson(e)) + .cast() + .toList(); +} @RoutePage() -class AccountEventCalendar extends HookConsumerWidget { - const AccountEventCalendar({super.key}); +class MyselfEventCalendarScreen extends HookConsumerWidget { + const MyselfEventCalendarScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - return const Placeholder(); + final events = ref.watch(myselfAccountEventCalendarProvider); + + final selectedDay = useState(DateTime.now()); + + return AppScaffold( + appBar: AppBar( + leading: const PageBackButton(), + title: Text('eventCalander').tr(), + ), + body: Column( + children: [ + SizedBox( + height: 340, + child: events.when( + data: (events) { + return TableCalendar( + locale: EasyLocalization.of(context)!.locale.toString(), + firstDay: DateTime.utc(2010, 10, 16), + lastDay: DateTime.utc(2030, 3, 14), + focusedDay: DateTime.now(), + calendarFormat: CalendarFormat.month, + selectedDayPredicate: (day) { + return isSameDay(selectedDay.value, day); + }, + onDaySelected: (value, _) { + selectedDay.value = value; + }, + eventLoader: (day) { + return events + .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.onPrimary + : Theme.of(context).colorScheme.onSurface, + ), + ), + ); + } + return null; + }, + ), + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + error: + (error, stack) => + Center(child: Text('Error loading calendar: $error')), + ), + ), + const Divider(height: 1), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: Builder( + builder: (context) { + final event = + events.value + ?.where((e) => isSameDay(e.date, selectedDay.value)) + .firstOrNull; + if (event == null) { + return Center(child: Text('eventCalanderEmpty').tr()); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(DateFormat.EEEE().format(event.date)) + .fontSize(16) + .bold() + .textColor( + Theme.of(context).colorScheme.onSecondaryContainer, + ), + Text(DateFormat.yMd().format(event.date)) + .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) + Text('eventCalanderEmpty').tr(), + ], + ).padding(vertical: 24, horizontal: 24); + }, + ), + ), + ], + ), + ); } } diff --git a/lib/screens/account/me/event_calendar.g.dart b/lib/screens/account/me/event_calendar.g.dart new file mode 100644 index 0000000..489809e --- /dev/null +++ b/lib/screens/account/me/event_calendar.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'event_calendar.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myselfAccountEventCalendarHash() => + r'd3faad342d5104e87ff6597ab459e65cb5f4ef97'; + +/// See also [myselfAccountEventCalendar]. +@ProviderFor(myselfAccountEventCalendar) +final myselfAccountEventCalendarProvider = + AutoDisposeFutureProvider>.internal( + myselfAccountEventCalendar, + name: r'myselfAccountEventCalendarProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$myselfAccountEventCalendarHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef MyselfAccountEventCalendarRef = + AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/widgets/check_in.dart b/lib/widgets/check_in.dart index a4547cd..7b2ac28 100644 --- a/lib/widgets/check_in.dart +++ b/lib/widgets/check_in.dart @@ -1,9 +1,12 @@ +import 'package:auto_route/auto_route.dart'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/activity.dart'; import 'package:island/pods/network.dart'; +import 'package:island/route.gr.dart'; +import 'package:island/widgets/alert.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -33,6 +36,16 @@ class CheckInWidget extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final todayResult = ref.watch(checkInResultTodayProvider); + Future checkIn() async { + final client = ref.read(apiClientProvider); + try { + await client.post('/accounts/me/check-in'); + ref.invalidate(checkInResultTodayProvider); + } catch (err) { + showErrorAlert(err); + } + } + return Card( margin: EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), child: Row( @@ -83,7 +96,7 @@ class CheckInWidget extends HookConsumerWidget { (e) => '${e.isPositive ? '宜' : '忌'} ${e.title}', ) .join(' · '), - ).tr().fontSize(11), + ).fontSize(11), ], ); }, @@ -100,7 +113,13 @@ class CheckInWidget extends HookConsumerWidget { ), ), IconButton.outlined( - onPressed: () {}, + onPressed: () { + if (todayResult.valueOrNull == null) { + checkIn(); + } else { + context.router.push(MyselfEventCalendarRoute()); + } + }, icon: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: todayResult.when( diff --git a/pubspec.lock b/pubspec.lock index 56640ca..0ca4448 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -509,10 +509,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "8986dec4581b4bcd4b6df5d75a2ea0bede3db802f500635d05fa8be298f9467f" + sha256: "978be1f602e0695daef8e345a3c597abf72b0c0ca6102fa2665eb549f5406a17" url: "https://pub.dev" source: hosted - version: "10.1.2" + version: "10.1.5" file_selector_linux: dependency: transitive description: @@ -727,10 +727,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: "634622a3a826d67cb05c0e3e576d1812c430fa98404e95b60b131775c73d76ec" + sha256: "08fb8315236099ff8e90cb87bb2b935e0a724a3af1623000a9cec930468e0f27" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.7+1" flutter_markdown_latex: dependency: "direct main" description: @@ -889,10 +889,10 @@ packages: dependency: transitive description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_multi_server: dependency: transitive description: @@ -1549,6 +1549,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + simple_gesture_detector: + dependency: transitive + description: + name: simple_gesture_detector + sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 + url: "https://pub.dev" + source: hosted + version: "0.2.1" sky_engine: dependency: transitive description: flutter @@ -1738,6 +1746,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.1" + table_calendar: + dependency: "direct main" + description: + name: table_calendar + sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63 + url: "https://pub.dev" + source: hosted + version: "3.1.3" tencent_rtc_sdk: dependency: "direct main" description: @@ -1975,10 +1991,10 @@ packages: dependency: transitive description: name: web_socket - sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" web_socket_channel: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index fafd4a8..c78f40a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -92,6 +92,7 @@ dependencies: markdown_editor_plus: ^0.2.15 croppy: ^1.3.6 tencent_rtc_sdk: ^12.3.6 + table_calendar: ^3.1.3 dev_dependencies: flutter_test: