Compare commits
	
		
			15 Commits
		
	
	
		
			3.2.0+127
			...
			a127b5bace
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a127b5bace | |||
| b2097cf044 | |||
| 701f29748d | |||
| 9e40ed4600 | |||
| c90e6fe661 | |||
| 569483300d | |||
| bab602d98b | |||
| b4f2bb803a | |||
| 03bfed6f46 | |||
| f98e5a0aec | |||
| 3d473e2fec | |||
| 0b6efa373a | |||
| 9b60e96cde | |||
| 81cd9b2082 | |||
| 923d5d7514 | 
| @@ -854,5 +854,26 @@ | |||||||
|   "failedToLoadUserInfo": "Failed to load user info", |   "failedToLoadUserInfo": "Failed to load user info", | ||||||
|   "failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.", |   "failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.", | ||||||
|   "failedToLoadUserInfoUnauthorized": "It seems your session has been logged out or not available anymore, you can still try agian to fetch the user info if you want.", |   "failedToLoadUserInfoUnauthorized": "It seems your session has been logged out or not available anymore, you can still try agian to fetch the user info if you want.", | ||||||
|   "okay": "Okay" |   "okay": "Okay", | ||||||
|  |   "postDetails": "Post Details", | ||||||
|  |   "postCount": { | ||||||
|  |     "zero": "No posts", | ||||||
|  |     "one": "{} post", | ||||||
|  |     "other": "{} posts" | ||||||
|  |   }, | ||||||
|  |   "mimeType": "MIME Type", | ||||||
|  |   "fileSize": "File Size", | ||||||
|  |   "fileHash": "File Hash", | ||||||
|  |   "exifData": "EXIF Data", | ||||||
|  |   "postShuffle": "Shuffle Posts", | ||||||
|  |   "leveling": "Leveling", | ||||||
|  |   "levelingHistory": "Leveling History", | ||||||
|  |   "stellarProgram": "Stellar Program", | ||||||
|  |   "socialCredits": "Social Credits", | ||||||
|  |   "credits": "Credits", | ||||||
|  |   "socialCreditsDescription": "Social Credit is a way for Solar Network to evaluate users. It is calculated based on their behavior and interactions. With a base score of 100, higher scores indicate a user's credibility within the community. Scores change over time to reflect a user's recent behavior. Users with higher credit ratings enjoy more benefits, while users with lower credit ratings may have some functionality restricted.", | ||||||
|  |   "socialCreditsLevelPoor": "Poor", | ||||||
|  |   "socialCreditsLevelNormal": "Normal", | ||||||
|  |   "socialCreditsLevelGood": "Good", | ||||||
|  |   "socialCreditsLevelExcellent": "Excellent" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -828,5 +828,20 @@ | |||||||
|   "failedToLoadUserInfo": "加载用户信息失败", |   "failedToLoadUserInfo": "加载用户信息失败", | ||||||
|   "failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试", |   "failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试", | ||||||
|   "failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。", |   "failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。", | ||||||
|   "okay": "了解" |   "okay": "了解", | ||||||
|  |   "postDetails": "帖子详情", | ||||||
|  |   "mimeType": "类型", | ||||||
|  |   "fileSize": "大小", | ||||||
|  |   "fileHash": "哈希", | ||||||
|  |   "exifData": "EXIF 数据", | ||||||
|  |   "leveling": "等级", | ||||||
|  |   "levelingHistory": "经验记录", | ||||||
|  |   "stellarProgram": "恒星计划", | ||||||
|  |   "socialCredits": "社会信用点", | ||||||
|  |   "credits": "信用", | ||||||
|  |   "socialCreditsDescription": "社会信用是 Solar Network 评价用户的一种方式。它基于用户的行为和互动来计算。以 100 分为基准,分数越高表示用户在社区中的信誉越好。分数会随着时间的推移而变化,反映用户的最新行为。信用等级高的用户可以享受到更多的福利,反之的用户部份功能可能受到限制。", | ||||||
|  |   "socialCreditsLevelPoor": "糟糕", | ||||||
|  |   "socialCreditsLevelNormal": "正常", | ||||||
|  |   "socialCreditsLevelGood": "良好", | ||||||
|  |   "socialCreditsLevelExcellent": "优秀" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -208,3 +208,37 @@ sealed class SnAuthDeviceWithChallenge with _$SnAuthDeviceWithChallenge { | |||||||
|   factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) => |   factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) => | ||||||
|       _$SnAuthDeviceWithChallengeFromJson(json); |       _$SnAuthDeviceWithChallengeFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnExperienceRecord with _$SnExperienceRecord { | ||||||
|  |   const factory SnExperienceRecord({ | ||||||
|  |     required String id, | ||||||
|  |     required int delta, | ||||||
|  |     required String reasonType, | ||||||
|  |     required String reason, | ||||||
|  |     @Default(1.0) double? bonusMultiplier, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnExperienceRecord; | ||||||
|  |  | ||||||
|  |   factory SnExperienceRecord.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnExperienceRecordFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnSocialCreditRecord with _$SnSocialCreditRecord { | ||||||
|  |   const factory SnSocialCreditRecord({ | ||||||
|  |     required String id, | ||||||
|  |     required double delta, | ||||||
|  |     required String reasonType, | ||||||
|  |     required String reason, | ||||||
|  |     required DateTime? expiredAt, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnSocialCreditRecord; | ||||||
|  |  | ||||||
|  |   factory SnSocialCreditRecord.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnSocialCreditRecordFromJson(json); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3018,6 +3018,562 @@ as bool, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnExperienceRecord { | ||||||
|  |  | ||||||
|  |  String get id; int get delta; String get reasonType; String get reason; double? get bonusMultiplier; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnExperienceRecordCopyWith<SnExperienceRecord> get copyWith => _$SnExperienceRecordCopyWithImpl<SnExperienceRecord>(this as SnExperienceRecord, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnExperienceRecord to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnExperienceRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.bonusMultiplier, bonusMultiplier) || other.bonusMultiplier == bonusMultiplier)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,delta,reasonType,reason,bonusMultiplier,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnExperienceRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, bonusMultiplier: $bonusMultiplier, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnExperienceRecordCopyWith<$Res>  { | ||||||
|  |   factory $SnExperienceRecordCopyWith(SnExperienceRecord value, $Res Function(SnExperienceRecord) _then) = _$SnExperienceRecordCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, int delta, String reasonType, String reason, double? bonusMultiplier, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnExperienceRecordCopyWithImpl<$Res> | ||||||
|  |     implements $SnExperienceRecordCopyWith<$Res> { | ||||||
|  |   _$SnExperienceRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnExperienceRecord _self; | ||||||
|  |   final $Res Function(SnExperienceRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? bonusMultiplier = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,bonusMultiplier: freezed == bonusMultiplier ? _self.bonusMultiplier : bonusMultiplier // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnExperienceRecord]. | ||||||
|  | extension SnExperienceRecordPatterns on SnExperienceRecord { | ||||||
|  | /// 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( _SnExperienceRecord value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() 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( _SnExperienceRecord value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord(): | ||||||
|  | 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( _SnExperienceRecord value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() 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 id,  int delta,  String reasonType,  String reason,  double? bonusMultiplier,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.bonusMultiplier,_that.createdAt,_that.updatedAt,_that.deletedAt);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 id,  int delta,  String reasonType,  String reason,  double? bonusMultiplier,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord(): | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.bonusMultiplier,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
|  | } | ||||||
|  | /// 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 id,  int delta,  String reasonType,  String reason,  double? bonusMultiplier,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.bonusMultiplier,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnExperienceRecord implements SnExperienceRecord { | ||||||
|  |   const _SnExperienceRecord({required this.id, required this.delta, required this.reasonType, required this.reason, this.bonusMultiplier = 1.0, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||||
|  |   factory _SnExperienceRecord.fromJson(Map<String, dynamic> json) => _$SnExperienceRecordFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  int delta; | ||||||
|  | @override final  String reasonType; | ||||||
|  | @override final  String reason; | ||||||
|  | @override@JsonKey() final  double? bonusMultiplier; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnExperienceRecordCopyWith<_SnExperienceRecord> get copyWith => __$SnExperienceRecordCopyWithImpl<_SnExperienceRecord>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnExperienceRecordToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnExperienceRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.bonusMultiplier, bonusMultiplier) || other.bonusMultiplier == bonusMultiplier)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,delta,reasonType,reason,bonusMultiplier,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnExperienceRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, bonusMultiplier: $bonusMultiplier, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnExperienceRecordCopyWith<$Res> implements $SnExperienceRecordCopyWith<$Res> { | ||||||
|  |   factory _$SnExperienceRecordCopyWith(_SnExperienceRecord value, $Res Function(_SnExperienceRecord) _then) = __$SnExperienceRecordCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, int delta, String reasonType, String reason, double? bonusMultiplier, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnExperienceRecordCopyWithImpl<$Res> | ||||||
|  |     implements _$SnExperienceRecordCopyWith<$Res> { | ||||||
|  |   __$SnExperienceRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnExperienceRecord _self; | ||||||
|  |   final $Res Function(_SnExperienceRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? bonusMultiplier = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_SnExperienceRecord( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,bonusMultiplier: freezed == bonusMultiplier ? _self.bonusMultiplier : bonusMultiplier // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnSocialCreditRecord { | ||||||
|  |  | ||||||
|  |  String get id; double get delta; String get reasonType; String get reason; DateTime? get expiredAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnSocialCreditRecordCopyWith<SnSocialCreditRecord> get copyWith => _$SnSocialCreditRecordCopyWithImpl<SnSocialCreditRecord>(this as SnSocialCreditRecord, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnSocialCreditRecord to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnSocialCreditRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,delta,reasonType,reason,expiredAt,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnSocialCreditRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, expiredAt: $expiredAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnSocialCreditRecordCopyWith<$Res>  { | ||||||
|  |   factory $SnSocialCreditRecordCopyWith(SnSocialCreditRecord value, $Res Function(SnSocialCreditRecord) _then) = _$SnSocialCreditRecordCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, double delta, String reasonType, String reason, DateTime? expiredAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnSocialCreditRecordCopyWithImpl<$Res> | ||||||
|  |     implements $SnSocialCreditRecordCopyWith<$Res> { | ||||||
|  |   _$SnSocialCreditRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnSocialCreditRecord _self; | ||||||
|  |   final $Res Function(SnSocialCreditRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? expiredAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnSocialCreditRecord]. | ||||||
|  | extension SnSocialCreditRecordPatterns on SnSocialCreditRecord { | ||||||
|  | /// 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( _SnSocialCreditRecord value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() 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( _SnSocialCreditRecord value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord(): | ||||||
|  | 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( _SnSocialCreditRecord value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() 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 id,  double delta,  String reasonType,  String reason,  DateTime? expiredAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.expiredAt,_that.createdAt,_that.updatedAt,_that.deletedAt);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 id,  double delta,  String reasonType,  String reason,  DateTime? expiredAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord(): | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.expiredAt,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
|  | } | ||||||
|  | /// 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 id,  double delta,  String reasonType,  String reason,  DateTime? expiredAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.expiredAt,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnSocialCreditRecord implements SnSocialCreditRecord { | ||||||
|  |   const _SnSocialCreditRecord({required this.id, required this.delta, required this.reasonType, required this.reason, required this.expiredAt, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||||
|  |   factory _SnSocialCreditRecord.fromJson(Map<String, dynamic> json) => _$SnSocialCreditRecordFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  double delta; | ||||||
|  | @override final  String reasonType; | ||||||
|  | @override final  String reason; | ||||||
|  | @override final  DateTime? expiredAt; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnSocialCreditRecordCopyWith<_SnSocialCreditRecord> get copyWith => __$SnSocialCreditRecordCopyWithImpl<_SnSocialCreditRecord>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnSocialCreditRecordToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnSocialCreditRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,delta,reasonType,reason,expiredAt,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnSocialCreditRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, expiredAt: $expiredAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnSocialCreditRecordCopyWith<$Res> implements $SnSocialCreditRecordCopyWith<$Res> { | ||||||
|  |   factory _$SnSocialCreditRecordCopyWith(_SnSocialCreditRecord value, $Res Function(_SnSocialCreditRecord) _then) = __$SnSocialCreditRecordCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, double delta, String reasonType, String reason, DateTime? expiredAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnSocialCreditRecordCopyWithImpl<$Res> | ||||||
|  |     implements _$SnSocialCreditRecordCopyWith<$Res> { | ||||||
|  |   __$SnSocialCreditRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnSocialCreditRecord _self; | ||||||
|  |   final $Res Function(_SnSocialCreditRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? expiredAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_SnSocialCreditRecord( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // dart format on | // dart format on | ||||||
|   | |||||||
| @@ -348,3 +348,62 @@ Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson( | |||||||
|   'challenges': instance.challenges.map((e) => e.toJson()).toList(), |   'challenges': instance.challenges.map((e) => e.toJson()).toList(), | ||||||
|   'is_current': instance.isCurrent, |   'is_current': instance.isCurrent, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | _SnExperienceRecord _$SnExperienceRecordFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnExperienceRecord( | ||||||
|  |       id: json['id'] as String, | ||||||
|  |       delta: (json['delta'] as num).toInt(), | ||||||
|  |       reasonType: json['reason_type'] as String, | ||||||
|  |       reason: json['reason'] as String, | ||||||
|  |       bonusMultiplier: (json['bonus_multiplier'] as num?)?.toDouble() ?? 1.0, | ||||||
|  |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |       deletedAt: | ||||||
|  |           json['deleted_at'] == null | ||||||
|  |               ? null | ||||||
|  |               : DateTime.parse(json['deleted_at'] as String), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnExperienceRecordToJson(_SnExperienceRecord instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'delta': instance.delta, | ||||||
|  |       'reason_type': instance.reasonType, | ||||||
|  |       'reason': instance.reason, | ||||||
|  |       'bonus_multiplier': instance.bonusMultiplier, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _SnSocialCreditRecord _$SnSocialCreditRecordFromJson( | ||||||
|  |   Map<String, dynamic> json, | ||||||
|  | ) => _SnSocialCreditRecord( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   delta: (json['delta'] as num).toDouble(), | ||||||
|  |   reasonType: json['reason_type'] as String, | ||||||
|  |   reason: json['reason'] as String, | ||||||
|  |   expiredAt: | ||||||
|  |       json['expired_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['expired_at'] as String), | ||||||
|  |   createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |   deletedAt: | ||||||
|  |       json['deleted_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['deleted_at'] as String), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnSocialCreditRecordToJson( | ||||||
|  |   _SnSocialCreditRecord instance, | ||||||
|  | ) => <String, dynamic>{ | ||||||
|  |   'id': instance.id, | ||||||
|  |   'delta': instance.delta, | ||||||
|  |   'reason_type': instance.reasonType, | ||||||
|  |   'reason': instance.reason, | ||||||
|  |   'expired_at': instance.expiredAt?.toIso8601String(), | ||||||
|  |   'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |   'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |   'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ sealed class SnChatMember with _$SnChatMember { | |||||||
| sealed class SnChatSummary with _$SnChatSummary { | sealed class SnChatSummary with _$SnChatSummary { | ||||||
|   const factory SnChatSummary({ |   const factory SnChatSummary({ | ||||||
|     required int unreadCount, |     required int unreadCount, | ||||||
|     required SnChatMessage lastMessage, |     required SnChatMessage? lastMessage, | ||||||
|   }) = _SnChatSummary; |   }) = _SnChatSummary; | ||||||
|  |  | ||||||
|   factory SnChatSummary.fromJson(Map<String, dynamic> json) => |   factory SnChatSummary.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -1410,7 +1410,7 @@ $SnAccountStatusCopyWith<$Res>? get status { | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChatSummary { | mixin _$SnChatSummary { | ||||||
|  |  | ||||||
|  int get unreadCount; SnChatMessage get lastMessage; |  int get unreadCount; SnChatMessage? get lastMessage; | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -1443,11 +1443,11 @@ abstract mixin class $SnChatSummaryCopyWith<$Res>  { | |||||||
|   factory $SnChatSummaryCopyWith(SnChatSummary value, $Res Function(SnChatSummary) _then) = _$SnChatSummaryCopyWithImpl; |   factory $SnChatSummaryCopyWith(SnChatSummary value, $Res Function(SnChatSummary) _then) = _$SnChatSummaryCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  int unreadCount, SnChatMessage lastMessage |  int unreadCount, SnChatMessage? lastMessage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage; | $SnChatMessageCopyWith<$Res>? get lastMessage; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1460,20 +1460,23 @@ class _$SnChatSummaryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? unreadCount = null,Object? lastMessage = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? unreadCount = null,Object? lastMessage = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | ||||||
| as int,lastMessage: null == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | as int,lastMessage: freezed == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | ||||||
| as SnChatMessage, | as SnChatMessage?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage { | $SnChatMessageCopyWith<$Res>? get lastMessage { | ||||||
|  |     if (_self.lastMessage == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) { |   return $SnChatMessageCopyWith<$Res>(_self.lastMessage!, (value) { | ||||||
|     return _then(_self.copyWith(lastMessage: value)); |     return _then(_self.copyWith(lastMessage: value)); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| @@ -1555,7 +1558,7 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage lastMessage)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage? lastMessage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary() when $default != null: | case _SnChatSummary() when $default != null: | ||||||
| return $default(_that.unreadCount,_that.lastMessage);case _: | return $default(_that.unreadCount,_that.lastMessage);case _: | ||||||
| @@ -1576,7 +1579,7 @@ return $default(_that.unreadCount,_that.lastMessage);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage lastMessage)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage? lastMessage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary(): | case _SnChatSummary(): | ||||||
| return $default(_that.unreadCount,_that.lastMessage);} | return $default(_that.unreadCount,_that.lastMessage);} | ||||||
| @@ -1593,7 +1596,7 @@ return $default(_that.unreadCount,_that.lastMessage);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int unreadCount,  SnChatMessage lastMessage)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int unreadCount,  SnChatMessage? lastMessage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary() when $default != null: | case _SnChatSummary() when $default != null: | ||||||
| return $default(_that.unreadCount,_that.lastMessage);case _: | return $default(_that.unreadCount,_that.lastMessage);case _: | ||||||
| @@ -1612,7 +1615,7 @@ class _SnChatSummary implements SnChatSummary { | |||||||
|   factory _SnChatSummary.fromJson(Map<String, dynamic> json) => _$SnChatSummaryFromJson(json); |   factory _SnChatSummary.fromJson(Map<String, dynamic> json) => _$SnChatSummaryFromJson(json); | ||||||
|  |  | ||||||
| @override final  int unreadCount; | @override final  int unreadCount; | ||||||
| @override final  SnChatMessage lastMessage; | @override final  SnChatMessage? lastMessage; | ||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -1647,11 +1650,11 @@ abstract mixin class _$SnChatSummaryCopyWith<$Res> implements $SnChatSummaryCopy | |||||||
|   factory _$SnChatSummaryCopyWith(_SnChatSummary value, $Res Function(_SnChatSummary) _then) = __$SnChatSummaryCopyWithImpl; |   factory _$SnChatSummaryCopyWith(_SnChatSummary value, $Res Function(_SnChatSummary) _then) = __$SnChatSummaryCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  int unreadCount, SnChatMessage lastMessage |  int unreadCount, SnChatMessage? lastMessage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override $SnChatMessageCopyWith<$Res> get lastMessage; | @override $SnChatMessageCopyWith<$Res>? get lastMessage; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1664,11 +1667,11 @@ class __$SnChatSummaryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? unreadCount = null,Object? lastMessage = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? unreadCount = null,Object? lastMessage = freezed,}) { | ||||||
|   return _then(_SnChatSummary( |   return _then(_SnChatSummary( | ||||||
| unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | ||||||
| as int,lastMessage: null == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | as int,lastMessage: freezed == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | ||||||
| as SnChatMessage, | as SnChatMessage?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1676,9 +1679,12 @@ as SnChatMessage, | |||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage { | $SnChatMessageCopyWith<$Res>? get lastMessage { | ||||||
|  |     if (_self.lastMessage == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) { |   return $SnChatMessageCopyWith<$Res>(_self.lastMessage!, (value) { | ||||||
|     return _then(_self.copyWith(lastMessage: value)); |     return _then(_self.copyWith(lastMessage: value)); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -213,7 +213,10 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) => | |||||||
| _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | ||||||
|     _SnChatSummary( |     _SnChatSummary( | ||||||
|       unreadCount: (json['unread_count'] as num).toInt(), |       unreadCount: (json['unread_count'] as num).toInt(), | ||||||
|       lastMessage: SnChatMessage.fromJson( |       lastMessage: | ||||||
|  |           json['last_message'] == null | ||||||
|  |               ? null | ||||||
|  |               : SnChatMessage.fromJson( | ||||||
|                 json['last_message'] as Map<String, dynamic>, |                 json['last_message'] as Map<String, dynamic>, | ||||||
|               ), |               ), | ||||||
|     ); |     ); | ||||||
| @@ -221,7 +224,7 @@ _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | |||||||
| Map<String, dynamic> _$SnChatSummaryToJson(_SnChatSummary instance) => | Map<String, dynamic> _$SnChatSummaryToJson(_SnChatSummary instance) => | ||||||
|     <String, dynamic>{ |     <String, dynamic>{ | ||||||
|       'unread_count': instance.unreadCount, |       'unread_count': instance.unreadCount, | ||||||
|       'last_message': instance.lastMessage.toJson(), |       'last_message': instance.lastMessage?.toJson(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| _MessageChange _$MessageChangeFromJson(Map<String, dynamic> json) => | _MessageChange _$MessageChangeFromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ sealed class SnPostCategory with _$SnPostCategory { | |||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|  |     @Default(0) int usage, | ||||||
|   }) = _SnPostCategory; |   }) = _SnPostCategory; | ||||||
|  |  | ||||||
|   factory SnPostCategory.fromJson(Map<String, dynamic> json) => |   factory SnPostCategory.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPostCategory { | mixin _$SnPostCategory { | ||||||
|  |  | ||||||
|  String get id; String get slug; String? get name; List<SnPost> get posts; |  String get id; String get slug; String? get name; List<SnPost> get posts; int get usage; | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -28,16 +28,16 @@ $SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWith | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnPostCategoryCopyWith<$Res>  { | |||||||
|   factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl; |   factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,13 +65,14 @@ class _$SnPostCategoryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,10 +154,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -174,10 +175,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory(): | case _SnPostCategory(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -191,10 +192,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPostCategory extends SnPostCategory { | class _SnPostCategory extends SnPostCategory { | ||||||
|   const _SnPostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts,super._(); |   const _SnPostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const [], this.usage = 0}): _posts = posts,super._(); | ||||||
|   factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json); |   factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -219,6 +220,7 @@ class _SnPostCategory extends SnPostCategory { | |||||||
|   return EqualUnmodifiableListView(_posts); |   return EqualUnmodifiableListView(_posts); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  int usage; | ||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -233,16 +235,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -253,7 +255,7 @@ abstract mixin class _$SnPostCategoryCopyWith<$Res> implements $SnPostCategoryCo | |||||||
|   factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl; |   factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -270,13 +272,14 @@ class __$SnPostCategoryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_SnPostCategory( |   return _then(_SnPostCategory( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ _SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) => | |||||||
|               ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) |               ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) | ||||||
|               .toList() ?? |               .toList() ?? | ||||||
|           const [], |           const [], | ||||||
|  |       usage: (json['usage'] as num?)?.toInt() ?? 0, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | ||||||
| @@ -24,4 +25,5 @@ Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | |||||||
|       'slug': instance.slug, |       'slug': instance.slug, | ||||||
|       'name': instance.name, |       'name': instance.name, | ||||||
|       'posts': instance.posts.map((e) => e.toJson()).toList(), |       'posts': instance.posts.map((e) => e.toJson()).toList(), | ||||||
|  |       'usage': instance.usage, | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ sealed class SnPostTag with _$SnPostTag { | |||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|  |     @Default(0) int usage, | ||||||
|   }) = _SnPostTag; |   }) = _SnPostTag; | ||||||
|  |  | ||||||
|   factory SnPostTag.fromJson(Map<String, dynamic> json) => |   factory SnPostTag.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPostTag { | mixin _$SnPostTag { | ||||||
|  |  | ||||||
|  String get id; String get slug; String? get name; List<SnPost> get posts; |  String get id; String get slug; String? get name; List<SnPost> get posts; int get usage; | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -28,16 +28,16 @@ $SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag> | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnPostTagCopyWith<$Res>  { | |||||||
|   factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl; |   factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,13 +65,14 @@ class _$SnPostTagCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,10 +154,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -174,10 +175,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag(): | case _SnPostTag(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -191,10 +192,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPostTag implements SnPostTag { | class _SnPostTag implements SnPostTag { | ||||||
|   const _SnPostTag({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts; |   const _SnPostTag({required this.id, required this.slug, this.name, final  List<SnPost> posts = const [], this.usage = 0}): _posts = posts; | ||||||
|   factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json); |   factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -219,6 +220,7 @@ class _SnPostTag implements SnPostTag { | |||||||
|   return EqualUnmodifiableListView(_posts); |   return EqualUnmodifiableListView(_posts); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  int usage; | ||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -233,16 +235,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -253,7 +255,7 @@ abstract mixin class _$SnPostTagCopyWith<$Res> implements $SnPostTagCopyWith<$Re | |||||||
|   factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl; |   factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -270,13 +272,14 @@ class __$SnPostTagCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_SnPostTag( |   return _then(_SnPostTag( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ _SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag( | |||||||
|           ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) | ||||||
|           .toList() ?? |           .toList() ?? | ||||||
|       const [], |       const [], | ||||||
|  |   usage: (json['usage'] as num?)?.toInt() ?? 0, | ||||||
| ); | ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | ||||||
| @@ -23,4 +24,5 @@ Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | |||||||
|       'slug': instance.slug, |       'slug': instance.slug, | ||||||
|       'name': instance.name, |       'name': instance.name, | ||||||
|       'posts': instance.posts.map((e) => e.toJson()).toList(), |       'posts': instance.posts.map((e) => e.toJson()).toList(), | ||||||
|  |       'usage': instance.usage, | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -6,11 +6,13 @@ import 'package:flutter/foundation.dart' show kIsWeb; | |||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/screens/about.dart'; | import 'package:island/screens/about.dart'; | ||||||
|  | import 'package:island/screens/account/credits.dart'; | ||||||
| import 'package:island/screens/developers/apps.dart'; | import 'package:island/screens/developers/apps.dart'; | ||||||
| import 'package:island/screens/developers/edit_app.dart'; | import 'package:island/screens/developers/edit_app.dart'; | ||||||
| import 'package:island/screens/developers/new_app.dart'; | import 'package:island/screens/developers/new_app.dart'; | ||||||
| import 'package:island/screens/developers/hub.dart'; | import 'package:island/screens/developers/hub.dart'; | ||||||
| import 'package:island/screens/discovery/articles.dart'; | import 'package:island/screens/discovery/articles.dart'; | ||||||
|  | import 'package:island/screens/posts/post_categories_list.dart'; | ||||||
| import 'package:island/screens/posts/post_category_detail.dart'; | import 'package:island/screens/posts/post_category_detail.dart'; | ||||||
| import 'package:island/screens/posts/post_search.dart'; | import 'package:island/screens/posts/post_search.dart'; | ||||||
| import 'package:island/widgets/app_wrapper.dart'; | import 'package:island/widgets/app_wrapper.dart'; | ||||||
| @@ -52,6 +54,7 @@ import 'package:island/screens/account/event_calendar.dart'; | |||||||
| import 'package:island/screens/discovery/realms.dart'; | import 'package:island/screens/discovery/realms.dart'; | ||||||
| import 'package:island/screens/reports/report_detail.dart'; | import 'package:island/screens/reports/report_detail.dart'; | ||||||
| import 'package:island/screens/reports/report_list.dart'; | import 'package:island/screens/reports/report_list.dart'; | ||||||
|  | import 'package:island/widgets/post/post_shuffle.dart'; | ||||||
|  |  | ||||||
| // Shell route keys for nested navigation | // Shell route keys for nested navigation | ||||||
| final rootNavigatorKey = GlobalKey<NavigatorState>(); | final rootNavigatorKey = GlobalKey<NavigatorState>(); | ||||||
| @@ -376,12 +379,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 builder: (context, state) => const PostSearchScreen(), |                 builder: (context, state) => const PostSearchScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postDetail', |                 name: 'postShuffle', | ||||||
|                 path: '/posts/:id', |                 path: '/posts/shuffle', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) => const PostShuffleScreen(), | ||||||
|                   final id = state.pathParameters['id']!; |               ), | ||||||
|                   return PostDetailScreen(id: id); |               GoRoute( | ||||||
|                 }, |                 name: 'postCategories', | ||||||
|  |                 path: '/posts/categories', | ||||||
|  |                 builder: (context, state) => const PostCategoriesListScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postCategoryDetail', |                 name: 'postCategoryDetail', | ||||||
| @@ -391,6 +396,11 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                   return PostCategoryDetailScreen(slug: slug, isCategory: true); |                   return PostCategoryDetailScreen(slug: slug, isCategory: true); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postTags', | ||||||
|  |                 path: '/posts/tags', | ||||||
|  |                 builder: (context, state) => const PostTagsListScreen(), | ||||||
|  |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postTagDetail', |                 name: 'postTagDetail', | ||||||
|                 path: '/posts/tags/:slug', |                 path: '/posts/tags/:slug', | ||||||
| @@ -402,6 +412,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postDetail', | ||||||
|  |                 path: '/posts/:id', | ||||||
|  |                 builder: (context, state) { | ||||||
|  |                   final id = state.pathParameters['id']!; | ||||||
|  |                   return PostDetailScreen(id: id); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'publisherProfile', |                 name: 'publisherProfile', | ||||||
|                 path: '/publishers/:name', |                 path: '/publishers/:name', | ||||||
| @@ -538,6 +556,11 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                     path: '/account/wallet', |                     path: '/account/wallet', | ||||||
|                     builder: (context, state) => const WalletScreen(), |                     builder: (context, state) => const WalletScreen(), | ||||||
|                   ), |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'socialCredits', | ||||||
|  |                     path: '/account/credits', | ||||||
|  |                     builder: (context, state) => const SocialCreditsScreen(), | ||||||
|  |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'relationships', |                     name: 'relationships', | ||||||
|                     path: '/account/relationships', |                     path: '/account/relationships', | ||||||
|   | |||||||
| @@ -236,6 +236,16 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 context.pushNamed('stickerMarketplace'); |                 context.pushNamed('stickerMarketplace'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|  |             ListTile( | ||||||
|  |               minTileHeight: 48, | ||||||
|  |               leading: const Icon(Symbols.star), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |               title: Text('credits').tr(), | ||||||
|  |               onTap: () { | ||||||
|  |                 context.pushNamed('socialCredits'); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|               minTileHeight: 48, |               minTileHeight: 48, | ||||||
|               title: Text('abuseReport').tr(), |               title: Text('abuseReport').tr(), | ||||||
| @@ -389,6 +399,15 @@ class _UnauthorizedAccountScreen extends StatelessWidget { | |||||||
|                       }, |                       }, | ||||||
|                       child: Text('about').tr(), |                       child: Text('about').tr(), | ||||||
|                     ), |                     ), | ||||||
|  |                     TextButton( | ||||||
|  |                       child: Text('debugOptions').tr(), | ||||||
|  |                       onPressed: () { | ||||||
|  |                         showModalBottomSheet( | ||||||
|  |                           context: context, | ||||||
|  |                           builder: (context) => DebugSheet(), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|                     TextButton( |                     TextButton( | ||||||
|                       onPressed: () { |                       onPressed: () { | ||||||
|                         context.pushNamed('settings'); |                         context.pushNamed('settings'); | ||||||
|   | |||||||
							
								
								
									
										152
									
								
								lib/screens/account/credits.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								lib/screens/account/credits.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/account.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'credits.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<double> socialCredits(Ref ref) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final response = await client.get('/id/accounts/me/credits'); | ||||||
|  |   if (response.statusCode != 200) { | ||||||
|  |     throw Exception('Failed to load social credits'); | ||||||
|  |   } | ||||||
|  |   return response.data?.toDouble() ?? 0.0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class SocialCreditHistoryNotifier extends _$SocialCreditHistoryNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnSocialCreditRecord> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnSocialCreditRecord>> build() => fetch(cursor: null); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnSocialCreditRecord>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/id/accounts/me/credits/history', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final records = | ||||||
|  |         data.map((json) => SnSocialCreditRecord.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + records.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + records.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: records, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SocialCreditsScreen extends HookConsumerWidget { | ||||||
|  |   const SocialCreditsScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final socialCredits = ref.watch(socialCreditsProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: Text('socialCredits').tr()), | ||||||
|  |       body: Column( | ||||||
|  |         children: [ | ||||||
|  |           Card( | ||||||
|  |             margin: EdgeInsets.only(left: 16, right: 16, top: 8), | ||||||
|  |             child: socialCredits | ||||||
|  |                 .when( | ||||||
|  |                   data: | ||||||
|  |                       (credits) => Stack( | ||||||
|  |                         children: [ | ||||||
|  |                           Column( | ||||||
|  |                             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                             children: [ | ||||||
|  |                               Text( | ||||||
|  |                                 credits < 100 | ||||||
|  |                                     ? 'socialCreditsLevelPoor'.tr() | ||||||
|  |                                     : credits < 150 | ||||||
|  |                                     ? 'socialCreditsLevelNormal'.tr() | ||||||
|  |                                     : credits < 200 | ||||||
|  |                                     ? 'socialCreditsLevelGood'.tr() | ||||||
|  |                                     : 'socialCreditsLevelExcellent'.tr(), | ||||||
|  |                               ).tr().bold().fontSize(20), | ||||||
|  |                               Text( | ||||||
|  |                                 '${credits.toStringAsFixed(2)} pts', | ||||||
|  |                               ).fontSize(14), | ||||||
|  |                               const Gap(8), | ||||||
|  |                               LinearProgressIndicator(value: credits / 200), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                           Positioned( | ||||||
|  |                             right: 0, | ||||||
|  |                             top: 0, | ||||||
|  |                             child: IconButton( | ||||||
|  |                               onPressed: () {}, | ||||||
|  |                               icon: const Icon(Symbols.info), | ||||||
|  |                               tooltip: 'socialCreditsDescription'.tr(), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                   error: (_, _) => Text('Error loading credits'), | ||||||
|  |                   loading: () => const LinearProgressIndicator(), | ||||||
|  |                 ) | ||||||
|  |                 .padding(horizontal: 20, vertical: 16), | ||||||
|  |           ), | ||||||
|  |           Expanded( | ||||||
|  |             child: PagingHelperView( | ||||||
|  |               provider: socialCreditHistoryNotifierProvider, | ||||||
|  |               futureRefreshable: socialCreditHistoryNotifierProvider.future, | ||||||
|  |               notifierRefreshable: socialCreditHistoryNotifierProvider.notifier, | ||||||
|  |               contentBuilder: | ||||||
|  |                   (data, widgetCount, endItemView) => ListView.builder( | ||||||
|  |                     padding: EdgeInsets.zero, | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |                       final record = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                         title: Text(record.reason), | ||||||
|  |                         subtitle: Text( | ||||||
|  |                           DateFormat.yMMMd().format(record.createdAt), | ||||||
|  |                         ), | ||||||
|  |                         trailing: Text( | ||||||
|  |                           record.delta > 0 | ||||||
|  |                               ? '+${record.delta}' | ||||||
|  |                               : '${record.delta}', | ||||||
|  |                           style: TextStyle( | ||||||
|  |                             color: record.delta > 0 ? Colors.green : Colors.red, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								lib/screens/account/credits.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								lib/screens/account/credits.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'credits.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$socialCreditsHash() => r'2599844e892127ee4d315caced5c10e4dbaea142'; | ||||||
|  |  | ||||||
|  | /// See also [socialCredits]. | ||||||
|  | @ProviderFor(socialCredits) | ||||||
|  | final socialCreditsProvider = AutoDisposeFutureProvider<double>.internal( | ||||||
|  |   socialCredits, | ||||||
|  |   name: r'socialCreditsProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$socialCreditsHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | typedef SocialCreditsRef = AutoDisposeFutureProviderRef<double>; | ||||||
|  | String _$socialCreditHistoryNotifierHash() => | ||||||
|  |     r'950db020754160f835c64cedf3fa2175e61e4d64'; | ||||||
|  |  | ||||||
|  | /// See also [SocialCreditHistoryNotifier]. | ||||||
|  | @ProviderFor(SocialCreditHistoryNotifier) | ||||||
|  | final socialCreditHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider< | ||||||
|  |   SocialCreditHistoryNotifier, | ||||||
|  |   CursorPagingData<SnSocialCreditRecord> | ||||||
|  | >.internal( | ||||||
|  |   SocialCreditHistoryNotifier.new, | ||||||
|  |   name: r'socialCreditHistoryNotifierProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$socialCreditHistoryNotifierHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | typedef _$SocialCreditHistoryNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnSocialCreditRecord>>; | ||||||
|  | // 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 | ||||||
| @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:google_fonts/google_fonts.dart'; | import 'package:google_fonts/google_fonts.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/models/wallet.dart'; | import 'package:island/models/wallet.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
| @@ -19,6 +20,7 @@ import 'package:island/widgets/payment/payment_overlay.dart'; | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.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:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| part 'leveling.g.dart'; | part 'leveling.g.dart'; | ||||||
| @@ -35,13 +37,49 @@ Future<SnWalletSubscription?> accountStellarSubscription(Ref ref) async { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class LevelingHistoryNotifier extends _$LevelingHistoryNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnExperienceRecord> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnExperienceRecord>> build() => fetch(cursor: null); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnExperienceRecord>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/id/accounts/me/leveling', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final records = | ||||||
|  |         data.map((json) => SnExperienceRecord.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + records.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + records.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: records, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class LevelingScreen extends HookConsumerWidget { | class LevelingScreen extends HookConsumerWidget { | ||||||
|   const LevelingScreen({super.key}); |   const LevelingScreen({super.key}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|     final stellarSubscription = ref.watch(accountStellarSubscriptionProvider); |  | ||||||
|  |  | ||||||
|     if (user.value == null) { |     if (user.value == null) { | ||||||
|       return AppScaffold( |       return AppScaffold( | ||||||
| @@ -50,13 +88,140 @@ class LevelingScreen extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     final currentLevel = user.value!.profile.level; |     return DefaultTabController( | ||||||
|     final currentExp = user.value!.profile.experience; |       length: 2, | ||||||
|     final progress = user.value!.profile.levelingProgress; |       child: AppScaffold( | ||||||
|  |         appBar: AppBar( | ||||||
|  |           title: Text('levelingProgress'.tr()), | ||||||
|  |           bottom: TabBar( | ||||||
|  |             tabs: [ | ||||||
|  |               Tab(text: 'leveling'.tr()), | ||||||
|  |               Tab(text: 'stellarProgram'.tr()), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         body: TabBarView( | ||||||
|  |           children: [ | ||||||
|  |             _buildLevelingTab(context, ref, user.value!), | ||||||
|  |             _buildStellarProgramTab(context, ref), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|     return AppScaffold( |   Widget _buildLevelingTab( | ||||||
|       appBar: AppBar(title: Text('levelingProgress'.tr())), |     BuildContext context, | ||||||
|       body: SingleChildScrollView( |     WidgetRef ref, | ||||||
|  |     SnAccount user, | ||||||
|  |   ) { | ||||||
|  |     final currentLevel = user.profile.level; | ||||||
|  |     final currentExp = user.profile.experience; | ||||||
|  |     final progress = user.profile.levelingProgress; | ||||||
|  |  | ||||||
|  |     return Center( | ||||||
|  |       child: Container( | ||||||
|  |         padding: const EdgeInsets.symmetric(horizontal: 20), | ||||||
|  |         constraints: const BoxConstraints(maxWidth: 480), | ||||||
|  |         child: CustomScrollView( | ||||||
|  |           slivers: [ | ||||||
|  |             const SliverGap(20), | ||||||
|  |  | ||||||
|  |             // Current Progress Card | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: LevelingProgressCard( | ||||||
|  |                 level: currentLevel, | ||||||
|  |                 experience: currentExp, | ||||||
|  |                 progress: progress, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SliverGap(24), | ||||||
|  |  | ||||||
|  |             // Level Stairs Graph | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Text( | ||||||
|  |                 'levelProgress'.tr(), | ||||||
|  |                 style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SliverGap(16), | ||||||
|  |  | ||||||
|  |             // Stairs visualization with fixed height and horizontal scroll | ||||||
|  |             SliverToBoxAdapter(child: _buildLevelStairs(context, currentLevel)), | ||||||
|  |             const SliverGap(24), | ||||||
|  |  | ||||||
|  |             // Leveling History | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Text( | ||||||
|  |                 'levelingHistory'.tr(), | ||||||
|  |                 style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SliverGap(8), | ||||||
|  |             PagingHelperSliverView( | ||||||
|  |               provider: levelingHistoryNotifierProvider, | ||||||
|  |               futureRefreshable: levelingHistoryNotifierProvider.future, | ||||||
|  |               notifierRefreshable: levelingHistoryNotifierProvider.notifier, | ||||||
|  |               contentBuilder: | ||||||
|  |                   (data, widgetCount, endItemView) => SliverList.builder( | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |                       final record = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         title: Column( | ||||||
|  |                           mainAxisSize: MainAxisSize.min, | ||||||
|  |                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                           children: [ | ||||||
|  |                             Text(record.reason), | ||||||
|  |                             Row( | ||||||
|  |                               spacing: 4, | ||||||
|  |                               children: [ | ||||||
|  |                                 Text( | ||||||
|  |                                   record.createdAt.formatRelative(context), | ||||||
|  |                                 ).fontSize(13), | ||||||
|  |                                 Text('·').fontSize(13).bold(), | ||||||
|  |                                 Text( | ||||||
|  |                                   record.createdAt.formatSystem(), | ||||||
|  |                                 ).fontSize(13), | ||||||
|  |                               ], | ||||||
|  |                             ).opacity(0.8), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         subtitle: Row( | ||||||
|  |                           spacing: 8, | ||||||
|  |                           children: [ | ||||||
|  |                             Text( | ||||||
|  |                               '${record.delta > 0 ? '+' : ''}${record.delta} EXP', | ||||||
|  |                             ), | ||||||
|  |                             if (record.bonusMultiplier != 1.0) | ||||||
|  |                               Text('x${record.bonusMultiplier}'), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         minTileHeight: 56, | ||||||
|  |                         contentPadding: EdgeInsets.symmetric(horizontal: 4), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |  | ||||||
|  |             SliverGap(getTabbedPadding(context, vertical: 20).vertical), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget _buildStellarProgramTab(BuildContext context, WidgetRef ref) { | ||||||
|  |     final stellarSubscription = ref.watch(accountStellarSubscriptionProvider); | ||||||
|  |  | ||||||
|  |     return SingleChildScrollView( | ||||||
|       padding: getTabbedPadding(context, horizontal: 20, vertical: 20), |       padding: getTabbedPadding(context, horizontal: 20, vertical: 20), | ||||||
|       child: Center( |       child: Center( | ||||||
|         child: ConstrainedBox( |         child: ConstrainedBox( | ||||||
| @@ -64,36 +229,12 @@ class LevelingScreen extends HookConsumerWidget { | |||||||
|           child: Column( |           child: Column( | ||||||
|             crossAxisAlignment: CrossAxisAlignment.stretch, |             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|             children: [ |             children: [ | ||||||
|                 // Current Progress Card |  | ||||||
|                 LevelingProgressCard( |  | ||||||
|                   level: currentLevel, |  | ||||||
|                   experience: currentExp, |  | ||||||
|                   progress: progress, |  | ||||||
|                 ), |  | ||||||
|                 const Gap(24), |  | ||||||
|  |  | ||||||
|                 // Level Stairs Graph |  | ||||||
|                 Text( |  | ||||||
|                   'levelProgress'.tr(), |  | ||||||
|                   style: Theme.of(context).textTheme.headlineSmall?.copyWith( |  | ||||||
|                     fontWeight: FontWeight.bold, |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|                 const Gap(16), |  | ||||||
|  |  | ||||||
|                 // Stairs visualization with fixed height and horizontal scroll |  | ||||||
|                 _buildLevelStairs(context, currentLevel), |  | ||||||
|  |  | ||||||
|                 const Gap(24), |  | ||||||
|  |  | ||||||
|                 // Membership section |  | ||||||
|               _buildMembershipSection(context, ref, stellarSubscription), |               _buildMembershipSection(context, ref, stellarSubscription), | ||||||
|               const Gap(16), |               const Gap(16), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,5 +27,26 @@ final accountStellarSubscriptionProvider = | |||||||
| // ignore: unused_element | // ignore: unused_element | ||||||
| typedef AccountStellarSubscriptionRef = | typedef AccountStellarSubscriptionRef = | ||||||
|     AutoDisposeFutureProviderRef<SnWalletSubscription?>; |     AutoDisposeFutureProviderRef<SnWalletSubscription?>; | ||||||
|  | String _$levelingHistoryNotifierHash() => | ||||||
|  |     r'e795f9b7911c9e50f15c095ea237cb0e87bf1e89'; | ||||||
|  |  | ||||||
|  | /// See also [LevelingHistoryNotifier]. | ||||||
|  | @ProviderFor(LevelingHistoryNotifier) | ||||||
|  | final levelingHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider< | ||||||
|  |   LevelingHistoryNotifier, | ||||||
|  |   CursorPagingData<SnExperienceRecord> | ||||||
|  | >.internal( | ||||||
|  |   LevelingHistoryNotifier.new, | ||||||
|  |   name: r'levelingHistoryNotifierProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$levelingHistoryNotifierHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | typedef _$LevelingHistoryNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnExperienceRecord>>; | ||||||
| // 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 | ||||||
|   | |||||||
| @@ -79,18 +79,21 @@ class ChatRoomListTile extends HookConsumerWidget { | |||||||
|                     color: Theme.of(context).colorScheme.primary, |                     color: Theme.of(context).colorScheme.primary, | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|  |               if (data.lastMessage == null) | ||||||
|  |                 Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1) | ||||||
|  |               else | ||||||
|                 Row( |                 Row( | ||||||
|                   spacing: 4, |                   spacing: 4, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     Badge( |                     Badge( | ||||||
|                     label: Text(data.lastMessage.sender.account.nick), |                       label: Text(data.lastMessage!.sender.account.nick), | ||||||
|                       textColor: Theme.of(context).colorScheme.onPrimary, |                       textColor: Theme.of(context).colorScheme.onPrimary, | ||||||
|                       backgroundColor: Theme.of(context).colorScheme.primary, |                       backgroundColor: Theme.of(context).colorScheme.primary, | ||||||
|                     ), |                     ), | ||||||
|                     Expanded( |                     Expanded( | ||||||
|                       child: Text( |                       child: Text( | ||||||
|                       (data.lastMessage.content?.isNotEmpty ?? false) |                         (data.lastMessage!.content?.isNotEmpty ?? false) | ||||||
|                           ? data.lastMessage.content! |                             ? data.lastMessage!.content! | ||||||
|                             : 'messageNone'.tr(), |                             : 'messageNone'.tr(), | ||||||
|                         maxLines: 1, |                         maxLines: 1, | ||||||
|                         overflow: TextOverflow.ellipsis, |                         overflow: TextOverflow.ellipsis, | ||||||
| @@ -100,7 +103,9 @@ class ChatRoomListTile extends HookConsumerWidget { | |||||||
|                     Align( |                     Align( | ||||||
|                       alignment: Alignment.centerRight, |                       alignment: Alignment.centerRight, | ||||||
|                       child: Text( |                       child: Text( | ||||||
|                       RelativeTime(context).format(data.lastMessage.createdAt), |                         RelativeTime( | ||||||
|  |                           context, | ||||||
|  |                         ).format(data.lastMessage!.createdAt), | ||||||
|                         style: Theme.of(context).textTheme.bodySmall, |                         style: Theme.of(context).textTheme.bodySmall, | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ part of 'room_detail.dart'; | |||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$chatMemberListNotifierHash() => | String _$chatMemberListNotifierHash() => | ||||||
|     r'c8fbf4b95df6dae24b1ba21063e9a43351832974'; |     r'3ea30150278523e9f6b23f9200ea9a9fbae9c973'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -106,11 +106,7 @@ class StickerPacksNotifier extends _$StickerPacksNotifier | |||||||
|     try { |     try { | ||||||
|       final response = await client.get( |       final response = await client.get( | ||||||
|         '/sphere/stickers', |         '/sphere/stickers', | ||||||
|         queryParameters: { |         queryParameters: {'offset': offset, 'take': _pageSize, 'pub': pubName}, | ||||||
|           'offset': offset, |  | ||||||
|           'take': _pageSize, |  | ||||||
|           'pubName': pubName, |  | ||||||
|         }, |  | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       final total = int.parse(response.headers.value('X-Total') ?? '0'); |       final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|   | |||||||
| @@ -148,7 +148,7 @@ class _StickerPackProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$stickerPacksNotifierHash() => | String _$stickerPacksNotifierHash() => | ||||||
|     r'0a8edcf9c35396c411f1214f5e77b1e8fac6a3e6'; |     r'30024b35235f3085a5b1ec2204d0a974ee907e22'; | ||||||
|  |  | ||||||
| abstract class _$StickerPacksNotifier | abstract class _$StickerPacksNotifier | ||||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> { |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> { | ||||||
|   | |||||||
| @@ -173,12 +173,60 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                     tooltip: 'webArticlesStand'.tr(), |                     tooltip: 'webArticlesStand'.tr(), | ||||||
|                   ), |                   ), | ||||||
|                   IconButton( |                   PopupMenuButton( | ||||||
|                     onPressed: () { |                     itemBuilder: | ||||||
|  |                         (context) => [ | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.category), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('categories').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postCategories'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.label), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('tags').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postTags'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.shuffle), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('postShuffle').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postShuffle'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.search), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('search').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|                               context.pushNamed('postSearch'); |                               context.pushNamed('postSearch'); | ||||||
|                             }, |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|                     icon: Icon( |                     icon: Icon( | ||||||
|                       Symbols.search, |                       Symbols.action_key, | ||||||
|                       color: Theme.of(context).appBarTheme.foregroundColor!, |                       color: Theme.of(context).appBarTheme.foregroundColor!, | ||||||
|                     ), |                     ), | ||||||
|                     tooltip: 'search'.tr(), |                     tooltip: 'search'.tr(), | ||||||
|   | |||||||
							
								
								
									
										242
									
								
								lib/screens/posts/post_categories_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								lib/screens/posts/post_categories_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/post_category.dart'; | ||||||
|  | import 'package:island/models/post_tag.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  |  | ||||||
|  | // Post Categories Notifier | ||||||
|  | final postCategoriesNotifierProvider = StateNotifierProvider.autoDispose< | ||||||
|  |   PostCategoriesNotifier, | ||||||
|  |   AsyncValue<CursorPagingData<SnPostCategory>> | ||||||
|  | >((ref) { | ||||||
|  |   return PostCategoriesNotifier(ref); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class PostCategoriesNotifier | ||||||
|  |     extends StateNotifier<AsyncValue<CursorPagingData<SnPostCategory>>> { | ||||||
|  |   final AutoDisposeRef ref; | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |   bool _isLoading = false; | ||||||
|  |  | ||||||
|  |   PostCategoriesNotifier(this.ref) : super(const AsyncValue.loading()) { | ||||||
|  |     state = const AsyncValue.data( | ||||||
|  |       CursorPagingData(items: [], hasMore: false, nextCursor: null), | ||||||
|  |     ); | ||||||
|  |     fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> fetch({String? cursor}) async { | ||||||
|  |     if (_isLoading) return; | ||||||
|  |  | ||||||
|  |     _isLoading = true; | ||||||
|  |     if (cursor == null) { | ||||||
|  |       state = const AsyncValue.loading(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/sphere/posts/categories', | ||||||
|  |         queryParameters: { | ||||||
|  |           'offset': offset, | ||||||
|  |           'take': _pageSize, | ||||||
|  |           'order': 'usage', | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final data = response.data as List; | ||||||
|  |       final categories = | ||||||
|  |           data.map((json) => SnPostCategory.fromJson(json)).toList(); | ||||||
|  |       final hasMore = categories.length == _pageSize; | ||||||
|  |       final nextCursor = | ||||||
|  |           hasMore ? (offset + categories.length).toString() : null; | ||||||
|  |  | ||||||
|  |       state = AsyncValue.data( | ||||||
|  |         CursorPagingData( | ||||||
|  |           items: [...(state.value?.items ?? []), ...categories], | ||||||
|  |           hasMore: hasMore, | ||||||
|  |           nextCursor: nextCursor, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } catch (e, stack) { | ||||||
|  |       state = AsyncValue.error(e, stack); | ||||||
|  |     } finally { | ||||||
|  |       _isLoading = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Post Tags Notifier | ||||||
|  | final postTagsNotifierProvider = StateNotifierProvider.autoDispose< | ||||||
|  |   PostTagsNotifier, | ||||||
|  |   AsyncValue<CursorPagingData<SnPostTag>> | ||||||
|  | >((ref) { | ||||||
|  |   return PostTagsNotifier(ref); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class PostTagsNotifier | ||||||
|  |     extends StateNotifier<AsyncValue<CursorPagingData<SnPostTag>>> { | ||||||
|  |   final AutoDisposeRef ref; | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |   bool _isLoading = false; | ||||||
|  |  | ||||||
|  |   PostTagsNotifier(this.ref) : super(const AsyncValue.loading()) { | ||||||
|  |     state = const AsyncValue.data( | ||||||
|  |       CursorPagingData(items: [], hasMore: false, nextCursor: null), | ||||||
|  |     ); | ||||||
|  |     fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> fetch({String? cursor}) async { | ||||||
|  |     if (_isLoading) return; | ||||||
|  |  | ||||||
|  |     _isLoading = true; | ||||||
|  |     if (cursor == null) { | ||||||
|  |       state = const AsyncValue.loading(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/sphere/posts/tags', | ||||||
|  |         queryParameters: { | ||||||
|  |           'offset': offset, | ||||||
|  |           'take': _pageSize, | ||||||
|  |           'order': 'usage', | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final data = response.data as List; | ||||||
|  |       final tags = data.map((json) => SnPostTag.fromJson(json)).toList(); | ||||||
|  |       final hasMore = tags.length == _pageSize; | ||||||
|  |       final nextCursor = hasMore ? (offset + tags.length).toString() : null; | ||||||
|  |  | ||||||
|  |       state = AsyncValue.data( | ||||||
|  |         CursorPagingData( | ||||||
|  |           items: [...(state.value?.items ?? []), ...tags], | ||||||
|  |           hasMore: hasMore, | ||||||
|  |           nextCursor: nextCursor, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } catch (e, stack) { | ||||||
|  |       state = AsyncValue.error(e, stack); | ||||||
|  |     } finally { | ||||||
|  |       _isLoading = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PostCategoriesListScreen extends ConsumerWidget { | ||||||
|  |   const PostCategoriesListScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final categoriesState = ref.watch(postCategoriesNotifierProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('categories').tr()), | ||||||
|  |       body: categoriesState.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.items.isEmpty) { | ||||||
|  |             return const Center(child: Text('No categories found')); | ||||||
|  |           } | ||||||
|  |           return ListView.builder( | ||||||
|  |             padding: EdgeInsets.zero, | ||||||
|  |             itemCount: data.items.length + (data.hasMore ? 1 : 0), | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index >= data.items.length) { | ||||||
|  |                 ref | ||||||
|  |                     .read(postCategoriesNotifierProvider.notifier) | ||||||
|  |                     .fetch(cursor: data.nextCursor); | ||||||
|  |                 return const Center(child: CircularProgressIndicator()); | ||||||
|  |               } | ||||||
|  |               final category = data.items[index]; | ||||||
|  |               return ListTile( | ||||||
|  |                 leading: const Icon(Symbols.category), | ||||||
|  |                 contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                 title: Text(category.categoryDisplayTitle), | ||||||
|  |                 subtitle: Text('postCount'.plural(category.usage)), | ||||||
|  |                 onTap: () { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'postCategoryDetail', | ||||||
|  |                     pathParameters: {'slug': category.slug}, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, stack) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(postCategoriesNotifierProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PostTagsListScreen extends ConsumerWidget { | ||||||
|  |   const PostTagsListScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final tagsState = ref.watch(postTagsNotifierProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('tags').tr()), | ||||||
|  |       body: tagsState.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.items.isEmpty) { | ||||||
|  |             return const Center(child: Text('No tags found')); | ||||||
|  |           } | ||||||
|  |           return ListView.builder( | ||||||
|  |             padding: EdgeInsets.zero, | ||||||
|  |             itemCount: data.items.length + (data.hasMore ? 1 : 0), | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index >= data.items.length) { | ||||||
|  |                 ref | ||||||
|  |                     .read(postTagsNotifierProvider.notifier) | ||||||
|  |                     .fetch(cursor: data.nextCursor); | ||||||
|  |                 return const Center(child: CircularProgressIndicator()); | ||||||
|  |               } | ||||||
|  |               final tag = data.items[index]; | ||||||
|  |               return ListTile( | ||||||
|  |                 title: Text(tag.name ?? '#${tag.slug}'), | ||||||
|  |                 contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                 leading: const Icon(Symbols.label), | ||||||
|  |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                 subtitle: Text('postCount'.plural(tag.usage)), | ||||||
|  |                 onTap: () { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'postTagDetail', | ||||||
|  |                     pathParameters: {'slug': tag.slug}, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, stack) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(postTagsNotifierProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -8,6 +9,7 @@ import 'package:island/widgets/app_scaffold.dart'; | |||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.dart'; | ||||||
| import 'package:island/widgets/post/post_quick_reply.dart'; | import 'package:island/widgets/post/post_quick_reply.dart'; | ||||||
| import 'package:island/widgets/post/post_replies.dart'; | import 'package:island/widgets/post/post_replies.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| @@ -55,7 +57,10 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       isNoBackground: false, |       isNoBackground: false, | ||||||
|       appBar: AppBar(title: const Text('Post')), |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('postDetail').tr(), | ||||||
|  |       ), | ||||||
|       body: postState.when( |       body: postState.when( | ||||||
|         data: (post) { |         data: (post) { | ||||||
|           return Stack( |           return Stack( | ||||||
| @@ -117,8 +122,12 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|         loading: () => const Center(child: CircularProgressIndicator()), |         loading: () => ResponseLoadingWidget(), | ||||||
|         error: (e, _) => Text('Error: $e'), |         error: | ||||||
|  |             (e, _) => ResponseErrorWidget( | ||||||
|  |               error: e, | ||||||
|  |               onRetry: () => ref.invalidate(postStateProvider(id)), | ||||||
|  |             ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -399,7 +399,7 @@ class _RealmChatRoomsProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$realmMemberListNotifierHash() => | String _$realmMemberListNotifierHash() => | ||||||
|     r'022bcef5a90cbae05ff23b937851afc3ef913d42'; |     r'2f88f803b2e61e7287ed8a43025173e56ff6ca3b'; | ||||||
|  |  | ||||||
| abstract class _$RealmMemberListNotifier | abstract class _$RealmMemberListNotifier | ||||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> { |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> { | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|                         return ConstrainedBox( |                         return ConstrainedBox( | ||||||
|                           constraints: const BoxConstraints(maxWidth: 540), |                           constraints: const BoxConstraints(maxWidth: 540), | ||||||
|                           child: RealmListTile(realm: value[item]), |                           child: RealmListTile(realm: value[item]), | ||||||
|                         ).center(); |                         ).padding(horizontal: 8).center(); | ||||||
|                       }, |                       }, | ||||||
|                       separatorBuilder: (_, _) => const Gap(8), |                       separatorBuilder: (_, _) => const Gap(8), | ||||||
|                     ), |                     ), | ||||||
|   | |||||||
| @@ -235,7 +235,11 @@ class PageBackButton extends StatelessWidget { | |||||||
|     return IconButton( |     return IconButton( | ||||||
|       onPressed: () { |       onPressed: () { | ||||||
|         onWillPop?.call(); |         onWillPop?.call(); | ||||||
|  |         if (context.canPop()) { | ||||||
|           context.pop(); |           context.pop(); | ||||||
|  |         } else { | ||||||
|  |           context.go('/'); | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       icon: Icon( |       icon: Icon( | ||||||
|         color: color, |         color: color, | ||||||
|   | |||||||
| @@ -50,6 +50,6 @@ class AppWrapper extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return TourTriggerWidget(child: child); |     return TourTriggerWidget(key: UniqueKey(), child: child); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
|  | import 'dart:convert'; | ||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
|  |  | ||||||
| import 'package:dismissible_page/dismissible_page.dart'; | import 'package:dismissible_page/dismissible_page.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/services.dart'; | ||||||
| import 'package:flutter_blurhash/flutter_blurhash.dart'; | import 'package:flutter_blurhash/flutter_blurhash.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| @@ -326,7 +328,11 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|  |  | ||||||
|         // Create a temporary file to save the image |         // Create a temporary file to save the image | ||||||
|         final tempDir = await getTemporaryDirectory(); |         final tempDir = await getTemporaryDirectory(); | ||||||
|         final filePath = '${tempDir.path}/${item.id}.${extension(item.name)}'; |         var extName = extension(item.name).trim(); | ||||||
|  |         if (extName.isEmpty) { | ||||||
|  |           extName = item.mimeType?.split('/').lastOrNull ?? 'jpeg'; | ||||||
|  |         } | ||||||
|  |         final filePath = '${tempDir.path}/${item.id}.$extName'; | ||||||
|  |  | ||||||
|         await client.download( |         await client.download( | ||||||
|           '/drive/files/${item.id}', |           '/drive/files/${item.id}', | ||||||
| @@ -342,39 +348,6 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Widget buildInfoRow(IconData icon, String label, String value) { |  | ||||||
|       return Padding( |  | ||||||
|         padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), |  | ||||||
|         child: Row( |  | ||||||
|           children: [ |  | ||||||
|             Icon( |  | ||||||
|               icon, |  | ||||||
|               size: 20, |  | ||||||
|               color: Theme.of(context).colorScheme.onSurface, |  | ||||||
|             ), |  | ||||||
|             const SizedBox(width: 12), |  | ||||||
|             Text( |  | ||||||
|               label, |  | ||||||
|               style: Theme.of(context).textTheme.bodyMedium?.copyWith( |  | ||||||
|                 color: Theme.of(context).textTheme.bodySmall?.color, |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|             const Spacer(), |  | ||||||
|             Flexible( |  | ||||||
|               child: Text( |  | ||||||
|                 value, |  | ||||||
|                 style: Theme.of( |  | ||||||
|                   context, |  | ||||||
|                 ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), |  | ||||||
|                 textAlign: TextAlign.end, |  | ||||||
|                 overflow: TextOverflow.ellipsis, |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ), |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String formatFileSize(int bytes) { |     String formatFileSize(int bytes) { | ||||||
|       if (bytes <= 0) return '0 B'; |       if (bytes <= 0) return '0 B'; | ||||||
|       if (bytes < 1024) return '$bytes B'; |       if (bytes < 1024) return '$bytes B'; | ||||||
| @@ -400,57 +373,247 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|                 child: Column( |                 child: Column( | ||||||
|                   crossAxisAlignment: CrossAxisAlignment.start, |                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     buildInfoRow(Icons.description, 'Name', item.name), |                     Row( | ||||||
|                     const Divider(height: 1), |                       children: [ | ||||||
|                     buildInfoRow( |                         Expanded( | ||||||
|                       Icons.storage, |                           child: Column( | ||||||
|                       'Size', |                             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                       formatFileSize(item.size), |                             mainAxisSize: MainAxisSize.min, | ||||||
|                     ), |                             children: [ | ||||||
|                     const Divider(height: 1), |                               Text('mimeType').tr(), | ||||||
|                     buildInfoRow( |  | ||||||
|                       Icons.category, |  | ||||||
|                       'Type', |  | ||||||
|                       item.mimeType?.toUpperCase() ?? 'UNKNOWN', |  | ||||||
|                     ), |  | ||||||
|                     if (exifData.isNotEmpty) ...[ |  | ||||||
|                       const SizedBox(height: 16), |  | ||||||
|                               Text( |                               Text( | ||||||
|                         'EXIF Data', |                                 item.mimeType ?? 'unknown'.tr(), | ||||||
|  |                                 maxLines: 1, | ||||||
|  |                                 overflow: TextOverflow.ellipsis, | ||||||
|                                 style: theme.textTheme.titleMedium?.copyWith( |                                 style: theme.textTheme.titleMedium?.copyWith( | ||||||
|                                   fontWeight: FontWeight.bold, |                                   fontWeight: FontWeight.bold, | ||||||
|                                 ), |                                 ), | ||||||
|                       ).padding(horizontal: 24), |                               ), | ||||||
|                       const SizedBox(height: 8), |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         SizedBox(height: 28, child: const VerticalDivider()), | ||||||
|  |                         Expanded( | ||||||
|  |                           child: Column( | ||||||
|  |                             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                             mainAxisSize: MainAxisSize.min, | ||||||
|  |                             children: [ | ||||||
|  |                               Text('fileSize').tr(), | ||||||
|  |                               Text( | ||||||
|  |                                 formatFileSize(item.size), | ||||||
|  |                                 style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                                   fontWeight: FontWeight.bold, | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         if (item.hash != null) | ||||||
|  |                           SizedBox(height: 28, child: const VerticalDivider()), | ||||||
|  |                         if (item.hash != null) | ||||||
|  |                           Expanded( | ||||||
|  |                             child: GestureDetector( | ||||||
|  |                               child: Column( | ||||||
|  |                                 crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                                 mainAxisSize: MainAxisSize.min, | ||||||
|  |                                 children: [ | ||||||
|  |                                   Text('fileHash').tr(), | ||||||
|  |                                   Text( | ||||||
|  |                                     '${item.hash!.substring(0, 6)}...', | ||||||
|  |                                     style: theme.textTheme.titleMedium | ||||||
|  |                                         ?.copyWith(fontWeight: FontWeight.bold), | ||||||
|  |                                   ), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                               onLongPress: () { | ||||||
|  |                                 Clipboard.setData( | ||||||
|  |                                   ClipboardData(text: item.hash!), | ||||||
|  |                                 ); | ||||||
|  |                                 showSnackBar('File hash copied to clipboard'); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                       ], | ||||||
|  |                     ).padding(horizontal: 24, vertical: 16), | ||||||
|  |                     const Divider(height: 1), | ||||||
|  |                     ListTile( | ||||||
|  |                       leading: const Icon(Icons.file_present), | ||||||
|  |                       title: Text('Name').tr(), | ||||||
|  |                       subtitle: Text( | ||||||
|  |                         item.name, | ||||||
|  |                         maxLines: 1, | ||||||
|  |                         overflow: TextOverflow.ellipsis, | ||||||
|  |                       ), | ||||||
|  |                       contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                       trailing: IconButton( | ||||||
|  |                         icon: const Icon(Icons.copy), | ||||||
|  |                         onPressed: () { | ||||||
|  |                           Clipboard.setData(ClipboardData(text: item.name)); | ||||||
|  |                           showSnackBar('File name copied to clipboard'); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     if (exifData.isNotEmpty) ...[ | ||||||
|  |                       const Divider(height: 1), | ||||||
|  |                       Theme( | ||||||
|  |                         data: theme.copyWith(dividerColor: Colors.transparent), | ||||||
|  |                         child: ExpansionTile( | ||||||
|  |                           tilePadding: const EdgeInsets.symmetric( | ||||||
|  |                             horizontal: 24, | ||||||
|  |                           ), | ||||||
|  |                           title: Text( | ||||||
|  |                             'exifData'.tr(), | ||||||
|  |                             style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                               fontWeight: FontWeight.bold, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           children: [ | ||||||
|                             Column( |                             Column( | ||||||
|                               crossAxisAlignment: CrossAxisAlignment.start, |                               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                               children: [ |                               children: [ | ||||||
|                                 ...exifData.entries.map( |                                 ...exifData.entries.map( | ||||||
|                             (entry) => Padding( |                                   (entry) => ListTile( | ||||||
|                               padding: const EdgeInsets.symmetric(vertical: 4), |                                     dense: true, | ||||||
|                               child: Row( |                                     contentPadding: EdgeInsets.symmetric( | ||||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, |                                       horizontal: 24, | ||||||
|                                 children: [ |                                     ), | ||||||
|  |                                     title: | ||||||
|                                         Text( |                                         Text( | ||||||
|                                     '• ${entry.key.contains('-') ? entry.key.split('-').last : entry.key}: ', |                                           entry.key.contains('-') | ||||||
|                                     style: theme.textTheme.bodyMedium?.copyWith( |                                               ? entry.key.split('-').last | ||||||
|  |                                               : entry.key, | ||||||
|  |                                           style: theme.textTheme.bodyMedium | ||||||
|  |                                               ?.copyWith( | ||||||
|                                                 fontWeight: FontWeight.w500, |                                                 fontWeight: FontWeight.w500, | ||||||
|                                               ), |                                               ), | ||||||
|                                   ), |                                         ).bold(), | ||||||
|                                   Expanded( |                                     subtitle: Text( | ||||||
|                                     child: Text( |  | ||||||
|                                       '${entry.value}'.isNotEmpty |                                       '${entry.value}'.isNotEmpty | ||||||
|                                           ? '${entry.value}' |                                           ? '${entry.value}' | ||||||
|                                           : 'N/A', |                                           : 'N/A', | ||||||
|                                       style: theme.textTheme.bodyMedium, |                                       style: theme.textTheme.bodyMedium, | ||||||
|                                     ), |                                     ), | ||||||
|  |                                     onTap: () { | ||||||
|  |                                       Clipboard.setData( | ||||||
|  |                                         ClipboardData(text: '${entry.value}'), | ||||||
|  |                                       ); | ||||||
|  |                                       showSnackBar('Value copied to clipboard'); | ||||||
|  |                                     }, | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ], | ||||||
|                             ), |                             ), | ||||||
|                           ], |                           ], | ||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|  |                     ], | ||||||
|  |                     if (item.fileMeta != null && item.fileMeta!.isNotEmpty) ...[ | ||||||
|  |                       const Divider(height: 1), | ||||||
|  |                       Theme( | ||||||
|  |                         data: theme.copyWith(dividerColor: Colors.transparent), | ||||||
|  |                         child: ExpansionTile( | ||||||
|  |                           tilePadding: const EdgeInsets.symmetric( | ||||||
|  |                             horizontal: 24, | ||||||
|  |                           ), | ||||||
|  |                           title: Text( | ||||||
|  |                             'File Metadata', | ||||||
|  |                             style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                               fontWeight: FontWeight.bold, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           children: [ | ||||||
|  |                             Column( | ||||||
|  |                               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                               children: [ | ||||||
|  |                                 ...item.fileMeta!.entries.map( | ||||||
|  |                                   (entry) => ListTile( | ||||||
|  |                                     dense: true, | ||||||
|  |                                     contentPadding: EdgeInsets.symmetric( | ||||||
|  |                                       horizontal: 24, | ||||||
|  |                                     ), | ||||||
|  |                                     title: | ||||||
|  |                                         Text( | ||||||
|  |                                           entry.key, | ||||||
|  |                                           style: theme.textTheme.bodyMedium | ||||||
|  |                                               ?.copyWith( | ||||||
|  |                                                 fontWeight: FontWeight.w500, | ||||||
|  |                                               ), | ||||||
|  |                                         ).bold(), | ||||||
|  |                                     subtitle: Text( | ||||||
|  |                                       jsonEncode(entry.value), | ||||||
|  |                                       style: theme.textTheme.bodyMedium, | ||||||
|  |                                       maxLines: 3, | ||||||
|  |                                       overflow: TextOverflow.ellipsis, | ||||||
|  |                                     ), | ||||||
|  |                                     onTap: () { | ||||||
|  |                                       Clipboard.setData( | ||||||
|  |                                         ClipboardData( | ||||||
|  |                                           text: jsonEncode(entry.value), | ||||||
|  |                                         ), | ||||||
|  |                                       ); | ||||||
|  |                                       showSnackBar('Value copied to clipboard'); | ||||||
|  |                                     }, | ||||||
|  |                                   ), | ||||||
|                                 ), |                                 ), | ||||||
|                               ], |                               ], | ||||||
|                       ).padding(horizontal: 24), |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                     if (item.userMeta != null && item.userMeta!.isNotEmpty) ...[ | ||||||
|  |                       const Divider(height: 1), | ||||||
|  |                       Theme( | ||||||
|  |                         data: theme.copyWith(dividerColor: Colors.transparent), | ||||||
|  |                         child: ExpansionTile( | ||||||
|  |                           tilePadding: const EdgeInsets.symmetric( | ||||||
|  |                             horizontal: 24, | ||||||
|  |                           ), | ||||||
|  |                           title: Text( | ||||||
|  |                             'User Metadata', | ||||||
|  |                             style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                               fontWeight: FontWeight.bold, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           children: [ | ||||||
|  |                             Column( | ||||||
|  |                               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                               children: [ | ||||||
|  |                                 ...item.userMeta!.entries.map( | ||||||
|  |                                   (entry) => ListTile( | ||||||
|  |                                     dense: true, | ||||||
|  |                                     contentPadding: EdgeInsets.symmetric( | ||||||
|  |                                       horizontal: 24, | ||||||
|  |                                     ), | ||||||
|  |                                     title: | ||||||
|  |                                         Text( | ||||||
|  |                                           entry.key, | ||||||
|  |                                           style: theme.textTheme.bodyMedium | ||||||
|  |                                               ?.copyWith( | ||||||
|  |                                                 fontWeight: FontWeight.w500, | ||||||
|  |                                               ), | ||||||
|  |                                         ).bold(), | ||||||
|  |                                     subtitle: Text( | ||||||
|  |                                       jsonEncode(entry.value), | ||||||
|  |                                       style: theme.textTheme.bodyMedium, | ||||||
|  |                                       maxLines: 3, | ||||||
|  |                                       overflow: TextOverflow.ellipsis, | ||||||
|  |                                     ), | ||||||
|  |                                     onTap: () { | ||||||
|  |                                       Clipboard.setData( | ||||||
|  |                                         ClipboardData( | ||||||
|  |                                           text: jsonEncode(entry.value), | ||||||
|  |                                         ), | ||||||
|  |                                       ); | ||||||
|  |                                       showSnackBar('Value copied to clipboard'); | ||||||
|  |                                     }, | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|                     ], |                     ], | ||||||
|                     const SizedBox(height: 16), |                     const SizedBox(height: 16), | ||||||
|                   ], |                   ], | ||||||
|   | |||||||
| @@ -8,6 +8,52 @@ import 'package:island/pods/websocket.dart'; | |||||||
| import 'package:island/widgets/content/network_status_sheet.dart'; | import 'package:island/widgets/content/network_status_sheet.dart'; | ||||||
| import 'package:island/widgets/content/sheet.dart'; | import 'package:island/widgets/content/sheet.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
|  |  | ||||||
|  | Future<void> _showSetTokenDialog(BuildContext context, WidgetRef ref) async { | ||||||
|  |   final TextEditingController controller = TextEditingController(); | ||||||
|  |   final prefs = ref.read(sharedPreferencesProvider); | ||||||
|  |  | ||||||
|  |   return showDialog<void>( | ||||||
|  |     context: context, | ||||||
|  |     builder: (BuildContext context) { | ||||||
|  |       return AlertDialog( | ||||||
|  |         title: const Text('Set Access Token'), | ||||||
|  |         content: TextField( | ||||||
|  |           controller: controller, | ||||||
|  |           decoration: const InputDecoration( | ||||||
|  |             hintText: 'Enter access token', | ||||||
|  |             border: OutlineInputBorder(), | ||||||
|  |           ), | ||||||
|  |           autofocus: true, | ||||||
|  |         ), | ||||||
|  |         actions: <Widget>[ | ||||||
|  |           TextButton( | ||||||
|  |             child: const Text('Cancel'), | ||||||
|  |             onPressed: () { | ||||||
|  |               Navigator.of(context).pop(); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           TextButton( | ||||||
|  |             child: const Text('Set'), | ||||||
|  |             onPressed: () async { | ||||||
|  |               final token = controller.text.trim(); | ||||||
|  |               if (token.isNotEmpty) { | ||||||
|  |                 await setToken(prefs, token); | ||||||
|  |                 ref.invalidate(tokenProvider); | ||||||
|  |                 // Store context in local variable to avoid async gap issue | ||||||
|  |                 final navigatorContext = context; | ||||||
|  |                 if (navigatorContext.mounted) { | ||||||
|  |                   Navigator.of(navigatorContext).pop(); | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
| class DebugSheet extends HookConsumerWidget { | class DebugSheet extends HookConsumerWidget { | ||||||
|   const DebugSheet({super.key}); |   const DebugSheet({super.key}); | ||||||
| @@ -49,6 +95,17 @@ class DebugSheet extends HookConsumerWidget { | |||||||
|               Clipboard.setData(ClipboardData(text: tk!.token)); |               Clipboard.setData(ClipboardData(text: tk!.token)); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|  |           ListTile( | ||||||
|  |             minTileHeight: 48, | ||||||
|  |             leading: const Icon(Symbols.edit), | ||||||
|  |             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |             contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |             title: Text('Set access token'), | ||||||
|  |             onTap: () async { | ||||||
|  |               await _showSetTokenDialog(context, ref); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Divider(height: 1), | ||||||
|           ListTile( |           ListTile( | ||||||
|             minTileHeight: 48, |             minTileHeight: 48, | ||||||
|             leading: const Icon(Symbols.delete), |             leading: const Icon(Symbols.delete), | ||||||
|   | |||||||
| @@ -94,7 +94,7 @@ class PostItemCreator extends HookConsumerWidget { | |||||||
|           borderRadius: BorderRadius.circular(12), |           borderRadius: BorderRadius.circular(12), | ||||||
|           onTap: () { |           onTap: () { | ||||||
|             if (isOpenable) { |             if (isOpenable) { | ||||||
|               context.pushNamed('postDetail', pathParameters: {'id': item.id}); |               context.goNamed('postDetail', pathParameters: {'id': item.id}); | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           child: Padding( |           child: Padding( | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ class PostListNotifier extends _$PostListNotifier | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }) { |   }) { | ||||||
|     return fetch(cursor: null); |     return fetch(cursor: null); | ||||||
|   } |   } | ||||||
| @@ -38,6 +39,7 @@ class PostListNotifier extends _$PostListNotifier | |||||||
|       if (type != null) 'type': type, |       if (type != null) 'type': type, | ||||||
|       if (tags != null) 'tags': tags, |       if (tags != null) 'tags': tags, | ||||||
|       if (categories != null) 'categories': categories, |       if (categories != null) 'categories': categories, | ||||||
|  |       if (shuffle) 'shuffle': true, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     final response = await client.get( |     final response = await client.get( | ||||||
| @@ -74,6 +76,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|   final int? type; |   final int? type; | ||||||
|   final List<String>? categories; |   final List<String>? categories; | ||||||
|   final List<String>? tags; |   final List<String>? tags; | ||||||
|  |   final bool shuffle; | ||||||
|   final PostItemType itemType; |   final PostItemType itemType; | ||||||
|   final Color? backgroundColor; |   final Color? backgroundColor; | ||||||
|   final EdgeInsets? padding; |   final EdgeInsets? padding; | ||||||
| @@ -88,6 +91,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|     this.type, |     this.type, | ||||||
|     this.categories, |     this.categories, | ||||||
|     this.tags, |     this.tags, | ||||||
|  |     this.shuffle = false, | ||||||
|     this.itemType = PostItemType.regular, |     this.itemType = PostItemType.regular, | ||||||
|     this.backgroundColor, |     this.backgroundColor, | ||||||
|     this.padding, |     this.padding, | ||||||
| @@ -105,6 +109,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|         type: type, |         type: type, | ||||||
|         categories: categories, |         categories: categories, | ||||||
|         tags: tags, |         tags: tags, | ||||||
|  |         shuffle: shuffle, | ||||||
|       ), |       ), | ||||||
|       futureRefreshable: |       futureRefreshable: | ||||||
|           postListNotifierProvider( |           postListNotifierProvider( | ||||||
| @@ -113,6 +118,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|             type: type, |             type: type, | ||||||
|             categories: categories, |             categories: categories, | ||||||
|             tags: tags, |             tags: tags, | ||||||
|  |             shuffle: shuffle, | ||||||
|           ).future, |           ).future, | ||||||
|       notifierRefreshable: |       notifierRefreshable: | ||||||
|           postListNotifierProvider( |           postListNotifierProvider( | ||||||
| @@ -121,6 +127,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|             type: type, |             type: type, | ||||||
|             categories: categories, |             categories: categories, | ||||||
|             tags: tags, |             tags: tags, | ||||||
|  |             shuffle: shuffle, | ||||||
|           ).notifier, |           ).notifier, | ||||||
|       contentBuilder: |       contentBuilder: | ||||||
|           (data, widgetCount, endItemView) => SliverList.builder( |           (data, widgetCount, endItemView) => SliverList.builder( | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'post_list.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$postListNotifierHash() => r'9784b282b3ee14b7109e263c5841a082cf0be78e'; | String _$postListNotifierHash() => r'faa0b939fae56367ff120ce63d9deb17b1995c9c'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -36,6 +36,7 @@ abstract class _$PostListNotifier | |||||||
|   late final int? type; |   late final int? type; | ||||||
|   late final List<String>? categories; |   late final List<String>? categories; | ||||||
|   late final List<String>? tags; |   late final List<String>? tags; | ||||||
|  |   late final bool shuffle; | ||||||
|  |  | ||||||
|   FutureOr<CursorPagingData<SnPost>> build({ |   FutureOr<CursorPagingData<SnPost>> build({ | ||||||
|     String? pubName, |     String? pubName, | ||||||
| @@ -43,6 +44,7 @@ abstract class _$PostListNotifier | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -63,6 +65,7 @@ class PostListNotifierFamily | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }) { |   }) { | ||||||
|     return PostListNotifierProvider( |     return PostListNotifierProvider( | ||||||
|       pubName: pubName, |       pubName: pubName, | ||||||
| @@ -70,6 +73,7 @@ class PostListNotifierFamily | |||||||
|       type: type, |       type: type, | ||||||
|       categories: categories, |       categories: categories, | ||||||
|       tags: tags, |       tags: tags, | ||||||
|  |       shuffle: shuffle, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -83,6 +87,7 @@ class PostListNotifierFamily | |||||||
|       type: provider.type, |       type: provider.type, | ||||||
|       categories: provider.categories, |       categories: provider.categories, | ||||||
|       tags: provider.tags, |       tags: provider.tags, | ||||||
|  |       shuffle: provider.shuffle, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -115,6 +120,7 @@ class PostListNotifierProvider | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }) : this._internal( |   }) : this._internal( | ||||||
|          () => |          () => | ||||||
|              PostListNotifier() |              PostListNotifier() | ||||||
| @@ -122,7 +128,8 @@ class PostListNotifierProvider | |||||||
|                ..realm = realm |                ..realm = realm | ||||||
|                ..type = type |                ..type = type | ||||||
|                ..categories = categories |                ..categories = categories | ||||||
|                ..tags = tags, |                ..tags = tags | ||||||
|  |                ..shuffle = shuffle, | ||||||
|          from: postListNotifierProvider, |          from: postListNotifierProvider, | ||||||
|          name: r'postListNotifierProvider', |          name: r'postListNotifierProvider', | ||||||
|          debugGetCreateSourceHash: |          debugGetCreateSourceHash: | ||||||
| @@ -137,6 +144,7 @@ class PostListNotifierProvider | |||||||
|          type: type, |          type: type, | ||||||
|          categories: categories, |          categories: categories, | ||||||
|          tags: tags, |          tags: tags, | ||||||
|  |          shuffle: shuffle, | ||||||
|        ); |        ); | ||||||
|  |  | ||||||
|   PostListNotifierProvider._internal( |   PostListNotifierProvider._internal( | ||||||
| @@ -151,6 +159,7 @@ class PostListNotifierProvider | |||||||
|     required this.type, |     required this.type, | ||||||
|     required this.categories, |     required this.categories, | ||||||
|     required this.tags, |     required this.tags, | ||||||
|  |     required this.shuffle, | ||||||
|   }) : super.internal(); |   }) : super.internal(); | ||||||
|  |  | ||||||
|   final String? pubName; |   final String? pubName; | ||||||
| @@ -158,6 +167,7 @@ class PostListNotifierProvider | |||||||
|   final int? type; |   final int? type; | ||||||
|   final List<String>? categories; |   final List<String>? categories; | ||||||
|   final List<String>? tags; |   final List<String>? tags; | ||||||
|  |   final bool shuffle; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   FutureOr<CursorPagingData<SnPost>> runNotifierBuild( |   FutureOr<CursorPagingData<SnPost>> runNotifierBuild( | ||||||
| @@ -169,6 +179,7 @@ class PostListNotifierProvider | |||||||
|       type: type, |       type: type, | ||||||
|       categories: categories, |       categories: categories, | ||||||
|       tags: tags, |       tags: tags, | ||||||
|  |       shuffle: shuffle, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -183,7 +194,8 @@ class PostListNotifierProvider | |||||||
|               ..realm = realm |               ..realm = realm | ||||||
|               ..type = type |               ..type = type | ||||||
|               ..categories = categories |               ..categories = categories | ||||||
|               ..tags = tags, |               ..tags = tags | ||||||
|  |               ..shuffle = shuffle, | ||||||
|         from: from, |         from: from, | ||||||
|         name: null, |         name: null, | ||||||
|         dependencies: null, |         dependencies: null, | ||||||
| @@ -194,6 +206,7 @@ class PostListNotifierProvider | |||||||
|         type: type, |         type: type, | ||||||
|         categories: categories, |         categories: categories, | ||||||
|         tags: tags, |         tags: tags, | ||||||
|  |         shuffle: shuffle, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -214,7 +227,8 @@ class PostListNotifierProvider | |||||||
|         other.realm == realm && |         other.realm == realm && | ||||||
|         other.type == type && |         other.type == type && | ||||||
|         other.categories == categories && |         other.categories == categories && | ||||||
|         other.tags == tags; |         other.tags == tags && | ||||||
|  |         other.shuffle == shuffle; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -225,6 +239,7 @@ class PostListNotifierProvider | |||||||
|     hash = _SystemHash.combine(hash, type.hashCode); |     hash = _SystemHash.combine(hash, type.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, categories.hashCode); |     hash = _SystemHash.combine(hash, categories.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, tags.hashCode); |     hash = _SystemHash.combine(hash, tags.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, shuffle.hashCode); | ||||||
|  |  | ||||||
|     return _SystemHash.finish(hash); |     return _SystemHash.finish(hash); | ||||||
|   } |   } | ||||||
| @@ -248,6 +263,9 @@ mixin PostListNotifierRef | |||||||
|  |  | ||||||
|   /// The parameter `tags` of this provider. |   /// The parameter `tags` of this provider. | ||||||
|   List<String>? get tags; |   List<String>? get tags; | ||||||
|  |  | ||||||
|  |   /// The parameter `shuffle` of this provider. | ||||||
|  |   bool get shuffle; | ||||||
| } | } | ||||||
|  |  | ||||||
| class _PostListNotifierProviderElement | class _PostListNotifierProviderElement | ||||||
| @@ -270,6 +288,8 @@ class _PostListNotifierProviderElement | |||||||
|       (origin as PostListNotifierProvider).categories; |       (origin as PostListNotifierProvider).categories; | ||||||
|   @override |   @override | ||||||
|   List<String>? get tags => (origin as PostListNotifierProvider).tags; |   List<String>? get tags => (origin as PostListNotifierProvider).tags; | ||||||
|  |   @override | ||||||
|  |   bool get shuffle => (origin as PostListNotifierProvider).shuffle; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
|   | |||||||
							
								
								
									
										108
									
								
								lib/widgets/post/post_shuffle.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								lib/widgets/post/post_shuffle.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_card_swiper/flutter_card_swiper.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/post/post_item.dart'; | ||||||
|  | import 'package:island/widgets/post/post_list.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | class PostShuffleScreen extends HookConsumerWidget { | ||||||
|  |   const PostShuffleScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final postListState = ref.watch(postListNotifierProvider(shuffle: true)); | ||||||
|  |     final postListNotifier = ref.watch( | ||||||
|  |       postListNotifierProvider(shuffle: true).notifier, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final cardSwiperController = useMemoized(() => CardSwiperController(), []); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       return cardSwiperController.dispose; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|  |     const kBottomControlHeight = 96.0; | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('postShuffle').tr()), | ||||||
|  |       body: Stack( | ||||||
|  |         children: [ | ||||||
|  |           Padding( | ||||||
|  |             padding: EdgeInsets.only( | ||||||
|  |               bottom: | ||||||
|  |                   kBottomControlHeight + MediaQuery.of(context).padding.bottom, | ||||||
|  |             ), | ||||||
|  |             child: | ||||||
|  |                 (postListState.value?.items.length ?? 0) > 0 | ||||||
|  |                     ? CardSwiper( | ||||||
|  |                       controller: cardSwiperController, | ||||||
|  |                       cardsCount: postListState.value!.items.length, | ||||||
|  |                       cardBuilder: ( | ||||||
|  |                         context, | ||||||
|  |                         index, | ||||||
|  |                         horizontalOffsetPercentage, | ||||||
|  |                         verticalOffsetPercentage, | ||||||
|  |                       ) { | ||||||
|  |                         return Center( | ||||||
|  |                           child: Card( | ||||||
|  |                             margin: EdgeInsets.zero, | ||||||
|  |                             child: ClipRRect( | ||||||
|  |                               borderRadius: const BorderRadius.all( | ||||||
|  |                                 Radius.circular(8), | ||||||
|  |                               ), | ||||||
|  |                               child: PostActionableItem( | ||||||
|  |                                 item: postListState.value!.items[index], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                       onEnd: () { | ||||||
|  |                         if (postListState.value?.hasMore ?? true) { | ||||||
|  |                           postListNotifier.fetch( | ||||||
|  |                             cursor: postListState.value?.nextCursor, | ||||||
|  |                           ); | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                     ) | ||||||
|  |                     : Center(child: CircularProgressIndicator()), | ||||||
|  |           ), | ||||||
|  |           Positioned( | ||||||
|  |             left: 0, | ||||||
|  |             right: 0, | ||||||
|  |             bottom: 0, | ||||||
|  |             child: Container( | ||||||
|  |               color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |               padding: EdgeInsets.only( | ||||||
|  |                 bottom: MediaQuery.of(context).padding.bottom, | ||||||
|  |               ), | ||||||
|  |               height: kBottomControlHeight, | ||||||
|  |               child: | ||||||
|  |                   Row( | ||||||
|  |                     mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                     children: [ | ||||||
|  |                       IconButton( | ||||||
|  |                         onPressed: () { | ||||||
|  |                           cardSwiperController.undo(); | ||||||
|  |                         }, | ||||||
|  |                         icon: const Icon(Symbols.arrow_left_alt), | ||||||
|  |                       ), | ||||||
|  |                       IconButton( | ||||||
|  |                         onPressed: () { | ||||||
|  |                           cardSwiperController.swipe(CardSwiperDirection.right); | ||||||
|  |                         }, | ||||||
|  |                         icon: const Icon(Symbols.arrow_right_alt), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ).padding(all: 8).center(), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -565,10 +565,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: file_picker |       name: file_picker | ||||||
|       sha256: ef7d2a085c1b1d69d17b6842d0734aad90156de08df6bd3c12496d0bd6ddf8e2 |       sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "10.3.1" |     version: "10.3.2" | ||||||
|   file_selector_linux: |   file_selector_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -734,6 +734,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.4.1" |     version: "3.4.1" | ||||||
|  |   flutter_card_swiper: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: flutter_card_swiper | ||||||
|  |       sha256: "1eacbfab31b572223042e03409726553aec431abe48af48c8d591d376d070d3d" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "7.0.2" | ||||||
|   flutter_colorpicker: |   flutter_colorpicker: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -754,10 +762,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_hooks |       name: flutter_hooks | ||||||
|       sha256: c3df76c62bb3a9f9bee75c57cdab40abab6123b734c1cd7e9b26a5dbd436eceb |       sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.21.3" |     version: "0.21.3+1" | ||||||
|   flutter_inappwebview: |   flutter_inappwebview: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1137,10 +1145,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: go_router |       name: go_router | ||||||
|       sha256: "8b1f37dfaf6e958c6b872322db06f946509433bec3de753c3491a42ae9ec2b48" |       sha256: ced3fdc143c1437234ac3b8e985f3286cf138968bb83ca9a6f94d22f2951c6b9 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "16.1.0" |     version: "16.2.0" | ||||||
|   google_fonts: |   google_fonts: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1497,10 +1505,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: material_symbols_icons |       name: material_symbols_icons | ||||||
|       sha256: ef20d86fb34c2b59eb7553c4d795bb8a7ec8c890c53ffd3148c64f7adc46ae50 |       sha256: b1342194e859b2774f920b484c46f54a37a845488e23d570385fbe3ede92ee9f | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.2858.1" |     version: "4.2867.0" | ||||||
|   media_kit: |   media_kit: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1841,10 +1849,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: provider |       name: provider | ||||||
|       sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" |       sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.1.5" |     version: "6.1.5+1" | ||||||
|   pub_semver: |   pub_semver: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1897,42 +1905,42 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: record |       name: record | ||||||
|       sha256: "3d08502b77edf2a864aa6e4cd7874b983d42a80f3689431da053cc5e85c1ad21" |       sha256: "9dbc6ff3e784612f90a9b001373c45ff76b7a08abd2bd9fdf72c242320c8911c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.1.0" |     version: "6.1.1" | ||||||
|   record_android: |   record_android: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_android |       name: record_android | ||||||
|       sha256: "8b170e33d9866f9b51e01a767d7e1ecb97b9ecd629950bd87a47c79359ec57f8" |       sha256: "8361a791c9a3fa5c065f0b8b5adb10f12531f8538c86b19474cf7b56ea80d426" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.4.0" |     version: "1.4.1" | ||||||
|   record_ios: |   record_ios: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_ios |       name: record_ios | ||||||
|       sha256: ad97d0a75933c44bcf5aff648e86e32fc05eb61f8fbef190f14968c8eaf86692 |       sha256: "895c9467faec72d8e718a3142b51114958f42f18053836a8b484a74f9372f51a" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.0" |     version: "1.1.1" | ||||||
|   record_linux: |   record_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_linux |       name: record_linux | ||||||
|       sha256: "785e8e8d6db109aa606d0669d95aaae416458aaa39782bb0abe0bee74eee17d7" |       sha256: "235b1f1fb84e810f8149cc0c2c731d7d697f8d1c333b32cb820c449bf7bb72d8" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.2.0" |     version: "1.2.1" | ||||||
|   record_macos: |   record_macos: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_macos |       name: record_macos | ||||||
|       sha256: f1399bca76a1634da109e5b0cba764ed8332a2b4da49c704c66d2c553405ed81 |       sha256: "2849068bb59072f300ad63ed146e543d66afaef8263edba4de4834fc7c8d4d35" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.0" |     version: "1.1.1" | ||||||
|   record_platform_interface: |   record_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1953,10 +1961,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_windows |       name: record_windows | ||||||
|       sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99" |       sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.6" |     version: "1.0.7" | ||||||
|   relative_time: |   relative_time: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								pubspec.yaml
									
									
									
									
									
								
							| @@ -36,10 +36,10 @@ dependencies: | |||||||
|   # The following adds the Cupertino Icons font to your application. |   # The following adds the Cupertino Icons font to your application. | ||||||
|   # Use with the CupertinoIcons class for iOS style icons. |   # Use with the CupertinoIcons class for iOS style icons. | ||||||
|   cupertino_icons: ^1.0.8 |   cupertino_icons: ^1.0.8 | ||||||
|   flutter_hooks: ^0.21.3 |   flutter_hooks: ^0.21.3+1 | ||||||
|   hooks_riverpod: ^2.6.1 |   hooks_riverpod: ^2.6.1 | ||||||
|   bitsdojo_window: ^0.1.6 |   bitsdojo_window: ^0.1.6 | ||||||
|   go_router: ^16.1.0 |   go_router: ^16.2.0 | ||||||
|   styled_widget: ^0.4.1 |   styled_widget: ^0.4.1 | ||||||
|   shared_preferences: ^2.5.3 |   shared_preferences: ^2.5.3 | ||||||
|   flutter_riverpod: ^2.6.1 |   flutter_riverpod: ^2.6.1 | ||||||
| @@ -73,7 +73,7 @@ dependencies: | |||||||
|     git: https://github.com/LittleSheep2Code/tus_client.git |     git: https://github.com/LittleSheep2Code/tus_client.git | ||||||
|   cross_file: ^0.3.4+2 |   cross_file: ^0.3.4+2 | ||||||
|   image_picker: ^1.2.0 |   image_picker: ^1.2.0 | ||||||
|   file_picker: ^10.3.1 |   file_picker: ^10.3.2 | ||||||
|   riverpod_annotation: ^2.6.1 |   riverpod_annotation: ^2.6.1 | ||||||
|   image_picker_platform_interface: ^2.11.0 |   image_picker_platform_interface: ^2.11.0 | ||||||
|   image_picker_android: ^0.8.13 |   image_picker_android: ^0.8.13 | ||||||
| @@ -83,7 +83,7 @@ dependencies: | |||||||
|   flutter_udid: ^4.0.0 |   flutter_udid: ^4.0.0 | ||||||
|   firebase_core: ^4.0.0 |   firebase_core: ^4.0.0 | ||||||
|   web_socket_channel: ^3.0.3 |   web_socket_channel: ^3.0.3 | ||||||
|   material_symbols_icons: ^4.2858.1 |   material_symbols_icons: ^4.2867.0 | ||||||
|   drift: ^2.28.1 |   drift: ^2.28.1 | ||||||
|   drift_flutter: ^0.2.5 |   drift_flutter: ^0.2.5 | ||||||
|   path: ^1.9.1 |   path: ^1.9.1 | ||||||
| @@ -107,7 +107,7 @@ dependencies: | |||||||
|   livekit_client: ^2.5.0+hotfix.1 |   livekit_client: ^2.5.0+hotfix.1 | ||||||
|   pasteboard: ^0.4.0 |   pasteboard: ^0.4.0 | ||||||
|   flutter_colorpicker: ^1.1.0 |   flutter_colorpicker: ^1.1.0 | ||||||
|   record: ^6.1.0 |   record: ^6.1.1 | ||||||
|   qr_flutter: ^4.1.0 |   qr_flutter: ^4.1.0 | ||||||
|   flutter_otp_text_field: ^1.5.1+1 |   flutter_otp_text_field: ^1.5.1+1 | ||||||
|   palette_generator: ^0.3.3+7 |   palette_generator: ^0.3.3+7 | ||||||
| @@ -138,6 +138,7 @@ dependencies: | |||||||
|   firebase_analytics: ^12.0.0 |   firebase_analytics: ^12.0.0 | ||||||
|   material_color_utilities: ^0.11.1 |   material_color_utilities: ^0.11.1 | ||||||
|   screenshot: ^3.0.0 |   screenshot: ^3.0.0 | ||||||
|  |   flutter_card_swiper: ^7.0.2 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user