diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 5c68f126..f00b7d4c 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -941,5 +941,6 @@ "pinPostReplyDisabledHint": "This post is not a reply", "pin": "Pin", "unpinPostHint": "Are you sure you want to unpin this post?", - "all": "All" + "all": "All", + "statusPresent": "Present" } diff --git a/assets/i18n/zh-CN.json b/assets/i18n/zh-CN.json index 94f36d02..3a10939e 100644 --- a/assets/i18n/zh-CN.json +++ b/assets/i18n/zh-CN.json @@ -855,5 +855,6 @@ "newSecretGenerated": "已生成新密钥", "copySecretHint": "请复制此密钥并将其存放在安全的地方。您将无法再次看到它。", "expiresIn": "过期时间(秒)", - "isOidc": "OIDC 兼容" + "isOidc": "OIDC 兼容", + "statusPresent": "至今" } diff --git a/lib/models/activity.dart b/lib/models/activity.dart index b870aa53..23b06bd9 100644 --- a/lib/models/activity.dart +++ b/lib/models/activity.dart @@ -54,7 +54,7 @@ sealed class SnEventCalendarEntry with _$SnEventCalendarEntry { const factory SnEventCalendarEntry({ required DateTime date, required SnCheckInResult? checkInResult, - required List statuses, + required List statuses, }) = _SnEventCalendarEntry; factory SnEventCalendarEntry.fromJson(Map json) => diff --git a/lib/models/activity.freezed.dart b/lib/models/activity.freezed.dart index 2a709f0d..e50a2a02 100644 --- a/lib/models/activity.freezed.dart +++ b/lib/models/activity.freezed.dart @@ -861,7 +861,7 @@ as String, /// @nodoc mixin _$SnEventCalendarEntry { - DateTime get date; SnCheckInResult? get checkInResult; List get statuses; + 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) @@ -894,7 +894,7 @@ abstract mixin class $SnEventCalendarEntryCopyWith<$Res> { factory $SnEventCalendarEntryCopyWith(SnEventCalendarEntry value, $Res Function(SnEventCalendarEntry) _then) = _$SnEventCalendarEntryCopyWithImpl; @useResult $Res call({ - DateTime date, SnCheckInResult? checkInResult, List statuses + DateTime date, SnCheckInResult? checkInResult, List statuses }); @@ -916,7 +916,7 @@ class _$SnEventCalendarEntryCopyWithImpl<$Res> 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, +as List, )); } /// Create a copy of SnEventCalendarEntry @@ -1010,7 +1010,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( DateTime date, SnCheckInResult? checkInResult, List statuses)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( DateTime date, SnCheckInResult? checkInResult, List statuses)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _SnEventCalendarEntry() when $default != null: return $default(_that.date,_that.checkInResult,_that.statuses);case _: @@ -1031,7 +1031,7 @@ return $default(_that.date,_that.checkInResult,_that.statuses);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( DateTime date, SnCheckInResult? checkInResult, List statuses) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( DateTime date, SnCheckInResult? checkInResult, List statuses) $default,) {final _that = this; switch (_that) { case _SnEventCalendarEntry(): return $default(_that.date,_that.checkInResult,_that.statuses);} @@ -1048,7 +1048,7 @@ return $default(_that.date,_that.checkInResult,_that.statuses);} /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List statuses)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List statuses)? $default,) {final _that = this; switch (_that) { case _SnEventCalendarEntry() when $default != null: return $default(_that.date,_that.checkInResult,_that.statuses);case _: @@ -1063,13 +1063,13 @@ return $default(_that.date,_that.checkInResult,_that.statuses);case _: @JsonSerializable() class _SnEventCalendarEntry implements SnEventCalendarEntry { - const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List statuses}): _statuses = statuses; + 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 { + final List _statuses; +@override List get statuses { if (_statuses is EqualUnmodifiableListView) return _statuses; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_statuses); @@ -1109,7 +1109,7 @@ abstract mixin class _$SnEventCalendarEntryCopyWith<$Res> implements $SnEventCal factory _$SnEventCalendarEntryCopyWith(_SnEventCalendarEntry value, $Res Function(_SnEventCalendarEntry) _then) = __$SnEventCalendarEntryCopyWithImpl; @override @useResult $Res call({ - DateTime date, SnCheckInResult? checkInResult, List statuses + DateTime date, SnCheckInResult? checkInResult, List statuses }); @@ -1131,7 +1131,7 @@ class __$SnEventCalendarEntryCopyWithImpl<$Res> 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, +as List, )); } diff --git a/lib/models/activity.g.dart b/lib/models/activity.g.dart index 2373de1f..b5ca4633 100644 --- a/lib/models/activity.g.dart +++ b/lib/models/activity.g.dart @@ -87,7 +87,10 @@ _SnEventCalendarEntry _$SnEventCalendarEntryFromJson( : SnCheckInResult.fromJson( json['check_in_result'] as Map, ), - statuses: json['statuses'] as List, + statuses: + (json['statuses'] as List) + .map((e) => SnAccountStatus.fromJson(e as Map)) + .toList(), ); Map _$SnEventCalendarEntryToJson( @@ -95,5 +98,5 @@ Map _$SnEventCalendarEntryToJson( ) => { 'date': instance.date.toIso8601String(), 'check_in_result': instance.checkInResult?.toJson(), - 'statuses': instance.statuses, + 'statuses': instance.statuses.map((e) => e.toJson()).toList(), }; diff --git a/lib/widgets/account/event_calendar.dart b/lib/widgets/account/event_calendar.dart index 632c71f4..36f9ef33 100644 --- a/lib/widgets/account/event_calendar.dart +++ b/lib/widgets/account/event_calendar.dart @@ -2,7 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/models/account.dart'; import 'package:island/models/activity.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:island/widgets/account/event_details_widget.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -87,24 +89,56 @@ class EventCalendarWidget extends HookConsumerWidget { return Center(child: Text(text)); }, markerBuilder: (context, day, events) { - var checkInResult = + final checkInResult = events.whereType().firstOrNull; + final statuses = events.whereType().toList(); + + final textColor = + isSameDay(selectedDay.value, day) + ? Theme.of(context).colorScheme.onPrimary + : isSameDay(DateTime.now(), day) + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context).colorScheme.onSurface; + + final shadow = + isSameDay(selectedDay.value, day) || + isSameDay(DateTime.now(), day) + ? [ + Shadow( + color: Colors.black.withOpacity(0.5), + offset: const Offset(0, 1), + blurRadius: 4, + ), + ] + : null; + if (checkInResult != null) { return Positioned( top: 32, - child: Text( - 'checkInResultT${checkInResult.level}'.tr(), - 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, - ), + child: Row( + spacing: 2, + children: [ + Text( + 'checkInResultT${checkInResult.level}'.tr(), + style: TextStyle( + fontSize: 9, + color: textColor, + shadows: shadow, + ), + ), + if (statuses.isNotEmpty) ...[ + Icon( + switch (statuses.first.attitude) { + 0 => Symbols.sentiment_satisfied, + 2 => Symbols.sentiment_dissatisfied, + _ => Symbols.sentiment_neutral, + }, + size: 12, + color: textColor, + shadows: shadow, + ), + ], + ], ), ); } diff --git a/lib/widgets/account/event_details_widget.dart b/lib/widgets/account/event_details_widget.dart index 7683295a..d4054019 100644 --- a/lib/widgets/account/event_details_widget.dart +++ b/lib/widgets/account/event_details_widget.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:island/models/activity.dart'; +import 'package:island/services/time.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -53,6 +54,33 @@ class EventDetailsWidget extends StatelessWidget { ), ], ).padding(top: 8), + if (event!.statuses.isNotEmpty) ...[ + const Gap(16), + Text('statusLabel').tr().fontSize(16).bold(), + ], + for (final status in event!.statuses) ...[ + Row( + spacing: 8, + children: [ + Icon(switch (status.attitude) { + 0 => Symbols.sentiment_satisfied, + 2 => Symbols.sentiment_dissatisfied, + _ => Symbols.sentiment_neutral, + }), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(status.label), + Text( + '${status.createdAt.formatSystem()} - ${status.clearedAt?.formatSystem() ?? 'present'.tr()}', + ).fontSize(11).opacity(0.8), + ], + ), + ), + ], + ).padding(vertical: 8), + ], ], ), if (event?.checkInResult == null && (event?.statuses.isEmpty ?? true))