✨ Notable day countdown
This commit is contained in:
		| @@ -976,5 +976,6 @@ | |||||||
|   "shuffle": "Shuffle", |   "shuffle": "Shuffle", | ||||||
|   "pinned": "Pinned", |   "pinned": "Pinned", | ||||||
|   "noResultsFound": "No results found", |   "noResultsFound": "No results found", | ||||||
|   "toggleFilters": "Toggle filters" |   "toggleFilters": "Toggle filters", | ||||||
|  |   "notableDayNext": "{} is in" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -863,5 +863,6 @@ | |||||||
|   "statusPresent": "至今", |   "statusPresent": "至今", | ||||||
|   "accountAutomated": "机器人", |   "accountAutomated": "机器人", | ||||||
|   "openInBrowser": "在浏览器中打开", |   "openInBrowser": "在浏览器中打开", | ||||||
|   "highlightPost": "精选帖子" |   "highlightPost": "精选帖子", | ||||||
|  |   "notableDayNext": "距离 {} 还有" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ sealed class SnAccount with _$SnAccount { | |||||||
|     required String name, |     required String name, | ||||||
|     required String nick, |     required String nick, | ||||||
|     required String language, |     required String language, | ||||||
|     required String region, |     @Default("") String region, | ||||||
|     required bool isSuperuser, |     required bool isSuperuser, | ||||||
|     required String? automatedId, |     required String? automatedId, | ||||||
|     required SnAccountProfile profile, |     required SnAccountProfile profile, | ||||||
|   | |||||||
| @@ -236,14 +236,14 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnAccount implements SnAccount { | 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<SnAccountBadge> 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<SnAccountBadge> badges = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges; | ||||||
|   factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json); |   factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @override final  String name; | @override final  String name; | ||||||
| @override final  String nick; | @override final  String nick; | ||||||
| @override final  String language; | @override final  String language; | ||||||
| @override final  String region; | @override@JsonKey() final  String region; | ||||||
| @override final  bool isSuperuser; | @override final  bool isSuperuser; | ||||||
| @override final  String? automatedId; | @override final  String? automatedId; | ||||||
| @override final  SnAccountProfile profile; | @override final  SnAccountProfile profile; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ _SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount( | |||||||
|   name: json['name'] as String, |   name: json['name'] as String, | ||||||
|   nick: json['nick'] as String, |   nick: json['nick'] as String, | ||||||
|   language: json['language'] as String, |   language: json['language'] as String, | ||||||
|   region: json['region'] as String, |   region: json['region'] as String? ?? "", | ||||||
|   isSuperuser: json['is_superuser'] as bool, |   isSuperuser: json['is_superuser'] as bool, | ||||||
|   automatedId: json['automated_id'] as String?, |   automatedId: json['automated_id'] as String?, | ||||||
|   profile: SnAccountProfile.fromJson(json['profile'] as Map<String, dynamic>), |   profile: SnAccountProfile.fromJson(json['profile'] as Map<String, dynamic>), | ||||||
|   | |||||||
| @@ -4,6 +4,20 @@ import 'package:island/models/account.dart'; | |||||||
| part 'activity.freezed.dart'; | part 'activity.freezed.dart'; | ||||||
| part 'activity.g.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<int> holidays, | ||||||
|  |   }) = _SnNotableDay; | ||||||
|  |  | ||||||
|  |   factory SnNotableDay.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnNotableDayFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class SnActivity with _$SnActivity { | sealed class SnActivity with _$SnActivity { | ||||||
|   const factory SnActivity({ |   const factory SnActivity({ | ||||||
|   | |||||||
| @@ -12,6 +12,281 @@ part of 'activity.dart'; | |||||||
| // dart format off | // dart format off | ||||||
| T _$identity<T>(T value) => value; | 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; | ||||||
|  | /// 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<SnNotableDay> get copyWith => _$SnNotableDayCopyWithImpl<SnNotableDay>(this as SnNotableDay, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnNotableDay to a JSON map. | ||||||
|  |   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)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @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<int> 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<int>, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// 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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(TResult Function( DateTime date,  String localName,  String globalName,  String countryCode,  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 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( DateTime date,  String localName,  String globalName,  String countryCode,  List<int> 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 extends Object?>(TResult? Function( DateTime date,  String localName,  String globalName,  String countryCode,  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 null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @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; | ||||||
|  |   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; | ||||||
|  |  final  List<int> _holidays; | ||||||
|  | @override List<int> 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<String, dynamic> 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<int> 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<int>, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnActivity { | mixin _$SnActivity { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,27 @@ part of 'activity.dart'; | |||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _SnNotableDay _$SnNotableDayFromJson(Map<String, dynamic> 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<dynamic>) | ||||||
|  |               .map((e) => (e as num).toInt()) | ||||||
|  |               .toList(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnNotableDayToJson(_SnNotableDay instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'date': instance.date.toIso8601String(), | ||||||
|  |       'local_name': instance.localName, | ||||||
|  |       'global_name': instance.globalName, | ||||||
|  |       'country_code': instance.countryCode, | ||||||
|  |       'holidays': instance.holidays, | ||||||
|  |     }; | ||||||
|  |  | ||||||
| _SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity( | _SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity( | ||||||
|   id: json['id'] as String, |   id: json['id'] as String, | ||||||
|   type: json['type'] as String, |   type: json['type'] as String, | ||||||
|   | |||||||
| @@ -365,9 +365,7 @@ class PostActionButtons extends HookConsumerWidget { | |||||||
|       margin: const EdgeInsets.only(bottom: 12), |       margin: const EdgeInsets.only(bottom: 12), | ||||||
|       child: ListView( |       child: ListView( | ||||||
|         scrollDirection: Axis.horizontal, |         scrollDirection: Axis.horizontal, | ||||||
|         padding: EdgeInsets.symmetric( |         padding: EdgeInsets.symmetric(horizontal: renderingPadding.horizontal), | ||||||
|           horizontal: renderingPadding.horizontal + 4, |  | ||||||
|         ), |  | ||||||
|         children: children, |         children: children, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -46,6 +46,10 @@ class EventDetailsWidget extends StatelessWidget { | |||||||
|                       size: 12, |                       size: 12, | ||||||
|                       fill: 1, |                       fill: 1, | ||||||
|                     ).padding(top: 4, right: 4), |                     ).padding(top: 4, right: 4), | ||||||
|  |                     Icon( | ||||||
|  |                       tip.isPositive ? Symbols.thumb_up : Symbols.thumb_down, | ||||||
|  |                       size: 14, | ||||||
|  |                     ).padding(top: 2.5), | ||||||
|                     Expanded( |                     Expanded( | ||||||
|                       child: Column( |                       child: Column( | ||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'dart:convert'; | |||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | 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:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.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:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:slide_countdown/slide_countdown.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| part 'check_in.g.dart'; | part 'check_in.g.dart'; | ||||||
| @@ -34,6 +36,17 @@ Future<SnCheckInResult?> checkInResultToday(Ref ref) async { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnNotableDay?> 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 { | class CheckInWidget extends HookConsumerWidget { | ||||||
|   final EdgeInsets? margin; |   final EdgeInsets? margin; | ||||||
|   final VoidCallback? onChecked; |   final VoidCallback? onChecked; | ||||||
| @@ -42,6 +55,22 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final todayResult = ref.watch(checkInResultTodayProvider); |     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<void> checkIn({String? captchatTk}) async { |     Future<void> checkIn({String? captchatTk}) async { | ||||||
|       final client = ref.read(apiClientProvider); |       final client = ref.read(apiClientProvider); | ||||||
| @@ -71,21 +100,26 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|     return Card( |     return Card( | ||||||
|       margin: |       margin: | ||||||
|           margin ?? EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), |           margin ?? EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), | ||||||
|       child: Row( |       child: Column( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.center, |         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|         spacing: 16, |         spacing: 8, | ||||||
|         children: [ |         children: [ | ||||||
|           ClipRRect( |  | ||||||
|             borderRadius: BorderRadius.circular(8), |  | ||||||
|             child: Container( |  | ||||||
|               color: Theme.of(context).colorScheme.secondaryContainer, |  | ||||||
|               width: 56, |  | ||||||
|               height: 56, |  | ||||||
|               child: |  | ||||||
|           Column( |           Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |             children: [ | ||||||
|  |               Row( | ||||||
|  |                 spacing: 8, | ||||||
|                 mainAxisSize: MainAxisSize.min, |                 mainAxisSize: MainAxisSize.min, | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.center, |                 crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                 children: [ |                 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())) |                   Text(DateFormat('EEE').format(DateTime.now())) | ||||||
|                       .fontSize(16) |                       .fontSize(16) | ||||||
|                       .bold() |                       .bold() | ||||||
| @@ -96,11 +130,32 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|                       .fontSize(12) |                       .fontSize(12) | ||||||
|                       .textColor( |                       .textColor( | ||||||
|                         Theme.of(context).colorScheme.onSecondaryContainer, |                         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(), | ||||||
|  |                     ), | ||||||
|                   ), |                   ), | ||||||
|                 ], |                 ], | ||||||
|                   ).center(), |  | ||||||
|             ), |  | ||||||
|               ), |               ), | ||||||
|  |             ], | ||||||
|  |           ).padding(horizontal: 16, top: 8), | ||||||
|  |           const Divider(height: 1), | ||||||
|  |           Row( | ||||||
|  |             children: [ | ||||||
|               Expanded( |               Expanded( | ||||||
|                 child: AnimatedSwitcher( |                 child: AnimatedSwitcher( | ||||||
|                   duration: const Duration(milliseconds: 300), |                   duration: const Duration(milliseconds: 300), | ||||||
| @@ -183,7 +238,9 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ], |             ], | ||||||
|       ).padding(horizontal: 16, vertical: 12), |           ).padding(horizontal: 16, bottom: 12, top: 4), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,5 +26,24 @@ final checkInResultTodayProvider = | |||||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
| // ignore: unused_element | // ignore: unused_element | ||||||
| typedef CheckInResultTodayRef = AutoDisposeFutureProviderRef<SnCheckInResult?>; | typedef CheckInResultTodayRef = AutoDisposeFutureProviderRef<SnCheckInResult?>; | ||||||
|  | String _$nextNotableDayHash() => r'698370bec4be28774d332412c5a701f914064c90'; | ||||||
|  |  | ||||||
|  | /// See also [nextNotableDay]. | ||||||
|  | @ProviderFor(nextNotableDay) | ||||||
|  | final nextNotableDayProvider = | ||||||
|  |     AutoDisposeFutureProvider<SnNotableDay?>.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<SnNotableDay?>; | ||||||
| // ignore_for_file: type=lint | // 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 | // 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 | ||||||
|   | |||||||
| @@ -321,7 +321,7 @@ class AttachmentPreview extends HookConsumerWidget { | |||||||
|                               children: [ |                               children: [ | ||||||
|                                 Icon(fallbackIcon), |                                 Icon(fallbackIcon), | ||||||
|                                 const Gap(6), |                                 const Gap(6), | ||||||
|                                 Text(file.name), |                                 Text(file.name, textAlign: TextAlign.center), | ||||||
|                                 FutureBuilder( |                                 FutureBuilder( | ||||||
|                                   future: file.length(), |                                   future: file.length(), | ||||||
|                                   builder: (context, snapshot) { |                                   builder: (context, snapshot) { | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -1789,6 +1789,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.0" |     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: |   petitparser: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -2258,6 +2266,14 @@ packages: | |||||||
|     description: flutter |     description: flutter | ||||||
|     source: sdk |     source: sdk | ||||||
|     version: "0.0.0" |     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: |   source_gen: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -144,6 +144,7 @@ dependencies: | |||||||
|   flutter_webrtc: ^1.1.0 |   flutter_webrtc: ^1.1.0 | ||||||
|   flutter_local_notifications: ^19.4.1 |   flutter_local_notifications: ^19.4.1 | ||||||
|   wakelock_plus: ^1.3.2 |   wakelock_plus: ^1.3.2 | ||||||
|  |   slide_countdown: ^2.0.2 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user