💄 Optimized fortune saying and dashboard clock

This commit is contained in:
2025-12-25 00:29:11 +08:00
parent 7071399cd8
commit 0876ab9b74
12 changed files with 539 additions and 97 deletions

View File

@@ -1021,6 +1021,7 @@
"noResultsFound": "No results found",
"toggleFilters": "Toggle filters",
"notableDayNext": "{} is in",
"notableDayToday": "{} is today!",
"expandPoll": "Expand Poll",
"collapsePoll": "Collapse Poll",
"embedView": "Embed View",
@@ -1539,5 +1540,7 @@
"settingsNotifyWithHaptic": "Notification with Haptic Feedback",
"settingsDashSearchEngine": "Search Engine for web",
"settingsDashSearchEngineHelper": "Use %s as the placeholder for the query.",
"settingsDefaultScreen": "Default Screen"
"settingsDefaultScreen": "Default Screen",
"notableDayChristmas": "Christmas",
"notableDayNewYear": "New Year"
}

View File

@@ -999,6 +999,7 @@
"noResultsFound": "未找到结果",
"toggleFilters": "切换过滤器",
"notableDayNext": "距离 {} 还有",
"notableDayToday": "今天是 {}!",
"expandPoll": "展开投票",
"collapsePoll": "折叠投票",
"embedView": "嵌入视图",

View File

@@ -10,7 +10,8 @@ sealed class SnNotableDay with _$SnNotableDay {
required DateTime date,
required String localName,
required String globalName,
required String countryCode,
required String? countryCode,
required String? localizableKey,
required List<int> holidays,
}) = _SnNotableDay;

View File

@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnNotableDay {
DateTime get date; String get localName; String get globalName; String get countryCode; List<int> get holidays;
DateTime get date; String get localName; String get globalName; String? get countryCode; String? get localizableKey; List<int> get holidays;
/// Create a copy of SnNotableDay
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $SnNotableDayCopyWith<SnNotableDay> get copyWith => _$SnNotableDayCopyWithImpl<S
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other.holidays, holidays));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.localizableKey, localizableKey) || other.localizableKey == localizableKey)&&const DeepCollectionEquality().equals(other.holidays, holidays));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(holidays));
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,localizableKey,const DeepCollectionEquality().hash(holidays));
@override
String toString() {
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)';
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, localizableKey: $localizableKey, holidays: $holidays)';
}
@@ -48,7 +48,7 @@ abstract mixin class $SnNotableDayCopyWith<$Res> {
factory $SnNotableDayCopyWith(SnNotableDay value, $Res Function(SnNotableDay) _then) = _$SnNotableDayCopyWithImpl;
@useResult
$Res call({
DateTime date, String localName, String globalName, String countryCode, List<int> holidays
DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays
});
@@ -65,13 +65,14 @@ class _$SnNotableDayCopyWithImpl<$Res>
/// Create a copy of SnNotableDay
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = null,Object? holidays = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = freezed,Object? localizableKey = freezed,Object? holidays = null,}) {
return _then(_self.copyWith(
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable
as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable
as String,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String,holidays: null == holidays ? _self.holidays : holidays // ignore: cast_nullable_to_non_nullable
as String,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,localizableKey: freezed == localizableKey ? _self.localizableKey : localizableKey // ignore: cast_nullable_to_non_nullable
as String?,holidays: null == holidays ? _self.holidays : holidays // ignore: cast_nullable_to_non_nullable
as List<int>,
));
}
@@ -154,10 +155,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String countryCode, List<int> holidays)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnNotableDay() when $default != null:
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);case _:
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.localizableKey,_that.holidays);case _:
return orElse();
}
@@ -175,10 +176,10 @@ return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_t
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String countryCode, List<int> holidays) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays) $default,) {final _that = this;
switch (_that) {
case _SnNotableDay():
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);}
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.localizableKey,_that.holidays);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -192,10 +193,10 @@ return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_t
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, String localName, String globalName, String countryCode, List<int> holidays)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays)? $default,) {final _that = this;
switch (_that) {
case _SnNotableDay() when $default != null:
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);case _:
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.localizableKey,_that.holidays);case _:
return null;
}
@@ -207,13 +208,14 @@ return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_t
@JsonSerializable()
class _SnNotableDay implements SnNotableDay {
const _SnNotableDay({required this.date, required this.localName, required this.globalName, required this.countryCode, required final List<int> holidays}): _holidays = holidays;
const _SnNotableDay({required this.date, required this.localName, required this.globalName, required this.countryCode, required this.localizableKey, required final List<int> holidays}): _holidays = holidays;
factory _SnNotableDay.fromJson(Map<String, dynamic> json) => _$SnNotableDayFromJson(json);
@override final DateTime date;
@override final String localName;
@override final String globalName;
@override final String countryCode;
@override final String? countryCode;
@override final String? localizableKey;
final List<int> _holidays;
@override List<int> get holidays {
if (_holidays is EqualUnmodifiableListView) return _holidays;
@@ -235,16 +237,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other._holidays, _holidays));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.localizableKey, localizableKey) || other.localizableKey == localizableKey)&&const DeepCollectionEquality().equals(other._holidays, _holidays));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(_holidays));
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,localizableKey,const DeepCollectionEquality().hash(_holidays));
@override
String toString() {
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)';
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, localizableKey: $localizableKey, holidays: $holidays)';
}
@@ -255,7 +257,7 @@ abstract mixin class _$SnNotableDayCopyWith<$Res> implements $SnNotableDayCopyWi
factory _$SnNotableDayCopyWith(_SnNotableDay value, $Res Function(_SnNotableDay) _then) = __$SnNotableDayCopyWithImpl;
@override @useResult
$Res call({
DateTime date, String localName, String globalName, String countryCode, List<int> holidays
DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays
});
@@ -272,13 +274,14 @@ class __$SnNotableDayCopyWithImpl<$Res>
/// Create a copy of SnNotableDay
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = null,Object? holidays = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = freezed,Object? localizableKey = freezed,Object? holidays = null,}) {
return _then(_SnNotableDay(
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable
as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable
as String,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String,holidays: null == holidays ? _self._holidays : holidays // ignore: cast_nullable_to_non_nullable
as String,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,localizableKey: freezed == localizableKey ? _self.localizableKey : localizableKey // ignore: cast_nullable_to_non_nullable
as String?,holidays: null == holidays ? _self._holidays : holidays // ignore: cast_nullable_to_non_nullable
as List<int>,
));
}

