Event dairy

This commit is contained in:
2025-08-25 18:31:57 +08:00
parent c6599edc3d
commit 6501594100
7 changed files with 97 additions and 30 deletions

View File

@@ -941,5 +941,6 @@
"pinPostReplyDisabledHint": "This post is not a reply", "pinPostReplyDisabledHint": "This post is not a reply",
"pin": "Pin", "pin": "Pin",
"unpinPostHint": "Are you sure you want to unpin this post?", "unpinPostHint": "Are you sure you want to unpin this post?",
"all": "All" "all": "All",
"statusPresent": "Present"
} }

View File

@@ -855,5 +855,6 @@
"newSecretGenerated": "已生成新密钥", "newSecretGenerated": "已生成新密钥",
"copySecretHint": "请复制此密钥并将其存放在安全的地方。您将无法再次看到它。", "copySecretHint": "请复制此密钥并将其存放在安全的地方。您将无法再次看到它。",
"expiresIn": "过期时间(秒)", "expiresIn": "过期时间(秒)",
"isOidc": "OIDC 兼容" "isOidc": "OIDC 兼容",
"statusPresent": "至今"
} }

View File

@@ -54,7 +54,7 @@ sealed class SnEventCalendarEntry with _$SnEventCalendarEntry {
const factory SnEventCalendarEntry({ const factory SnEventCalendarEntry({
required DateTime date, required DateTime date,
required SnCheckInResult? checkInResult, required SnCheckInResult? checkInResult,
required List<dynamic> statuses, required List<SnAccountStatus> statuses,
}) = _SnEventCalendarEntry; }) = _SnEventCalendarEntry;
factory SnEventCalendarEntry.fromJson(Map<String, dynamic> json) => factory SnEventCalendarEntry.fromJson(Map<String, dynamic> json) =>

View File

@@ -861,7 +861,7 @@ as String,
/// @nodoc /// @nodoc
mixin _$SnEventCalendarEntry { mixin _$SnEventCalendarEntry {
DateTime get date; SnCheckInResult? get checkInResult; List<dynamic> get statuses; DateTime get date; SnCheckInResult? get checkInResult; List<SnAccountStatus> get statuses;
/// Create a copy of SnEventCalendarEntry /// Create a copy of SnEventCalendarEntry
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -894,7 +894,7 @@ abstract mixin class $SnEventCalendarEntryCopyWith<$Res> {
factory $SnEventCalendarEntryCopyWith(SnEventCalendarEntry value, $Res Function(SnEventCalendarEntry) _then) = _$SnEventCalendarEntryCopyWithImpl; factory $SnEventCalendarEntryCopyWith(SnEventCalendarEntry value, $Res Function(SnEventCalendarEntry) _then) = _$SnEventCalendarEntryCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses
}); });
@@ -916,7 +916,7 @@ class _$SnEventCalendarEntryCopyWithImpl<$Res>
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable 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 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 SnCheckInResult?,statuses: null == statuses ? _self.statuses : statuses // ignore: cast_nullable_to_non_nullable
as List<dynamic>, as List<SnAccountStatus>,
)); ));
} }
/// Create a copy of SnEventCalendarEntry /// Create a copy of SnEventCalendarEntry
@@ -1010,7 +1010,7 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _SnEventCalendarEntry() when $default != null: case _SnEventCalendarEntry() when $default != null:
return $default(_that.date,_that.checkInResult,_that.statuses);case _: 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 extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnEventCalendarEntry(): case _SnEventCalendarEntry():
return $default(_that.date,_that.checkInResult,_that.statuses);} return $default(_that.date,_that.checkInResult,_that.statuses);}
@@ -1048,7 +1048,7 @@ return $default(_that.date,_that.checkInResult,_that.statuses);}
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnEventCalendarEntry() when $default != null: case _SnEventCalendarEntry() when $default != null:
return $default(_that.date,_that.checkInResult,_that.statuses);case _: return $default(_that.date,_that.checkInResult,_that.statuses);case _:
@@ -1063,13 +1063,13 @@ return $default(_that.date,_that.checkInResult,_that.statuses);case _:
@JsonSerializable() @JsonSerializable()
class _SnEventCalendarEntry implements SnEventCalendarEntry { class _SnEventCalendarEntry implements SnEventCalendarEntry {
const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List<dynamic> statuses}): _statuses = statuses; const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List<SnAccountStatus> statuses}): _statuses = statuses;
factory _SnEventCalendarEntry.fromJson(Map<String, dynamic> json) => _$SnEventCalendarEntryFromJson(json); factory _SnEventCalendarEntry.fromJson(Map<String, dynamic> json) => _$SnEventCalendarEntryFromJson(json);
@override final DateTime date; @override final DateTime date;
@override final SnCheckInResult? checkInResult; @override final SnCheckInResult? checkInResult;
final List<dynamic> _statuses; final List<SnAccountStatus> _statuses;
@override List<dynamic> get statuses { @override List<SnAccountStatus> get statuses {
if (_statuses is EqualUnmodifiableListView) return _statuses; if (_statuses is EqualUnmodifiableListView) return _statuses;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_statuses); return EqualUnmodifiableListView(_statuses);
@@ -1109,7 +1109,7 @@ abstract mixin class _$SnEventCalendarEntryCopyWith<$Res> implements $SnEventCal
factory _$SnEventCalendarEntryCopyWith(_SnEventCalendarEntry value, $Res Function(_SnEventCalendarEntry) _then) = __$SnEventCalendarEntryCopyWithImpl; factory _$SnEventCalendarEntryCopyWith(_SnEventCalendarEntry value, $Res Function(_SnEventCalendarEntry) _then) = __$SnEventCalendarEntryCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses
}); });
@@ -1131,7 +1131,7 @@ class __$SnEventCalendarEntryCopyWithImpl<$Res>
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable 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 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 SnCheckInResult?,statuses: null == statuses ? _self._statuses : statuses // ignore: cast_nullable_to_non_nullable
as List<dynamic>, as List<SnAccountStatus>,
)); ));
} }

