✨ Explore subscription filter card
This commit is contained in:
@@ -10,6 +10,7 @@ part 'post_list.freezed.dart';
|
|||||||
sealed class PostListQuery with _$PostListQuery {
|
sealed class PostListQuery with _$PostListQuery {
|
||||||
const factory PostListQuery({
|
const factory PostListQuery({
|
||||||
String? pubName,
|
String? pubName,
|
||||||
|
List<String>? publishers,
|
||||||
String? realm,
|
String? realm,
|
||||||
int? type,
|
int? type,
|
||||||
List<String>? categories,
|
List<String>? categories,
|
||||||
@@ -61,35 +62,98 @@ class PostListNotifier extends AsyncNotifier<List<SnPost>>
|
|||||||
Future<List<SnPost>> fetch() async {
|
Future<List<SnPost>> fetch() async {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
|
|
||||||
final queryParams = {
|
// Handle multiple publishers by making separate requests and combining results
|
||||||
'offset': fetchedCount,
|
if (currentFilter.publishers != null &&
|
||||||
'take': pageSize,
|
currentFilter.publishers!.isNotEmpty) {
|
||||||
'replies': currentFilter.includeReplies,
|
final allPosts = <SnPost>[];
|
||||||
'orderDesc': currentFilter.orderDesc,
|
var totalPostsCount = 0;
|
||||||
if (currentFilter.shuffle) 'shuffle': currentFilter.shuffle,
|
|
||||||
if (currentFilter.pubName != null) 'pub': currentFilter.pubName,
|
|
||||||
if (currentFilter.realm != null) 'realm': currentFilter.realm,
|
|
||||||
if (currentFilter.type != null) 'type': currentFilter.type,
|
|
||||||
if (currentFilter.tags != null) 'tags': currentFilter.tags,
|
|
||||||
if (currentFilter.categories != null)
|
|
||||||
'categories': currentFilter.categories,
|
|
||||||
if (currentFilter.pinned != null) 'pinned': currentFilter.pinned,
|
|
||||||
if (currentFilter.order != null) 'order': currentFilter.order,
|
|
||||||
if (currentFilter.periodStart != null)
|
|
||||||
'periodStart': currentFilter.periodStart,
|
|
||||||
if (currentFilter.periodEnd != null) 'periodEnd': currentFilter.periodEnd,
|
|
||||||
if (currentFilter.queryTerm != null) 'query': currentFilter.queryTerm,
|
|
||||||
if (currentFilter.mediaOnly != null) 'media': currentFilter.mediaOnly,
|
|
||||||
};
|
|
||||||
|
|
||||||
final response = await client.get(
|
for (final publisherName in currentFilter.publishers!) {
|
||||||
'/sphere/posts',
|
final queryParams = {
|
||||||
queryParameters: queryParams,
|
'offset': fetchedCount,
|
||||||
);
|
'take': pageSize,
|
||||||
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
'replies': currentFilter.includeReplies,
|
||||||
return response.data
|
'orderDesc': currentFilter.orderDesc,
|
||||||
.map((json) => SnPost.fromJson(json))
|
if (currentFilter.shuffle) 'shuffle': currentFilter.shuffle,
|
||||||
.cast<SnPost>()
|
'pub': publisherName,
|
||||||
.toList();
|
if (currentFilter.realm != null) 'realm': currentFilter.realm,
|
||||||
|
if (currentFilter.type != null) 'type': currentFilter.type,
|
||||||
|
if (currentFilter.tags != null) 'tags': currentFilter.tags,
|
||||||
|
if (currentFilter.categories != null)
|
||||||
|
'categories': currentFilter.categories,
|
||||||
|
if (currentFilter.pinned != null) 'pinned': currentFilter.pinned,
|
||||||
|
if (currentFilter.order != null) 'order': currentFilter.order,
|
||||||
|
if (currentFilter.periodStart != null)
|
||||||
|
'periodStart': currentFilter.periodStart,
|
||||||
|
if (currentFilter.periodEnd != null)
|
||||||
|
'periodEnd': currentFilter.periodEnd,
|
||||||
|
if (currentFilter.queryTerm != null) 'query': currentFilter.queryTerm,
|
||||||
|
if (currentFilter.mediaOnly != null) 'media': currentFilter.mediaOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
'/sphere/posts',
|
||||||
|
queryParameters: queryParams,
|
||||||
|
);
|
||||||
|
|
||||||
|
final posts = response.data
|
||||||
|
.map((json) => SnPost.fromJson(json))
|
||||||
|
.cast<SnPost>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
allPosts.addAll(posts);
|
||||||
|
totalPostsCount += int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort combined results by creation date (newest first)
|
||||||
|
allPosts.sort(
|
||||||
|
(a, b) => (b.createdAt ?? DateTime.now()).compareTo(
|
||||||
|
a.createdAt ?? DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply pagination to combined results
|
||||||
|
final startIndex = fetchedCount;
|
||||||
|
final endIndex = (fetchedCount + pageSize).clamp(0, allPosts.length);
|
||||||
|
final paginatedPosts = startIndex < allPosts.length
|
||||||
|
? allPosts.sublist(startIndex, endIndex)
|
||||||
|
: <SnPost>[];
|
||||||
|
|
||||||
|
totalCount = totalPostsCount;
|
||||||
|
return paginatedPosts;
|
||||||
|
} else {
|
||||||
|
// Single publisher or no publisher filter
|
||||||
|
final queryParams = {
|
||||||
|
'offset': fetchedCount,
|
||||||
|
'take': pageSize,
|
||||||
|
'replies': currentFilter.includeReplies,
|
||||||
|
'orderDesc': currentFilter.orderDesc,
|
||||||
|
if (currentFilter.shuffle) 'shuffle': currentFilter.shuffle,
|
||||||
|
if (currentFilter.pubName != null) 'pub': currentFilter.pubName,
|
||||||
|
if (currentFilter.realm != null) 'realm': currentFilter.realm,
|
||||||
|
if (currentFilter.type != null) 'type': currentFilter.type,
|
||||||
|
if (currentFilter.tags != null) 'tags': currentFilter.tags,
|
||||||
|
if (currentFilter.categories != null)
|
||||||
|
'categories': currentFilter.categories,
|
||||||
|
if (currentFilter.pinned != null) 'pinned': currentFilter.pinned,
|
||||||
|
if (currentFilter.order != null) 'order': currentFilter.order,
|
||||||
|
if (currentFilter.periodStart != null)
|
||||||
|
'periodStart': currentFilter.periodStart,
|
||||||
|
if (currentFilter.periodEnd != null)
|
||||||
|
'periodEnd': currentFilter.periodEnd,
|
||||||
|
if (currentFilter.queryTerm != null) 'query': currentFilter.queryTerm,
|
||||||
|
if (currentFilter.mediaOnly != null) 'media': currentFilter.mediaOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
'/sphere/posts',
|
||||||
|
queryParameters: queryParams,
|
||||||
|
);
|
||||||
|
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
return response.data
|
||||||
|
.map((json) => SnPost.fromJson(json))
|
||||||
|
.cast<SnPost>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$PostListQuery {
|
mixin _$PostListQuery {
|
||||||
|
|
||||||
String? get pubName; String? get realm; int? get type; List<String>? get categories; List<String>? get tags; bool? get pinned; bool get shuffle; bool? get includeReplies; bool? get mediaOnly; String? get queryTerm; String? get order; int? get periodStart; int? get periodEnd; bool get orderDesc;
|
String? get pubName; List<String>? get publishers; String? get realm; int? get type; List<String>? get categories; List<String>? get tags; bool? get pinned; bool get shuffle; bool? get includeReplies; bool? get mediaOnly; String? get queryTerm; String? get order; int? get periodStart; int? get periodEnd; bool get orderDesc;
|
||||||
/// Create a copy of PostListQuery
|
/// Create a copy of PostListQuery
|
||||||
/// 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)
|
||||||
@@ -25,16 +25,16 @@ $PostListQueryCopyWith<PostListQuery> get copyWith => _$PostListQueryCopyWithImp
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&const DeepCollectionEquality().equals(other.publishers, publishers)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,pubName,realm,type,const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
int get hashCode => Object.hash(runtimeType,pubName,const DeepCollectionEquality().hash(publishers),realm,type,const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostListQuery(pubName: $pubName, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
return 'PostListQuery(pubName: $pubName, publishers: $publishers, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ abstract mixin class $PostListQueryCopyWith<$Res> {
|
|||||||
factory $PostListQueryCopyWith(PostListQuery value, $Res Function(PostListQuery) _then) = _$PostListQueryCopyWithImpl;
|
factory $PostListQueryCopyWith(PostListQuery value, $Res Function(PostListQuery) _then) = _$PostListQueryCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -62,10 +62,11 @@ class _$PostListQueryCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of PostListQuery
|
/// Create a copy of PostListQuery
|
||||||
/// 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? pubName = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? pubName = freezed,Object? publishers = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
as String?,publishers: freezed == publishers ? _self.publishers : publishers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,categories: freezed == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
as int?,categories: freezed == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<String>?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -160,10 +161,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostListQuery() when $default != null:
|
case _PostListQuery() when $default != null:
|
||||||
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
return $default(_that.pubName,_that.publishers,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -181,10 +182,10 @@ return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostListQuery():
|
case _PostListQuery():
|
||||||
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);}
|
return $default(_that.pubName,_that.publishers,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -198,10 +199,10 @@ return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostListQuery() when $default != null:
|
case _PostListQuery() when $default != null:
|
||||||
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
return $default(_that.pubName,_that.publishers,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -213,10 +214,19 @@ return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags
|
|||||||
|
|
||||||
|
|
||||||
class _PostListQuery implements PostListQuery {
|
class _PostListQuery implements PostListQuery {
|
||||||
const _PostListQuery({this.pubName, this.realm, this.type, final List<String>? categories, final List<String>? tags, this.pinned, this.shuffle = false, this.includeReplies, this.mediaOnly, this.queryTerm, this.order, this.periodStart, this.periodEnd, this.orderDesc = true}): _categories = categories,_tags = tags;
|
const _PostListQuery({this.pubName, final List<String>? publishers, this.realm, this.type, final List<String>? categories, final List<String>? tags, this.pinned, this.shuffle = false, this.includeReplies, this.mediaOnly, this.queryTerm, this.order, this.periodStart, this.periodEnd, this.orderDesc = true}): _publishers = publishers,_categories = categories,_tags = tags;
|
||||||
|
|
||||||
|
|
||||||
@override final String? pubName;
|
@override final String? pubName;
|
||||||
|
final List<String>? _publishers;
|
||||||
|
@override List<String>? get publishers {
|
||||||
|
final value = _publishers;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_publishers is EqualUnmodifiableListView) return _publishers;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
@override final String? realm;
|
@override final String? realm;
|
||||||
@override final int? type;
|
@override final int? type;
|
||||||
final List<String>? _categories;
|
final List<String>? _categories;
|
||||||
@@ -257,16 +267,16 @@ _$PostListQueryCopyWith<_PostListQuery> get copyWith => __$PostListQueryCopyWith
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&const DeepCollectionEquality().equals(other._publishers, _publishers)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,pubName,realm,type,const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
int get hashCode => Object.hash(runtimeType,pubName,const DeepCollectionEquality().hash(_publishers),realm,type,const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostListQuery(pubName: $pubName, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
return 'PostListQuery(pubName: $pubName, publishers: $publishers, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -277,7 +287,7 @@ abstract mixin class _$PostListQueryCopyWith<$Res> implements $PostListQueryCopy
|
|||||||
factory _$PostListQueryCopyWith(_PostListQuery value, $Res Function(_PostListQuery) _then) = __$PostListQueryCopyWithImpl;
|
factory _$PostListQueryCopyWith(_PostListQuery value, $Res Function(_PostListQuery) _then) = __$PostListQueryCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -294,10 +304,11 @@ class __$PostListQueryCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of PostListQuery
|
/// Create a copy of PostListQuery
|
||||||
/// 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? pubName = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? pubName = freezed,Object? publishers = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
||||||
return _then(_PostListQuery(
|
return _then(_PostListQuery(
|
||||||
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
as String?,publishers: freezed == publishers ? _self._publishers : publishers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,categories: freezed == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
as int?,categories: freezed == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<String>?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:island/models/post.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
|
||||||
|
|
||||||
final subscriptionsProvider = FutureProvider<List<SnPublisherSubscription>>((
|
|
||||||
ref,
|
|
||||||
) async {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
|
|
||||||
final response = await client.get('/sphere/subscriptions');
|
|
||||||
|
|
||||||
return response.data
|
|
||||||
.map((json) => SnPublisherSubscription.fromJson(json))
|
|
||||||
.cast<SnPublisherSubscription>()
|
|
||||||
.toList();
|
|
||||||
});
|
|
||||||
@@ -23,6 +23,7 @@ import 'package:island/widgets/navigation/fab_menu.dart';
|
|||||||
import 'package:island/widgets/paging/pagination_list.dart';
|
import 'package:island/widgets/paging/pagination_list.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/post/post_item_skeleton.dart';
|
import 'package:island/widgets/post/post_item_skeleton.dart';
|
||||||
|
import 'package:island/widgets/post/post_list.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:island/widgets/realm/realm_card.dart';
|
import 'package:island/widgets/realm/realm_card.dart';
|
||||||
import 'package:island/widgets/publisher/publisher_card.dart';
|
import 'package:island/widgets/publisher/publisher_card.dart';
|
||||||
@@ -31,6 +32,8 @@ import 'package:island/services/event_bus.dart';
|
|||||||
import 'package:island/widgets/share/share_sheet.dart';
|
import 'package:island/widgets/share/share_sheet.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||||
|
import 'package:island/widgets/posts/post_subscription_filter.dart';
|
||||||
|
import 'package:island/pods/post/post_list.dart';
|
||||||
|
|
||||||
class ExploreScreen extends HookConsumerWidget {
|
class ExploreScreen extends HookConsumerWidget {
|
||||||
const ExploreScreen({super.key});
|
const ExploreScreen({super.key});
|
||||||
@@ -38,6 +41,7 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final currentFilter = useState<String?>(null);
|
final currentFilter = useState<String?>(null);
|
||||||
|
final selectedPublisherNames = useState<List<String>>([]);
|
||||||
final notifier = ref.watch(activityListProvider.notifier);
|
final notifier = ref.watch(activityListProvider.notifier);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@@ -87,6 +91,8 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final isWide = isWideScreen(context);
|
final isWide = isWideScreen(context);
|
||||||
|
|
||||||
|
final hasSubscriptionsSelected = selectedPublisherNames.value.isNotEmpty;
|
||||||
|
|
||||||
final filterBar = Card(
|
final filterBar = Card(
|
||||||
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -95,7 +101,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange(null),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange(null),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.explore,
|
Symbols.explore,
|
||||||
fill: currentFilter.value == null ? 1 : null,
|
fill: currentFilter.value == null ? 1 : null,
|
||||||
@@ -107,7 +115,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange('subscriptions'),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange('subscriptions'),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.subscriptions,
|
Symbols.subscriptions,
|
||||||
fill: currentFilter.value == 'subscriptions' ? 1 : null,
|
fill: currentFilter.value == 'subscriptions' ? 1 : null,
|
||||||
@@ -119,7 +129,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange('friends'),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange('friends'),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.people,
|
Symbols.people,
|
||||||
fill: currentFilter.value == 'friends' ? 1 : null,
|
fill: currentFilter.value == 'friends' ? 1 : null,
|
||||||
@@ -188,7 +200,12 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final appBar = isWide
|
final appBar = isWide
|
||||||
? null
|
? null
|
||||||
: _buildAppBar(currentFilter.value, handleFilterChange, context);
|
: _buildAppBar(
|
||||||
|
currentFilter.value,
|
||||||
|
handleFilterChange,
|
||||||
|
context,
|
||||||
|
hasSubscriptionsSelected,
|
||||||
|
);
|
||||||
|
|
||||||
final dragging = useState(false);
|
final dragging = useState(false);
|
||||||
|
|
||||||
@@ -221,6 +238,8 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
query,
|
query,
|
||||||
events,
|
events,
|
||||||
selectedDay,
|
selectedDay,
|
||||||
|
currentFilter.value,
|
||||||
|
selectedPublisherNames,
|
||||||
)
|
)
|
||||||
: _buildNarrowBody(context, ref, currentFilter.value),
|
: _buildNarrowBody(context, ref, currentFilter.value),
|
||||||
),
|
),
|
||||||
@@ -273,6 +292,19 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildPostList(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
List<String> selectedPublisherIds,
|
||||||
|
) {
|
||||||
|
return SliverPostList(
|
||||||
|
queryKey: 'explore_filtered',
|
||||||
|
query: PostListQuery(publishers: selectedPublisherIds),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemPadding: EdgeInsets.zero,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildWideBody(
|
Widget _buildWideBody(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
@@ -282,10 +314,18 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
ValueNotifier<EventCalendarQuery> query,
|
ValueNotifier<EventCalendarQuery> query,
|
||||||
AsyncValue<List<dynamic>> events,
|
AsyncValue<List<dynamic>> events,
|
||||||
ValueNotifier<DateTime> selectedDay,
|
ValueNotifier<DateTime> selectedDay,
|
||||||
|
String? currentFilter,
|
||||||
|
ValueNotifier<List<String>> selectedPublisherNames,
|
||||||
) {
|
) {
|
||||||
final bodyView = _buildActivityList(context, ref);
|
// Use post list when subscription filter is active and publishers are selected
|
||||||
|
final usePostList = selectedPublisherNames.value.isNotEmpty;
|
||||||
|
final bodyView = usePostList
|
||||||
|
? _buildPostList(context, ref, selectedPublisherNames.value)
|
||||||
|
: _buildActivityList(context, ref);
|
||||||
|
|
||||||
final notifier = ref.watch(activityListProvider.notifier);
|
final notifier = usePostList
|
||||||
|
? null // Post list handles its own refreshing
|
||||||
|
: ref.watch(activityListProvider.notifier);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
@@ -293,7 +333,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: ExtendedRefreshIndicator(
|
child: ExtendedRefreshIndicator(
|
||||||
onRefresh: notifier.refresh,
|
onRefresh: () async {
|
||||||
|
await notifier?.refresh();
|
||||||
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverGap(12),
|
const SliverGap(12),
|
||||||
@@ -310,7 +352,19 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(spacing: 8, children: [const Gap(4)]),
|
child: Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Gap(4 + MediaQuery.paddingOf(context).top),
|
||||||
|
PostSubscriptionFilterWidget(
|
||||||
|
initialSelectedPublisherNames:
|
||||||
|
selectedPublisherNames.value,
|
||||||
|
onSelectedPublishersChanged: (names) {
|
||||||
|
selectedPublisherNames.value = names;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -358,6 +412,7 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
String? currentFilter,
|
String? currentFilter,
|
||||||
void Function(String?) handleFilterChange,
|
void Function(String?) handleFilterChange,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
bool hasSubscriptionsSelected,
|
||||||
) {
|
) {
|
||||||
final foregroundColor = Theme.of(context).appBarTheme.foregroundColor;
|
final foregroundColor = Theme.of(context).appBarTheme.foregroundColor;
|
||||||
|
|
||||||
@@ -376,7 +431,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange(null),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange(null),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.explore,
|
Symbols.explore,
|
||||||
color: foregroundColor,
|
color: foregroundColor,
|
||||||
@@ -387,7 +444,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
color: currentFilter == null ? foregroundColor : null,
|
color: currentFilter == null ? foregroundColor : null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange('subscriptions'),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange('subscriptions'),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.subscriptions,
|
Symbols.subscriptions,
|
||||||
color: foregroundColor,
|
color: foregroundColor,
|
||||||
@@ -397,7 +456,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
isSelected: currentFilter == 'subscriptions',
|
isSelected: currentFilter == 'subscriptions',
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange('friends'),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange('friends'),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.people,
|
Symbols.people,
|
||||||
color: foregroundColor,
|
color: foregroundColor,
|
||||||
@@ -477,12 +538,7 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: ExtendedRefreshIndicator(
|
child: ExtendedRefreshIndicator(
|
||||||
onRefresh: notifier.refresh,
|
onRefresh: notifier.refresh,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(slivers: [SliverGap(8), bodyView]),
|
||||||
slivers: [
|
|
||||||
SliverGap(8 + MediaQuery.paddingOf(context).top),
|
|
||||||
bodyView,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
).padding(horizontal: 8),
|
).padding(horizontal: 8),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
final PostItemType itemType;
|
final PostItemType itemType;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
|
final EdgeInsets? itemPadding;
|
||||||
final bool isOpenable;
|
final bool isOpenable;
|
||||||
final Function? onRefresh;
|
final Function? onRefresh;
|
||||||
final Function(SnPost)? onUpdate;
|
final Function(SnPost)? onUpdate;
|
||||||
@@ -34,6 +35,7 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
this.itemType = PostItemType.regular,
|
this.itemType = PostItemType.regular,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.padding,
|
this.padding,
|
||||||
|
this.itemPadding,
|
||||||
this.isOpenable = true,
|
this.isOpenable = true,
|
||||||
this.onRefresh,
|
this.onRefresh,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
@@ -74,17 +76,17 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
return Center(
|
return Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth!),
|
constraints: BoxConstraints(maxWidth: maxWidth!),
|
||||||
child: _buildPostItem(post),
|
child: _buildPostItem(post, itemPadding),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _buildPostItem(post);
|
return _buildPostItem(post, itemPadding);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPostItem(SnPost post) {
|
Widget _buildPostItem(SnPost post, EdgeInsets? padding) {
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case PostItemType.creator:
|
case PostItemType.creator:
|
||||||
return PostItemCreator(
|
return PostItemCreator(
|
||||||
@@ -97,7 +99,8 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
case PostItemType.regular:
|
case PostItemType.regular:
|
||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
margin:
|
||||||
|
itemPadding ?? EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: PostActionableItem(item: post, borderRadius: 8),
|
child: PostActionableItem(item: post, borderRadius: 8),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,153 +3,129 @@ import 'package:flutter/material.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';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/post/post_subscriptions.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
final subscriptionsProvider = FutureProvider<List<SnPublisherSubscription>>((
|
||||||
|
ref,
|
||||||
|
) async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
|
||||||
|
final response = await client.get('/sphere/publishers/subscriptions');
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
.map((json) => SnPublisherSubscription.fromJson(json))
|
||||||
|
.cast<SnPublisherSubscription>()
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
|
||||||
class PostSubscriptionFilterWidget extends HookConsumerWidget {
|
class PostSubscriptionFilterWidget extends HookConsumerWidget {
|
||||||
final List<String> initialSelectedPublisherIds;
|
final List<String> initialSelectedPublisherNames;
|
||||||
final ValueChanged<List<String>> onSelectedPublishersChanged;
|
final ValueChanged<List<String>> onSelectedPublishersChanged;
|
||||||
final bool hideSearch;
|
final bool hideSearch;
|
||||||
|
|
||||||
const PostSubscriptionFilterWidget({
|
const PostSubscriptionFilterWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.initialSelectedPublisherIds,
|
required this.initialSelectedPublisherNames,
|
||||||
required this.onSelectedPublishersChanged,
|
required this.onSelectedPublishersChanged,
|
||||||
this.hideSearch = false,
|
this.hideSearch = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final selectedPublisherIds = useState<List<String>>(
|
final selectedPublisherNames = useState<List<String>>(
|
||||||
initialSelectedPublisherIds,
|
initialSelectedPublisherNames,
|
||||||
);
|
);
|
||||||
final showSubscriptions = useState<bool>(false);
|
|
||||||
|
|
||||||
final subscriptionsAsync = ref.watch(subscriptionsProvider);
|
final subscriptionsAsync = ref.watch(subscriptionsProvider);
|
||||||
|
|
||||||
void updateSelection() {
|
void updateSelection() {
|
||||||
onSelectedPublishersChanged(selectedPublisherIds.value);
|
onSelectedPublishersChanged(selectedPublisherNames.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
Row(
|
||||||
title: Text('filterBySubscriptions'.tr()),
|
spacing: 16,
|
||||||
leading: const Icon(Symbols.subscriptions),
|
children: [
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
const Icon(Symbols.subscriptions, size: 20),
|
||||||
shape: RoundedRectangleBorder(
|
Text(
|
||||||
borderRadius: BorderRadius.all(const Radius.circular(8)),
|
'exploreFilterSubscriptions'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
trailing: Icon(
|
),
|
||||||
showSubscriptions.value
|
],
|
||||||
? Symbols.expand_less
|
).padding(horizontal: 16, top: 12),
|
||||||
: Symbols.expand_more,
|
const Gap(12),
|
||||||
),
|
subscriptionsAsync.when(
|
||||||
onTap: () {
|
data: (subscriptions) {
|
||||||
showSubscriptions.value = !showSubscriptions.value;
|
if (subscriptions.isEmpty) {
|
||||||
},
|
return Center(
|
||||||
),
|
child: Padding(
|
||||||
if (showSubscriptions.value) ...[
|
padding: const EdgeInsets.all(16.0),
|
||||||
const Divider(height: 1),
|
child: Text('noSubscriptions'.tr()),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
subscriptionsAsync.when(
|
|
||||||
data: (subscriptions) {
|
|
||||||
if (subscriptions.isEmpty) {
|
|
||||||
return Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Text('noSubscriptions'.tr()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: subscriptions.map((subscription) {
|
|
||||||
final isSelected = selectedPublisherIds.value
|
|
||||||
.contains(subscription.publisherId);
|
|
||||||
final publisher = subscription.publisher;
|
|
||||||
|
|
||||||
return CheckboxListTile(
|
|
||||||
title: Text(publisher.name),
|
|
||||||
subtitle:
|
|
||||||
publisher.nick.isNotEmpty &&
|
|
||||||
publisher.nick != publisher.name
|
|
||||||
? Text(publisher.nick)
|
|
||||||
: null,
|
|
||||||
value: isSelected,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == true) {
|
|
||||||
selectedPublisherIds.value = [
|
|
||||||
...selectedPublisherIds.value,
|
|
||||||
subscription.publisherId,
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
selectedPublisherIds.value =
|
|
||||||
selectedPublisherIds.value
|
|
||||||
.where(
|
|
||||||
(id) =>
|
|
||||||
id != subscription.publisherId,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
updateSelection();
|
|
||||||
},
|
|
||||||
dense: true,
|
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
|
||||||
secondary: const Icon(Symbols.person),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loading: () => const Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(16.0),
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
error: (error, stack) => Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Text('errorLoadingSubscriptions'.tr()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (subscriptionsAsync.hasValue &&
|
);
|
||||||
subscriptionsAsync.value!.isNotEmpty) ...[
|
}
|
||||||
const Gap(12),
|
|
||||||
Row(
|
return Column(
|
||||||
children: [
|
children: subscriptions.map((subscription) {
|
||||||
TextButton(
|
final isSelected = selectedPublisherNames.value.contains(
|
||||||
onPressed: () {
|
subscription.publisher.name,
|
||||||
selectedPublisherIds.value = subscriptionsAsync
|
);
|
||||||
.value!
|
final publisher = subscription.publisher;
|
||||||
.map((s) => s.publisherId)
|
|
||||||
.toList();
|
return CheckboxListTile(
|
||||||
updateSelection();
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
},
|
title: Text(publisher.nick),
|
||||||
child: Text('selectAll'.tr()),
|
subtitle: Text('@${publisher.name}'),
|
||||||
),
|
shape: const RoundedRectangleBorder(
|
||||||
const Gap(8),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
selectedPublisherIds.value = [];
|
|
||||||
updateSelection();
|
|
||||||
},
|
|
||||||
child: Text('selectNone'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
value: isSelected,
|
||||||
],
|
onChanged: (value) {
|
||||||
|
if (value == true) {
|
||||||
|
selectedPublisherNames.value = [
|
||||||
|
...selectedPublisherNames.value,
|
||||||
|
subscription.publisher.name,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
selectedPublisherNames.value = selectedPublisherNames
|
||||||
|
.value
|
||||||
|
.where(
|
||||||
|
(name) => name != subscription.publisher.name,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
updateSelection();
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
secondary: ProfilePictureWidget(
|
||||||
|
file: subscription.publisher.picture,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(16.0),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
error: (error, stack) => Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text('errorLoadingSubscriptions'.tr()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user