View File

@@ -11,7 +11,8 @@ _SnNotableDay _$SnNotableDayFromJson(Map<String, dynamic> json) =>
date: DateTime.parse(json['date'] as String),
localName: json['local_name'] as String,
globalName: json['global_name'] as String,
countryCode: json['country_code'] as String,
countryCode: json['country_code'] as String?,
localizableKey: json['localizable_key'] as String?,
holidays: (json['holidays'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
@@ -23,6 +24,7 @@ Map<String, dynamic> _$SnNotableDayToJson(_SnNotableDay instance) =>
'local_name': instance.localName,
'global_name': instance.globalName,
'country_code': instance.countryCode,
'localizable_key': instance.localizableKey,
'holidays': instance.holidays,
};

16
lib/models/fortune.dart Normal file
View File

@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'fortune.g.dart';
part 'fortune.freezed.dart';
@freezed
sealed class SnFortuneSaying with _$SnFortuneSaying {
const factory SnFortuneSaying({
required String content,
required String source,
required String language,
}) = _SnFortuneSaying;
factory SnFortuneSaying.fromJson(Map<String, dynamic> json) =>
_$SnFortuneSayingFromJson(json);
}

View File

@@ -0,0 +1,277 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'fortune.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnFortuneSaying {
String get content; String get source; String get language;
/// Create a copy of SnFortuneSaying
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnFortuneSayingCopyWith<SnFortuneSaying> get copyWith => _$SnFortuneSayingCopyWithImpl<SnFortuneSaying>(this as SnFortuneSaying, _$identity);
/// Serializes this SnFortuneSaying to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnFortuneSaying&&(identical(other.content, content) || other.content == content)&&(identical(other.source, source) || other.source == source)&&(identical(other.language, language) || other.language == language));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,content,source,language);
@override
String toString() {
return 'SnFortuneSaying(content: $content, source: $source, language: $language)';
}
}
/// @nodoc
abstract mixin class $SnFortuneSayingCopyWith<$Res> {
factory $SnFortuneSayingCopyWith(SnFortuneSaying value, $Res Function(SnFortuneSaying) _then) = _$SnFortuneSayingCopyWithImpl;
@useResult
$Res call({
String content, String source, String language
});
}
/// @nodoc
class _$SnFortuneSayingCopyWithImpl<$Res>
implements $SnFortuneSayingCopyWith<$Res> {
_$SnFortuneSayingCopyWithImpl(this._self, this._then);
final SnFortuneSaying _self;
final $Res Function(SnFortuneSaying) _then;
/// Create a copy of SnFortuneSaying
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? content = null,Object? source = null,Object? language = null,}) {
return _then(_self.copyWith(
content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [SnFortuneSaying].
extension SnFortuneSayingPatterns on SnFortuneSaying {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnFortuneSaying value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnFortuneSaying() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnFortuneSaying value) $default,){
final _that = this;
switch (_that) {
case _SnFortuneSaying():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnFortuneSaying value)? $default,){
final _that = this;
switch (_that) {
case _SnFortuneSaying() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String content, String source, String language)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnFortuneSaying() when $default != null:
return $default(_that.content,_that.source,_that.language);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String content, String source, String language) $default,) {final _that = this;
switch (_that) {
case _SnFortuneSaying():
return $default(_that.content,_that.source,_that.language);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String content, String source, String language)? $default,) {final _that = this;
switch (_that) {
case _SnFortuneSaying() when $default != null:
return $default(_that.content,_that.source,_that.language);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnFortuneSaying implements SnFortuneSaying {
const _SnFortuneSaying({required this.content, required this.source, required this.language});
factory _SnFortuneSaying.fromJson(Map<String, dynamic> json) => _$SnFortuneSayingFromJson(json);
@override final String content;
@override final String source;
@override final String language;
/// Create a copy of SnFortuneSaying
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnFortuneSayingCopyWith<_SnFortuneSaying> get copyWith => __$SnFortuneSayingCopyWithImpl<_SnFortuneSaying>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnFortuneSayingToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnFortuneSaying&&(identical(other.content, content) || other.content == content)&&(identical(other.source, source) || other.source == source)&&(identical(other.language, language) || other.language == language));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,content,source,language);
@override
String toString() {
return 'SnFortuneSaying(content: $content, source: $source, language: $language)';
}
}
/// @nodoc
abstract mixin class _$SnFortuneSayingCopyWith<$Res> implements $SnFortuneSayingCopyWith<$Res> {
factory _$SnFortuneSayingCopyWith(_SnFortuneSaying value, $Res Function(_SnFortuneSaying) _then) = __$SnFortuneSayingCopyWithImpl;
@override @useResult
$Res call({
String content, String source, String language
});
}
/// @nodoc
class __$SnFortuneSayingCopyWithImpl<$Res>
implements _$SnFortuneSayingCopyWith<$Res> {
__$SnFortuneSayingCopyWithImpl(this._self, this._then);
final _SnFortuneSaying _self;
final $Res Function(_SnFortuneSaying) _then;
/// Create a copy of SnFortuneSaying
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? content = null,Object? source = null,Object? language = null,}) {
return _then(_SnFortuneSaying(
content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

21
lib/models/fortune.g.dart Normal file
View File

@@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fortune.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_SnFortuneSaying _$SnFortuneSayingFromJson(Map<String, dynamic> json) =>
_SnFortuneSaying(
content: json['content'] as String,
source: json['source'] as String,
language: json['language'] as String,
);
Map<String, dynamic> _$SnFortuneSayingToJson(_SnFortuneSaying instance) =>
<String, dynamic>{
'content': instance.content,
'source': instance.source,
'language': instance.language,
};

View File

@@ -65,7 +65,7 @@ final class AppSettingsNotifierProvider
}
String _$appSettingsNotifierHash() =>
r'7e311f421fb3a24946f95b7d207821151ba5a326';
r'ef10d95a9f22e891ad6f5e0225e31508b3eb038e';
abstract class _$AppSettingsNotifier extends $Notifier<AppSettings> {
AppSettings build();

View File

@@ -21,6 +21,7 @@ import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/notification_tile.dart';
import 'package:island/widgets/post/post_featured.dart';
import 'package:island/widgets/check_in.dart';
import 'package:island/models/activity.dart';
import 'package:island/screens/notification.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:slide_countdown/slide_countdown.dart';
@@ -229,7 +230,7 @@ class ClockCard extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final time = useState(DateTime.now());
final timer = useRef<Timer?>(null);
final nextNotableDay = ref.watch(nextNotableDayProvider);
final notableDay = ref.watch(recentNotableDayProvider);
// Determine icon based on time of day
final int hour = time.value.hour;
@@ -301,23 +302,13 @@ class ClockCard extends HookConsumerWidget {
Row(
spacing: 5,
children: [
Text('notableDayNext')
.tr(
args: [
nextNotableDay.value?.localName ?? 'idk',
],
)
.fontSize(12),
if (nextNotableDay.value != null)
SlideCountdown(
decoration: const BoxDecoration(),
style: const TextStyle(fontSize: 12),
separatorStyle: const TextStyle(fontSize: 12),
padding: EdgeInsets.zero,
duration: nextNotableDay.value?.date.difference(
DateTime.now(),
),
),
notableDay.when(
data: (day) => _buildNotableDayText(context, day!),
error: (err, _) =>
Text(err.toString()).fontSize(12),
loading: () =>
const Text('loading').tr().fontSize(12),
),
],
),
],
@@ -330,6 +321,42 @@ class ClockCard extends HookConsumerWidget {
),
);
}
Widget _buildNotableDayText(BuildContext context, SnNotableDay notableDay) {
final today = DateTime.now();
final isToday =
notableDay.date.year == today.year &&
notableDay.date.month == today.month &&
notableDay.date.day == today.day;
if (isToday) {
return Row(
spacing: 5,
children: [
Text('notableDayToday').tr(args: [notableDay.localName]).fontSize(12),
Icon(
Symbols.celebration_rounded,
size: 16,
color: Theme.of(context).colorScheme.primary,
),
],
);
} else {
return Row(
spacing: 5,
children: [
Text('notableDayNext').tr(args: [notableDay.localName]).fontSize(12),
SlideCountdown(
decoration: const BoxDecoration(),
style: const TextStyle(fontSize: 12),
separatorStyle: const TextStyle(fontSize: 12),
padding: EdgeInsets.zero,
duration: notableDay.date.difference(DateTime.now()),
),
],
);
}
}
}
class NotificationsCard extends HookConsumerWidget {
@@ -493,65 +520,38 @@ class ChatListCard extends HookConsumerWidget {
}
}
class FortuneCard extends HookWidget {
class FortuneCard extends HookConsumerWidget {
const FortuneCard({super.key});
@override
Widget build(BuildContext context) {
final fortune = useMemoized(() {
const fortunes = [
{'text': '有的人活着,但他已经死了。', 'author': '—— 鲁迅'},
{'text': '天行健,君子以自强不息。', 'author': '—— 《周易》'},
{'text': '路漫漫其修远兮,吾将上下而求索。', 'author': '—— 屈原'},
{'text': '学海无涯苦作舟。', 'author': '—— 韩愈'},
{'text': '天道酬勤。', 'author': '—— 古语'},
{'text': '书山有路勤为径,学海无涯苦作舟。', 'author': '—— 韩愈'},
{'text': '莫等闲,白了少年头,空悲切。', 'author': '—— 岳飞'},
{
'text': 'The best way to predict the future is to create it.',
'author': '— Peter Drucker',
},
{'text': 'Fortune favors the bold.', 'author': '— Virgil'},
{
'text': 'A journey of a thousand miles begins with a single step.',
'author': '— Lao Tzu',
},
{
'text': 'The only way to do great work is to love what you do.',
'author': '— Steve Jobs',
},
{
'text': 'Believe you can and you\'re halfway there.',
'author': '— Theodore Roosevelt',
},
{
'text':
'The future belongs to those who believe in the beauty of their dreams.',
'author': '— Eleanor Roosevelt',
},
];
return fortunes[math.Random().nextInt(fortunes.length)];
});
Widget build(BuildContext context, WidgetRef ref) {
final fortuneAsync = ref.watch(randomFortuneSayingProvider);
return Card(
margin: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
fortune['text']!,
maxLines: 2,
overflow: TextOverflow.fade,
),
),
Text(fortune['author']!).bold(),
],
).padding(horizontal: 16),
child: fortuneAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (fortune) {
return Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
fortune.content,
maxLines: 2,
overflow: TextOverflow.fade,
),
),
Text('—— ${fortune.source}').bold(),
],
).padding(horizontal: 16);
},
),
).height(48);
}
}

