✨ Post browse by categories, tags
This commit is contained in:
@@ -854,5 +854,11 @@
|
||||
"failedToLoadUserInfo": "Failed to load user info",
|
||||
"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.",
|
||||
"okay": "Okay"
|
||||
"okay": "Okay",
|
||||
"postDetails": "Post Details",
|
||||
"postCount": {
|
||||
"zero": "No posts",
|
||||
"one": "{} post",
|
||||
"other": "{} posts"
|
||||
}
|
||||
}
|
||||
|
@@ -828,5 +828,6 @@
|
||||
"failedToLoadUserInfo": "加载用户信息失败",
|
||||
"failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试",
|
||||
"failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。",
|
||||
"okay": "了解"
|
||||
"okay": "了解",
|
||||
"postDetails": "帖子详情"
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ sealed class SnPostCategory with _$SnPostCategory {
|
||||
required String slug,
|
||||
String? name,
|
||||
@Default([]) List<SnPost> posts,
|
||||
@Default(0) int usage,
|
||||
}) = _SnPostCategory;
|
||||
|
||||
factory SnPostCategory.fromJson(Map<String, dynamic> json) =>
|
||||
|
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -28,16 +28,16 @@ $SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWith
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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;
|
||||
@useResult
|
||||
$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
|
||||
/// 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(
|
||||
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,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 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) {
|
||||
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();
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
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`
|
||||
///
|
||||
@@ -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) {
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
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);
|
||||
|
||||
@override final String id;
|
||||
@@ -219,6 +220,7 @@ class _SnPostCategory extends SnPostCategory {
|
||||
return EqualUnmodifiableListView(_posts);
|
||||
}
|
||||
|
||||
@override@JsonKey() final int usage;
|
||||
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -233,16 +235,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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;
|
||||
@override @useResult
|
||||
$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
|
||||
/// 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(
|
||||
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,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 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>))
|
||||
.toList() ??
|
||||
const [],
|
||||
usage: (json['usage'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) =>
|
||||
@@ -24,4 +25,5 @@ Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) =>
|
||||
'slug': instance.slug,
|
||||
'name': instance.name,
|
||||
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||
'usage': instance.usage,
|
||||
};
|
||||
|
@@ -11,6 +11,7 @@ sealed class SnPostTag with _$SnPostTag {
|
||||
required String slug,
|
||||
String? name,
|
||||
@Default([]) List<SnPost> posts,
|
||||
@Default(0) int usage,
|
||||
}) = _SnPostTag;
|
||||
|
||||
factory SnPostTag.fromJson(Map<String, dynamic> json) =>
|
||||
|
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -28,16 +28,16 @@ $SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag>
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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;
|
||||
@useResult
|
||||
$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
|
||||
/// 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(
|
||||
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,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 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) {
|
||||
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();
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
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`
|
||||
///
|
||||
@@ -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) {
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
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);
|
||||
|
||||
@override final String id;
|
||||
@@ -219,6 +220,7 @@ class _SnPostTag implements SnPostTag {
|
||||
return EqualUnmodifiableListView(_posts);
|
||||
}
|
||||
|
||||
@override@JsonKey() final int usage;
|
||||
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -233,16 +235,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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;
|
||||
@override @useResult
|
||||
$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
|
||||
/// 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(
|
||||
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,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 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>))
|
||||
.toList() ??
|
||||
const [],
|
||||
usage: (json['usage'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) =>
|
||||
@@ -23,4 +24,5 @@ Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) =>
|
||||
'slug': instance.slug,
|
||||
'name': instance.name,
|
||||
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||
'usage': instance.usage,
|
||||
};
|
||||
|
@@ -11,6 +11,7 @@ import 'package:island/screens/developers/edit_app.dart';
|
||||
import 'package:island/screens/developers/new_app.dart';
|
||||
import 'package:island/screens/developers/hub.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_search.dart';
|
||||
import 'package:island/widgets/app_wrapper.dart';
|
||||
@@ -376,12 +377,9 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
builder: (context, state) => const PostSearchScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postDetail',
|
||||
path: '/posts/:id',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
return PostDetailScreen(id: id);
|
||||
},
|
||||
name: 'postCategories',
|
||||
path: '/posts/categories',
|
||||
builder: (context, state) => const PostCategoriesListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postCategoryDetail',
|
||||
@@ -391,6 +389,11 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
return PostCategoryDetailScreen(slug: slug, isCategory: true);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postTags',
|
||||
path: '/posts/tags',
|
||||
builder: (context, state) => const PostTagsListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postTagDetail',
|
||||
path: '/posts/tags/:slug',
|
||||
@@ -402,6 +405,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(
|
||||
name: 'publisherProfile',
|
||||
path: '/publishers/:name',
|
||||
|
@@ -7,7 +7,7 @@ part of 'room_detail.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatMemberListNotifierHash() =>
|
||||
r'c8fbf4b95df6dae24b1ba21063e9a43351832974';
|
||||
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@@ -148,7 +148,7 @@ class _StickerPackProviderElement
|
||||
}
|
||||
|
||||
String _$stickerPacksNotifierHash() =>
|
||||
r'0a8edcf9c35396c411f1214f5e77b1e8fac6a3e6';
|
||||
r'30024b35235f3085a5b1ec2204d0a974ee907e22';
|
||||
|
||||
abstract class _$StickerPacksNotifier
|
||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> {
|
||||
|
@@ -173,12 +173,48 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
),
|
||||
tooltip: 'webArticlesStand'.tr(),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.pushNamed('postSearch');
|
||||
},
|
||||
PopupMenuButton(
|
||||
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.search),
|
||||
const Gap(12),
|
||||
Text('search').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
context.pushNamed('postSearch');
|
||||
},
|
||||
),
|
||||
],
|
||||
icon: Icon(
|
||||
Symbols.search,
|
||||
Symbols.action_key,
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
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')),
|
||||
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')),
|
||||
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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -399,7 +399,7 @@ class _RealmChatRoomsProviderElement
|
||||
}
|
||||
|
||||
String _$realmMemberListNotifierHash() =>
|
||||
r'022bcef5a90cbae05ff23b937851afc3ef913d42';
|
||||
r'2f88f803b2e61e7287ed8a43025173e56ff6ca3b';
|
||||
|
||||
abstract class _$RealmMemberListNotifier
|
||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> {
|
||||
|
@@ -50,6 +50,6 @@ class AppWrapper extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
return TourTriggerWidget(child: child);
|
||||
return TourTriggerWidget(key: UniqueKey(), child: child);
|
||||
}
|
||||
}
|
||||
|
@@ -94,7 +94,7 @@ class PostItemCreator extends HookConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () {
|
||||
if (isOpenable) {
|
||||
context.pushNamed('postDetail', pathParameters: {'id': item.id});
|
||||
context.goNamed('postDetail', pathParameters: {'id': item.id});
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
|
Reference in New Issue
Block a user