View File

@@ -87,7 +87,10 @@ _SnEventCalendarEntry _$SnEventCalendarEntryFromJson(
: SnCheckInResult.fromJson( : SnCheckInResult.fromJson(
json['check_in_result'] as Map<String, dynamic>, json['check_in_result'] as Map<String, dynamic>,
), ),
statuses: json['statuses'] as List<dynamic>, statuses:
(json['statuses'] as List<dynamic>)
.map((e) => SnAccountStatus.fromJson(e as Map<String, dynamic>))
.toList(),
); );
Map<String, dynamic> _$SnEventCalendarEntryToJson( Map<String, dynamic> _$SnEventCalendarEntryToJson(
@@ -95,5 +98,5 @@ Map<String, dynamic> _$SnEventCalendarEntryToJson(
) => <String, dynamic>{ ) => <String, dynamic>{
'date': instance.date.toIso8601String(), 'date': instance.date.toIso8601String(),
'check_in_result': instance.checkInResult?.toJson(), 'check_in_result': instance.checkInResult?.toJson(),
'statuses': instance.statuses, 'statuses': instance.statuses.map((e) => e.toJson()).toList(),
}; };

View File

@@ -2,7 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart';
import 'package:island/models/activity.dart'; import 'package:island/models/activity.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:island/widgets/account/event_details_widget.dart'; import 'package:island/widgets/account/event_details_widget.dart';
import 'package:table_calendar/table_calendar.dart'; import 'package:table_calendar/table_calendar.dart';
@@ -87,24 +89,56 @@ class EventCalendarWidget extends HookConsumerWidget {
return Center(child: Text(text)); return Center(child: Text(text));
}, },
markerBuilder: (context, day, events) { markerBuilder: (context, day, events) {
var checkInResult = final checkInResult =
events.whereType<SnCheckInResult>().firstOrNull; events.whereType<SnCheckInResult>().firstOrNull;
final statuses = events.whereType<SnAccountStatus>().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) { if (checkInResult != null) {
return Positioned( return Positioned(
top: 32, top: 32,
child: Text( child: Row(
'checkInResultT${checkInResult.level}'.tr(), spacing: 2,
style: TextStyle( children: [
fontSize: 9, Text(
color: 'checkInResultT${checkInResult.level}'.tr(),
isSameDay(selectedDay.value, day) style: TextStyle(
? Theme.of(context).colorScheme.onPrimaryContainer fontSize: 9,
: isSameDay(DateTime.now(), day) color: textColor,
? Theme.of( shadows: shadow,
context, ),
).colorScheme.onSecondaryContainer ),
: Theme.of(context).colorScheme.onSurface, 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,
),
],
],
), ),
); );
} }

View File

@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:island/models/activity.dart'; import 'package:island/models/activity.dart';
import 'package:island/services/time.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@@ -53,6 +54,33 @@ class EventDetailsWidget extends StatelessWidget {
), ),
], ],
).padding(top: 8), ).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)) if (event?.checkInResult == null && (event?.statuses.isEmpty ?? true))