View File

@@ -8,6 +8,7 @@ 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/models/fortune.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/screens/auth/captcha.dart';
@@ -42,12 +43,50 @@ Future<SnNotableDay?> nextNotableDay(Ref ref) async {
final client = ref.watch(apiClientProvider);
try {
final resp = await client.get('/pass/notable/me/next');
return SnNotableDay.fromJson(resp.data);
final day = SnNotableDay.fromJson(resp.data);
if (day.localizableKey != null) {
final key = 'notableDay${day.localizableKey}';
if (key.trExists()) {
return day.copyWith(
localName: key.tr(),
date: day.date.toLocal().copyWith(hour: 0, second: 0),
);
}
}
return day.copyWith(date: day.date.toLocal().copyWith(hour: 0, second: 0));
} catch (err) {
return null;
}
}
@riverpod
Future<SnNotableDay?> recentNotableDay(Ref ref) async {
final client = ref.watch(apiClientProvider);
try {
final resp = await client.get('/pass/notable/me/recent');
final day = SnNotableDay.fromJson(resp.data[0]);
if (day.localizableKey != null) {
final key = 'notableDay${day.localizableKey}';
if (key.trExists()) {
return day.copyWith(
localName: key.tr(),
date: day.date.toLocal().copyWith(hour: 0, second: 0),
);
}
}
return day.copyWith(date: day.date.toLocal().copyWith(hour: 0, second: 0));
} catch (err) {
return null;
}
}
@riverpod
Future<SnFortuneSaying> randomFortuneSaying(Ref ref) async {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/pass/fortune/random');
return SnFortuneSaying.fromJson(resp.data[0]);
}
class CheckInWidget extends HookConsumerWidget {
final EdgeInsets? margin;
final VoidCallback? onChecked;

View File

@@ -86,4 +86,83 @@ final class NextNotableDayProvider
}
}
String _$nextNotableDayHash() => r'c8404308f6b0f581cc7df251bce8f3c5ac130245';
String _$nextNotableDayHash() => r'60d0546a086bdcb89c433c38133eb4197e4fb0a6';
@ProviderFor(recentNotableDay)
const recentNotableDayProvider = RecentNotableDayProvider._();
final class RecentNotableDayProvider
extends
$FunctionalProvider<
AsyncValue<SnNotableDay?>,
SnNotableDay?,
FutureOr<SnNotableDay?>
>
with $FutureModifier<SnNotableDay?>, $FutureProvider<SnNotableDay?> {
const RecentNotableDayProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'recentNotableDayProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$recentNotableDayHash();
@$internal
@override
$FutureProviderElement<SnNotableDay?> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<SnNotableDay?> create(Ref ref) {
return recentNotableDay(ref);
}
}
String _$recentNotableDayHash() => r'780d0f0747d753c5d535d9c2413f8e68d457d974';
@ProviderFor(randomFortuneSaying)
const randomFortuneSayingProvider = RandomFortuneSayingProvider._();
final class RandomFortuneSayingProvider
extends
$FunctionalProvider<
AsyncValue<SnFortuneSaying>,
SnFortuneSaying,
FutureOr<SnFortuneSaying>
>
with $FutureModifier<SnFortuneSaying>, $FutureProvider<SnFortuneSaying> {
const RandomFortuneSayingProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'randomFortuneSayingProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$randomFortuneSayingHash();
@$internal
@override
$FutureProviderElement<SnFortuneSaying> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<SnFortuneSaying> create(Ref ref) {
return randomFortuneSaying(ref);
}
}
String _$randomFortuneSayingHash() =>
r'861378dba8021e8555b568fb8e0390b2b24056f6';