From f8c6887769b6d3e7f05d1f65d82da5998b594bb9 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 7 Sep 2025 16:30:36 +0800 Subject: [PATCH] :sparkles: Notable day countdown --- assets/i18n/en-US.json | 3 +- assets/i18n/zh-CN.json | 3 +- lib/models/account.dart | 2 +- lib/models/account.freezed.dart | 4 +- lib/models/account.g.dart | 2 +- lib/models/activity.dart | 14 + lib/models/activity.freezed.dart | 275 ++++++++++++++++++ lib/models/activity.g.dart | 21 ++ lib/screens/posts/post_detail.dart | 4 +- lib/widgets/account/event_details_widget.dart | 4 + lib/widgets/check_in.dart | 271 ++++++++++------- lib/widgets/check_in.g.dart | 19 ++ lib/widgets/content/attachment_preview.dart | 2 +- pubspec.lock | 16 + pubspec.yaml | 1 + 15 files changed, 524 insertions(+), 117 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 6aaf1d66..96d2d042 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -976,5 +976,6 @@ "shuffle": "Shuffle", "pinned": "Pinned", "noResultsFound": "No results found", - "toggleFilters": "Toggle filters" + "toggleFilters": "Toggle filters", + "notableDayNext": "{} is in" } diff --git a/assets/i18n/zh-CN.json b/assets/i18n/zh-CN.json index 4800b3cf..c4d307e9 100644 --- a/assets/i18n/zh-CN.json +++ b/assets/i18n/zh-CN.json @@ -863,5 +863,6 @@ "statusPresent": "至今", "accountAutomated": "机器人", "openInBrowser": "在浏览器中打开", - "highlightPost": "精选帖子" + "highlightPost": "精选帖子", + "notableDayNext": "距离 {} 还有" } diff --git a/lib/models/account.dart b/lib/models/account.dart index f1b01d17..1f68b7ce 100644 --- a/lib/models/account.dart +++ b/lib/models/account.dart @@ -13,7 +13,7 @@ sealed class SnAccount with _$SnAccount { required String name, required String nick, required String language, - required String region, + @Default("") String region, required bool isSuperuser, required String? automatedId, required SnAccountProfile profile, diff --git a/lib/models/account.freezed.dart b/lib/models/account.freezed.dart index 7c944e04..b644f1a3 100644 --- a/lib/models/account.freezed.dart +++ b/lib/models/account.freezed.dart @@ -236,14 +236,14 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that @JsonSerializable() class _SnAccount implements SnAccount { - const _SnAccount({required this.id, required this.name, required this.nick, required this.language, required this.region, required this.isSuperuser, required this.automatedId, required this.profile, required this.perkSubscription, final List badges = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges; + const _SnAccount({required this.id, required this.name, required this.nick, required this.language, this.region = "", required this.isSuperuser, required this.automatedId, required this.profile, required this.perkSubscription, final List badges = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges; factory _SnAccount.fromJson(Map json) => _$SnAccountFromJson(json); @override final String id; @override final String name; @override final String nick; @override final String language; -@override final String region; +@override@JsonKey() final String region; @override final bool isSuperuser; @override final String? automatedId; @override final SnAccountProfile profile; diff --git a/lib/models/account.g.dart b/lib/models/account.g.dart index 7b6e9806..3991fbe3 100644 --- a/lib/models/account.g.dart +++ b/lib/models/account.g.dart @@ -11,7 +11,7 @@ _SnAccount _$SnAccountFromJson(Map json) => _SnAccount( name: json['name'] as String, nick: json['nick'] as String, language: json['language'] as String, - region: json['region'] as String, + region: json['region'] as String? ?? "", isSuperuser: json['is_superuser'] as bool, automatedId: json['automated_id'] as String?, profile: SnAccountProfile.fromJson(json['profile'] as Map), diff --git a/lib/models/activity.dart b/lib/models/activity.dart index 23b06bd9..6255b7d2 100644 --- a/lib/models/activity.dart +++ b/lib/models/activity.dart @@ -4,6 +4,20 @@ import 'package:island/models/account.dart'; part 'activity.freezed.dart'; part 'activity.g.dart'; +@freezed +sealed class SnNotableDay with _$SnNotableDay { + const factory SnNotableDay({ + required DateTime date, + required String localName, + required String globalName, + required String countryCode, + required List holidays, + }) = _SnNotableDay; + + factory SnNotableDay.fromJson(Map json) => + _$SnNotableDayFromJson(json); +} + @freezed sealed class SnActivity with _$SnActivity { const factory SnActivity({ diff --git a/lib/models/activity.freezed.dart b/lib/models/activity.freezed.dart index e50a2a02..6fa72f25 100644 --- a/lib/models/activity.freezed.dart +++ b/lib/models/activity.freezed.dart @@ -12,6 +12,281 @@ part of 'activity.dart'; // dart format off T _$identity(T value) => value; +/// @nodoc +mixin _$SnNotableDay { + + DateTime get date; String get localName; String get globalName; String get countryCode; List get holidays; +/// Create a copy of SnNotableDay +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnNotableDayCopyWith get copyWith => _$SnNotableDayCopyWithImpl(this as SnNotableDay, _$identity); + + /// Serializes this SnNotableDay to a JSON map. + Map 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)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(holidays)); + +@override +String toString() { + return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)'; +} + + +} + +/// @nodoc +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 holidays +}); + + + + +} +/// @nodoc +class _$SnNotableDayCopyWithImpl<$Res> + implements $SnNotableDayCopyWith<$Res> { + _$SnNotableDayCopyWithImpl(this._self, this._then); + + final SnNotableDay _self; + final $Res Function(SnNotableDay) _then; + +/// 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,}) { + 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 List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SnNotableDay]. +extension SnNotableDayPatterns on SnNotableDay { +/// 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 Function( _SnNotableDay value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SnNotableDay() 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 Function( _SnNotableDay value) $default,){ +final _that = this; +switch (_that) { +case _SnNotableDay(): +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? Function( _SnNotableDay value)? $default,){ +final _that = this; +switch (_that) { +case _SnNotableDay() 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 Function( DateTime date, String localName, String globalName, String countryCode, List 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 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 Function( DateTime date, String localName, String globalName, String countryCode, List holidays) $default,) {final _that = this; +switch (_that) { +case _SnNotableDay(): +return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);} +} +/// 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? Function( DateTime date, String localName, String globalName, String countryCode, List 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 null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SnNotableDay implements SnNotableDay { + const _SnNotableDay({required this.date, required this.localName, required this.globalName, required this.countryCode, required final List holidays}): _holidays = holidays; + factory _SnNotableDay.fromJson(Map json) => _$SnNotableDayFromJson(json); + +@override final DateTime date; +@override final String localName; +@override final String globalName; +@override final String countryCode; + final List _holidays; +@override List get holidays { + if (_holidays is EqualUnmodifiableListView) return _holidays; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_holidays); +} + + +/// Create a copy of SnNotableDay +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnNotableDayCopyWith<_SnNotableDay> get copyWith => __$SnNotableDayCopyWithImpl<_SnNotableDay>(this, _$identity); + +@override +Map toJson() { + return _$SnNotableDayToJson(this, ); +} + +@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)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(_holidays)); + +@override +String toString() { + return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnNotableDayCopyWith<$Res> implements $SnNotableDayCopyWith<$Res> { + factory _$SnNotableDayCopyWith(_SnNotableDay value, $Res Function(_SnNotableDay) _then) = __$SnNotableDayCopyWithImpl; +@override @useResult +$Res call({ + DateTime date, String localName, String globalName, String countryCode, List holidays +}); + + + + +} +/// @nodoc +class __$SnNotableDayCopyWithImpl<$Res> + implements _$SnNotableDayCopyWith<$Res> { + __$SnNotableDayCopyWithImpl(this._self, this._then); + + final _SnNotableDay _self; + final $Res Function(_SnNotableDay) _then; + +/// 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,}) { + 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 List, + )); +} + + +} + + /// @nodoc mixin _$SnActivity { diff --git a/lib/models/activity.g.dart b/lib/models/activity.g.dart index b5ca4633..ff416167 100644 --- a/lib/models/activity.g.dart +++ b/lib/models/activity.g.dart @@ -6,6 +6,27 @@ part of 'activity.dart'; // JsonSerializableGenerator // ************************************************************************** +_SnNotableDay _$SnNotableDayFromJson(Map json) => + _SnNotableDay( + 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, + holidays: + (json['holidays'] as List) + .map((e) => (e as num).toInt()) + .toList(), + ); + +Map _$SnNotableDayToJson(_SnNotableDay instance) => + { + 'date': instance.date.toIso8601String(), + 'local_name': instance.localName, + 'global_name': instance.globalName, + 'country_code': instance.countryCode, + 'holidays': instance.holidays, + }; + _SnActivity _$SnActivityFromJson(Map json) => _SnActivity( id: json['id'] as String, type: json['type'] as String, diff --git a/lib/screens/posts/post_detail.dart b/lib/screens/posts/post_detail.dart index 60f0be51..c607e6fe 100644 --- a/lib/screens/posts/post_detail.dart +++ b/lib/screens/posts/post_detail.dart @@ -365,9 +365,7 @@ class PostActionButtons extends HookConsumerWidget { margin: const EdgeInsets.only(bottom: 12), child: ListView( scrollDirection: Axis.horizontal, - padding: EdgeInsets.symmetric( - horizontal: renderingPadding.horizontal + 4, - ), + padding: EdgeInsets.symmetric(horizontal: renderingPadding.horizontal), children: children, ), ); diff --git a/lib/widgets/account/event_details_widget.dart b/lib/widgets/account/event_details_widget.dart index d4054019..25102daf 100644 --- a/lib/widgets/account/event_details_widget.dart +++ b/lib/widgets/account/event_details_widget.dart @@ -46,6 +46,10 @@ class EventDetailsWidget extends StatelessWidget { size: 12, fill: 1, ).padding(top: 4, right: 4), + Icon( + tip.isPositive ? Symbols.thumb_up : Symbols.thumb_down, + size: 14, + ).padding(top: 2.5), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/widgets/check_in.dart b/lib/widgets/check_in.dart index 74fbdf06..dcbb9a5f 100644 --- a/lib/widgets/check_in.dart +++ b/lib/widgets/check_in.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -14,6 +15,7 @@ import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:slide_countdown/slide_countdown.dart'; import 'package:styled_widget/styled_widget.dart'; part 'check_in.g.dart'; @@ -34,6 +36,17 @@ Future checkInResultToday(Ref ref) async { } } +@riverpod +Future nextNotableDay(Ref ref) async { + final client = ref.watch(apiClientProvider); + try { + final resp = await client.get('/id/notable/me/next'); + return SnNotableDay.fromJson(resp.data); + } catch (err) { + return null; + } +} + class CheckInWidget extends HookConsumerWidget { final EdgeInsets? margin; final VoidCallback? onChecked; @@ -42,6 +55,22 @@ class CheckInWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final todayResult = ref.watch(checkInResultTodayProvider); + final nextNotableDay = ref.watch(nextNotableDayProvider); + + final userinfo = ref.watch(userInfoProvider); + final isAdult = useMemoized(() { + final birthday = userinfo.value?.profile.birthday; + if (birthday == null) return false; + final now = DateTime.now(); + final age = + now.year - + birthday.year - + ((now.month < birthday.month || + (now.month == birthday.month && now.day < birthday.day)) + ? 1 + : 0); + return age >= 18; + }, [userinfo]); Future checkIn({String? captchatTk}) async { final client = ref.read(apiClientProvider); @@ -71,119 +100,147 @@ class CheckInWidget extends HookConsumerWidget { return Card( margin: margin ?? EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), - child: Row( + child: Column( crossAxisAlignment: CrossAxisAlignment.center, - spacing: 16, + spacing: 8, children: [ - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Container( - color: Theme.of(context).colorScheme.secondaryContainer, - width: 56, - height: 56, - child: - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(DateFormat('EEE').format(DateTime.now())) - .fontSize(16) - .bold() - .textColor( - Theme.of(context).colorScheme.onSecondaryContainer, - ), - Text(DateFormat('MM/dd').format(DateTime.now())) - .fontSize(12) - .textColor( - Theme.of(context).colorScheme.onSecondaryContainer, - ), - ], - ).center(), - ), - ), - Expanded( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: todayResult.when( - data: (result) { - if (result == null) return _CheckInNoneWidget(); - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'checkInResultLevel${result.level}', - ).tr().fontSize(15).bold(), - Wrap( - children: - result.tips - .map((e) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - e.isPositive - ? Symbols.thumb_up - : Symbols.thumb_down, - size: 12, - ), - const Gap(4), - Text(e.title).fontSize(11), - ], - ); - }) - .toList() - .expand( - (widget) => [ - widget, - Text(' · ').fontSize(11), - ], - ) - .toList() - ..removeLast(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + switch (DateTime.now().weekday) { + 6 || 7 => Symbols.weekend, + _ => isAdult ? Symbols.work : Symbols.school, + }, + fill: 1, + size: 16, + ).padding(right: 2), + Text(DateFormat('EEE').format(DateTime.now())) + .fontSize(16) + .bold() + .textColor( + Theme.of(context).colorScheme.onSecondaryContainer, ), - ], - ); + Text(DateFormat('MM/dd').format(DateTime.now())) + .fontSize(12) + .textColor( + Theme.of(context).colorScheme.onSecondaryContainer, + ) + .padding(top: 2), + ], + ), + Row( + spacing: 5, + children: [ + Text('notableDayNext') + .tr(args: [nextNotableDay.value?.localName ?? 'idk']) + .fontSize(12), + SlideCountdown( + decoration: const BoxDecoration(), + style: const TextStyle(fontSize: 12), + separatorStyle: const TextStyle(fontSize: 12), + padding: EdgeInsets.zero, + duration: nextNotableDay.value?.date.difference( + DateTime.now(), + ), + ), + ], + ), + ], + ).padding(horizontal: 16, top: 8), + const Divider(height: 1), + Row( + children: [ + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: todayResult.when( + data: (result) { + if (result == null) return _CheckInNoneWidget(); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'checkInResultLevel${result.level}', + ).tr().fontSize(15).bold(), + Wrap( + children: + result.tips + .map((e) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + e.isPositive + ? Symbols.thumb_up + : Symbols.thumb_down, + size: 12, + ), + const Gap(4), + Text(e.title).fontSize(11), + ], + ); + }) + .toList() + .expand( + (widget) => [ + widget, + Text(' · ').fontSize(11), + ], + ) + .toList() + ..removeLast(), + ), + ], + ); + }, + loading: () => _CheckInNoneWidget(), + error: + (err, stack) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('error').tr().fontSize(15).bold(), + Text(err.toString()).fontSize(11), + ], + ), + ), + ), + ), + IconButton.outlined( + onPressed: () { + if (todayResult.valueOrNull == null) { + checkIn(); + } else { + context.pushNamed( + 'accountCalendar', + pathParameters: {'name': 'me'}, + ); + } }, - loading: () => _CheckInNoneWidget(), - error: - (err, stack) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('error').tr().fontSize(15).bold(), - Text(err.toString()).fontSize(11), - ], - ), + icon: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: todayResult.when( + data: + (result) => Icon( + result == null + ? Symbols.local_fire_department + : Symbols.event, + key: ValueKey(result != null), + ), + loading: () => const Icon(Symbols.refresh), + error: (_, _) => const Icon(Symbols.error), + ), + ), ), - ), - ), - IconButton.outlined( - onPressed: () { - if (todayResult.valueOrNull == null) { - checkIn(); - } else { - context.pushNamed( - 'accountCalendar', - pathParameters: {'name': 'me'}, - ); - } - }, - icon: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: todayResult.when( - data: - (result) => Icon( - result == null - ? Symbols.local_fire_department - : Symbols.event, - key: ValueKey(result != null), - ), - loading: () => const Icon(Symbols.refresh), - error: (_, _) => const Icon(Symbols.error), - ), - ), - ), + ], + ).padding(horizontal: 16, bottom: 12, top: 4), ], - ).padding(horizontal: 16, vertical: 12), + ), ); } } diff --git a/lib/widgets/check_in.g.dart b/lib/widgets/check_in.g.dart index dad57455..84a76b2b 100644 --- a/lib/widgets/check_in.g.dart +++ b/lib/widgets/check_in.g.dart @@ -26,5 +26,24 @@ final checkInResultTodayProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef CheckInResultTodayRef = AutoDisposeFutureProviderRef; +String _$nextNotableDayHash() => r'698370bec4be28774d332412c5a701f914064c90'; + +/// See also [nextNotableDay]. +@ProviderFor(nextNotableDay) +final nextNotableDayProvider = + AutoDisposeFutureProvider.internal( + nextNotableDay, + name: r'nextNotableDayProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$nextNotableDayHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef NextNotableDayRef = 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/content/attachment_preview.dart b/lib/widgets/content/attachment_preview.dart index da611a47..9f09c950 100644 --- a/lib/widgets/content/attachment_preview.dart +++ b/lib/widgets/content/attachment_preview.dart @@ -321,7 +321,7 @@ class AttachmentPreview extends HookConsumerWidget { children: [ Icon(fallbackIcon), const Gap(6), - Text(file.name), + Text(file.name, textAlign: TextAlign.center), FutureBuilder( future: file.length(), builder: (context, snapshot) { diff --git a/pubspec.lock b/pubspec.lock index a44e746a..d6c0ae40 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1789,6 +1789,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pausable_timer: + dependency: transitive + description: + name: pausable_timer + sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074" + url: "https://pub.dev" + source: hosted + version: "3.1.0+3" petitparser: dependency: transitive description: @@ -2258,6 +2266,14 @@ packages: description: flutter source: sdk version: "0.0.0" + slide_countdown: + dependency: "direct main" + description: + name: slide_countdown + sha256: "363914f96389502467d4dc9c0f26e88f93df3d8e37de2d5ff05b16d981fe973d" + url: "https://pub.dev" + source: hosted + version: "2.0.2" source_gen: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 944189d8..111642af 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -144,6 +144,7 @@ dependencies: flutter_webrtc: ^1.1.0 flutter_local_notifications: ^19.4.1 wakelock_plus: ^1.3.2 + slide_countdown: ^2.0.2 dev_dependencies: flutter_test: