Stronger filter

This commit is contained in:
2025-11-02 23:30:16 +08:00
parent 5f6e4763d3
commit 5a23eb1768
4 changed files with 443 additions and 28 deletions

View File

@@ -1310,5 +1310,12 @@
"presenceTypeMusic": "Listening to Music", "presenceTypeMusic": "Listening to Music",
"presenceTypeWorkout": "Working out", "presenceTypeWorkout": "Working out",
"articleCompose": "Compose Article", "articleCompose": "Compose Article",
"backToHub": "Back to Hub" "backToHub": "Back to Hub",
"advancedFilters": "Advanced Filters",
"searchPosts": "Search Posts",
"sortBy": "Sort by",
"fromDate": "From Date",
"toDate": "To Date",
"popularity": "Popularity",
"descendingOrder": "Descending Order"
} }

View File

@@ -326,21 +326,233 @@ class _PublisherHeatmapWidget extends StatelessWidget {
class _PublisherCategoryTabWidget extends StatelessWidget { class _PublisherCategoryTabWidget extends StatelessWidget {
final TabController categoryTabController; final TabController categoryTabController;
final ValueNotifier<bool?> includeReplies;
final ValueNotifier<bool> mediaOnly;
final ValueNotifier<String?> queryTerm;
final ValueNotifier<String?> order;
final ValueNotifier<bool> orderDesc;
final ValueNotifier<int?> periodStart;
final ValueNotifier<int?> periodEnd;
final ValueNotifier<bool> showAdvancedFilters;
const _PublisherCategoryTabWidget({required this.categoryTabController}); const _PublisherCategoryTabWidget({
required this.categoryTabController,
required this.includeReplies,
required this.mediaOnly,
required this.queryTerm,
required this.order,
required this.orderDesc,
required this.periodStart,
required this.periodEnd,
required this.showAdvancedFilters,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TabBar( child: Column(
controller: categoryTabController, children: [
dividerColor: Colors.transparent, TabBar(
splashBorderRadius: const BorderRadius.all(Radius.circular(8)), controller: categoryTabController,
tabs: [ dividerColor: Colors.transparent,
Tab(text: 'all'.tr()), splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
Tab(text: 'postTypePost'.tr()), tabs: [
Tab(text: 'postArticle'.tr()), Tab(text: 'all'.tr()),
Tab(text: 'postTypePost'.tr()),
Tab(text: 'postArticle'.tr()),
],
),
const Divider(height: 1),
Column(
children: [
Row(
children: [
Expanded(
child: CheckboxListTile(
title: Text('reply'.tr()),
value: includeReplies.value,
tristate: true,
onChanged: (value) {
// Cycle through: null -> false -> true -> null
if (includeReplies.value == null) {
includeReplies.value = false;
} else if (includeReplies.value == false) {
includeReplies.value = true;
} else {
includeReplies.value = null;
}
},
dense: true,
controlAffinity: ListTileControlAffinity.leading,
secondary: const Icon(Symbols.reply),
),
),
Expanded(
child: CheckboxListTile(
title: Text('attachments'.tr()),
value: mediaOnly.value,
onChanged: (value) {
if (value != null) {
mediaOnly.value = value;
}
},
dense: true,
controlAffinity: ListTileControlAffinity.leading,
secondary: const Icon(Symbols.attachment),
),
),
],
),
CheckboxListTile(
title: Text('descendingOrder'.tr()),
value: orderDesc.value,
onChanged: (value) {
if (value != null) {
orderDesc.value = value;
}
},
dense: true,
controlAffinity: ListTileControlAffinity.leading,
secondary: const Icon(Symbols.sort),
),
],
),
const Divider(height: 1),
ListTile(
title: Text('advancedFilters'.tr()),
leading: const Icon(Symbols.filter_list),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(const Radius.circular(8)),
),
trailing: Icon(
showAdvancedFilters.value
? Symbols.expand_less
: Symbols.expand_more,
),
onTap: () {
showAdvancedFilters.value = !showAdvancedFilters.value;
},
),
if (showAdvancedFilters.value) ...[
const Divider(height: 1),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
decoration: InputDecoration(
labelText: 'search'.tr(),
hintText: 'searchPosts'.tr(),
prefixIcon: const Icon(Symbols.search),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
onChanged: (value) {
queryTerm.value = value.isEmpty ? null : value;
},
),
const Gap(12),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'sortBy'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
value: order.value,
items: [
DropdownMenuItem(value: 'date', child: Text('date'.tr())),
DropdownMenuItem(
value: 'popularity',
child: Text('popularity'.tr()),
),
],
onChanged: (value) {
order.value = value;
},
),
const Gap(12),
Row(
children: [
Expanded(
child: TextField(
decoration: InputDecoration(
labelText: 'fromDate'.tr(),
hintText: 'YYYY-MM-DD',
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
onChanged: (value) {
if (value.isEmpty) {
periodStart.value = null;
} else {
try {
final date = DateTime.parse(value);
periodStart.value =
date.millisecondsSinceEpoch ~/ 1000;
} catch (_) {
periodStart.value = null;
}
}
},
),
),
const Gap(8),
Expanded(
child: TextField(
decoration: InputDecoration(
labelText: 'toDate'.tr(),
hintText: 'YYYY-MM-DD',
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
onChanged: (value) {
if (value.isEmpty) {
periodEnd.value = null;
} else {
try {
final date = DateTime.parse(value);
periodEnd.value =
date.millisecondsSinceEpoch ~/ 1000;
} catch (_) {
periodEnd.value = null;
}
}
},
),
),
],
),
],
),
),
],
], ],
), ),
); );
@@ -423,6 +635,16 @@ class PublisherProfileScreen extends HookConsumerWidget {
categoryTab.value = categoryTabController.index; categoryTab.value = categoryTabController.index;
}); });
final includeReplies = useState<bool?>(null);
final mediaOnly = useState(false);
final queryTerm = useState<String?>(null);
final order = useState<String?>('date'); // 'popularity' or 'date'
final orderDesc = useState(
true,
); // true for descending, false for ascending
final periodStart = useState<int?>(null);
final periodEnd = useState<int?>(null);
final showAdvancedFilters = useState(false);
final subscribing = useState(false); final subscribing = useState(false);
Future<void> subscribe() async { Future<void> subscribe() async {
@@ -498,17 +720,33 @@ class PublisherProfileScreen extends HookConsumerWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: _PublisherCategoryTabWidget( child: _PublisherCategoryTabWidget(
categoryTabController: categoryTabController, categoryTabController: categoryTabController,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
orderDesc: orderDesc,
periodStart: periodStart,
periodEnd: periodEnd,
showAdvancedFilters: showAdvancedFilters,
), ),
), ),
SliverPostList( SliverPostList(
key: ValueKey(categoryTab.value), key: ValueKey(
'${categoryTab.value}-${includeReplies.value}-${mediaOnly.value}-${queryTerm.value}-${order.value}-${orderDesc.value}-${periodStart.value}-${periodEnd.value}',
),
pubName: name, pubName: name,
pinned: false, pinned: false,
type: switch (categoryTab.value) { type:
1 => 0, categoryTab.value == 1
2 => 1, ? 0
_ => null, : (categoryTab.value == 2 ? 1 : null),
}, includeReplies: includeReplies.value,
mediaOnly: mediaOnly.value,
queryTerm: queryTerm.value,
order: order.value,
orderDesc: orderDesc.value,
periodStart: periodStart.value,
periodEnd: periodEnd.value,
), ),
SliverGap( SliverGap(
MediaQuery.of(context).padding.bottom + 16, MediaQuery.of(context).padding.bottom + 16,
@@ -621,17 +859,33 @@ class PublisherProfileScreen extends HookConsumerWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: _PublisherCategoryTabWidget( child: _PublisherCategoryTabWidget(
categoryTabController: categoryTabController, categoryTabController: categoryTabController,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
orderDesc: orderDesc,
periodStart: periodStart,
periodEnd: periodEnd,
showAdvancedFilters: showAdvancedFilters,
), ),
), ),
SliverPostList( SliverPostList(
key: ValueKey(categoryTab.value), key: ValueKey(
'${categoryTab.value}-${includeReplies.value}-${mediaOnly.value}-${queryTerm.value}-${order.value}-${orderDesc.value}-${periodStart.value}-${periodEnd.value}',
),
pubName: name, pubName: name,
pinned: false, pinned: false,
type: switch (categoryTab.value) { type:
1 => 0, categoryTab.value == 1
2 => 1, ? 0
_ => null, : (categoryTab.value == 2 ? 1 : null),
}, includeReplies: includeReplies.value,
mediaOnly: mediaOnly.value,
queryTerm: queryTerm.value,
order: order.value,
orderDesc: orderDesc.value,
periodStart: periodStart.value,
periodEnd: periodEnd.value,
), ),
SliverGap(MediaQuery.of(context).padding.bottom + 16), SliverGap(MediaQuery.of(context).padding.bottom + 16),
], ],

View File

@@ -24,6 +24,12 @@ class PostListNotifier extends _$PostListNotifier
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies, bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) { }) {
return fetch(cursor: null); return fetch(cursor: null);
} }
@@ -36,14 +42,20 @@ class PostListNotifier extends _$PostListNotifier
final queryParams = { final queryParams = {
'offset': offset, 'offset': offset,
'take': _pageSize, 'take': _pageSize,
'replies': includeReplies,
'orderDesc': orderDesc,
if (shuffle) 'shuffle': shuffle,
if (pubName != null) 'pub': pubName, if (pubName != null) 'pub': pubName,
if (realm != null) 'realm': realm, if (realm != null) 'realm': realm,
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,
if (pinned != null) 'pinned': pinned, if (pinned != null) 'pinned': pinned,
if (includeReplies != null) 'includeReplies': includeReplies, if (order != null) 'order': order,
if (periodStart != null) 'periodStart': periodStart,
if (periodEnd != null) 'periodEnd': periodEnd,
if (queryTerm != null) 'query': queryTerm,
if (mediaOnly != null) 'media': mediaOnly,
}; };
final response = await client.get( final response = await client.get(
@@ -82,6 +94,14 @@ class SliverPostList extends HookConsumerWidget {
final List<String>? tags; final List<String>? tags;
final bool shuffle; final bool shuffle;
final bool? pinned; final bool? pinned;
final bool? includeReplies;
final bool? mediaOnly;
final String? queryTerm;
// Can be "populaurity", other value will be treated as "date"
final String? order;
final int? periodStart;
final int? periodEnd;
final bool? orderDesc;
final PostItemType itemType; final PostItemType itemType;
final Color? backgroundColor; final Color? backgroundColor;
final EdgeInsets? padding; final EdgeInsets? padding;
@@ -99,6 +119,13 @@ class SliverPostList extends HookConsumerWidget {
this.tags, this.tags,
this.shuffle = false, this.shuffle = false,
this.pinned, this.pinned,
this.includeReplies,
this.mediaOnly,
this.queryTerm,
this.order,
this.orderDesc = true,
this.periodStart,
this.periodEnd,
this.itemType = PostItemType.regular, this.itemType = PostItemType.regular,
this.backgroundColor, this.backgroundColor,
this.padding, this.padding,
@@ -118,6 +145,13 @@ class SliverPostList extends HookConsumerWidget {
tags: tags, tags: tags,
shuffle: shuffle, shuffle: shuffle,
pinned: pinned, pinned: pinned,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc ?? true,
); );
return PagingHelperSliverView( return PagingHelperSliverView(
provider: provider, provider: provider,

View File

@@ -6,7 +6,7 @@ part of 'post_list.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$postListNotifierHash() => r'fc139ad4df0deb67bcbb949560319f2f7fbfb503'; String _$postListNotifierHash() => r'8241120dc3c2004387c6cf881e5cb9224cbd3a97';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {
@@ -39,6 +39,12 @@ abstract class _$PostListNotifier
late final bool? pinned; late final bool? pinned;
late final bool shuffle; late final bool shuffle;
late final bool? includeReplies; late final bool? includeReplies;
late final bool? mediaOnly;
late final String? queryTerm;
late final String? order;
late final int? periodStart;
late final int? periodEnd;
late final bool orderDesc;
FutureOr<CursorPagingData<SnPost>> build({ FutureOr<CursorPagingData<SnPost>> build({
String? pubName, String? pubName,
@@ -49,6 +55,12 @@ abstract class _$PostListNotifier
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies, bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}); });
} }
@@ -72,6 +84,12 @@ class PostListNotifierFamily
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies, bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) { }) {
return PostListNotifierProvider( return PostListNotifierProvider(
pubName: pubName, pubName: pubName,
@@ -82,6 +100,12 @@ class PostListNotifierFamily
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies, includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
); );
} }
@@ -98,6 +122,12 @@ class PostListNotifierFamily
pinned: provider.pinned, pinned: provider.pinned,
shuffle: provider.shuffle, shuffle: provider.shuffle,
includeReplies: provider.includeReplies, includeReplies: provider.includeReplies,
mediaOnly: provider.mediaOnly,
queryTerm: provider.queryTerm,
order: provider.order,
periodStart: provider.periodStart,
periodEnd: provider.periodEnd,
orderDesc: provider.orderDesc,
); );
} }
@@ -133,6 +163,12 @@ class PostListNotifierProvider
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies, bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) : this._internal( }) : this._internal(
() => () =>
PostListNotifier() PostListNotifier()
@@ -143,7 +179,13 @@ class PostListNotifierProvider
..tags = tags ..tags = tags
..pinned = pinned ..pinned = pinned
..shuffle = shuffle ..shuffle = shuffle
..includeReplies = includeReplies, ..includeReplies = includeReplies
..mediaOnly = mediaOnly
..queryTerm = queryTerm
..order = order
..periodStart = periodStart
..periodEnd = periodEnd
..orderDesc = orderDesc,
from: postListNotifierProvider, from: postListNotifierProvider,
name: r'postListNotifierProvider', name: r'postListNotifierProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
@@ -161,6 +203,12 @@ class PostListNotifierProvider
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies, includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
); );
PostListNotifierProvider._internal( PostListNotifierProvider._internal(
@@ -178,6 +226,12 @@ class PostListNotifierProvider
required this.pinned, required this.pinned,
required this.shuffle, required this.shuffle,
required this.includeReplies, required this.includeReplies,
required this.mediaOnly,
required this.queryTerm,
required this.order,
required this.periodStart,
required this.periodEnd,
required this.orderDesc,
}) : super.internal(); }) : super.internal();
final String? pubName; final String? pubName;
@@ -188,6 +242,12 @@ class PostListNotifierProvider
final bool? pinned; final bool? pinned;
final bool shuffle; final bool shuffle;
final bool? includeReplies; final bool? includeReplies;
final bool? mediaOnly;
final String? queryTerm;
final String? order;
final int? periodStart;
final int? periodEnd;
final bool orderDesc;
@override @override
FutureOr<CursorPagingData<SnPost>> runNotifierBuild( FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
@@ -202,6 +262,12 @@ class PostListNotifierProvider
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies, includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
); );
} }
@@ -219,7 +285,13 @@ class PostListNotifierProvider
..tags = tags ..tags = tags
..pinned = pinned ..pinned = pinned
..shuffle = shuffle ..shuffle = shuffle
..includeReplies = includeReplies, ..includeReplies = includeReplies
..mediaOnly = mediaOnly
..queryTerm = queryTerm
..order = order
..periodStart = periodStart
..periodEnd = periodEnd
..orderDesc = orderDesc,
from: from, from: from,
name: null, name: null,
dependencies: null, dependencies: null,
@@ -233,6 +305,12 @@ class PostListNotifierProvider
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies, includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
), ),
); );
} }
@@ -256,7 +334,13 @@ class PostListNotifierProvider
other.tags == tags && other.tags == tags &&
other.pinned == pinned && other.pinned == pinned &&
other.shuffle == shuffle && other.shuffle == shuffle &&
other.includeReplies == includeReplies; other.includeReplies == includeReplies &&
other.mediaOnly == mediaOnly &&
other.queryTerm == queryTerm &&
other.order == order &&
other.periodStart == periodStart &&
other.periodEnd == periodEnd &&
other.orderDesc == orderDesc;
} }
@override @override
@@ -270,6 +354,12 @@ class PostListNotifierProvider
hash = _SystemHash.combine(hash, pinned.hashCode); hash = _SystemHash.combine(hash, pinned.hashCode);
hash = _SystemHash.combine(hash, shuffle.hashCode); hash = _SystemHash.combine(hash, shuffle.hashCode);
hash = _SystemHash.combine(hash, includeReplies.hashCode); hash = _SystemHash.combine(hash, includeReplies.hashCode);
hash = _SystemHash.combine(hash, mediaOnly.hashCode);
hash = _SystemHash.combine(hash, queryTerm.hashCode);
hash = _SystemHash.combine(hash, order.hashCode);
hash = _SystemHash.combine(hash, periodStart.hashCode);
hash = _SystemHash.combine(hash, periodEnd.hashCode);
hash = _SystemHash.combine(hash, orderDesc.hashCode);
return _SystemHash.finish(hash); return _SystemHash.finish(hash);
} }
@@ -302,6 +392,24 @@ mixin PostListNotifierRef
/// The parameter `includeReplies` of this provider. /// The parameter `includeReplies` of this provider.
bool? get includeReplies; bool? get includeReplies;
/// The parameter `mediaOnly` of this provider.
bool? get mediaOnly;
/// The parameter `queryTerm` of this provider.
String? get queryTerm;
/// The parameter `order` of this provider.
String? get order;
/// The parameter `periodStart` of this provider.
int? get periodStart;
/// The parameter `periodEnd` of this provider.
int? get periodEnd;
/// The parameter `orderDesc` of this provider.
bool get orderDesc;
} }
class _PostListNotifierProviderElement class _PostListNotifierProviderElement
@@ -331,6 +439,18 @@ class _PostListNotifierProviderElement
@override @override
bool? get includeReplies => bool? get includeReplies =>
(origin as PostListNotifierProvider).includeReplies; (origin as PostListNotifierProvider).includeReplies;
@override
bool? get mediaOnly => (origin as PostListNotifierProvider).mediaOnly;
@override
String? get queryTerm => (origin as PostListNotifierProvider).queryTerm;
@override
String? get order => (origin as PostListNotifierProvider).order;
@override
int? get periodStart => (origin as PostListNotifierProvider).periodStart;
@override
int? get periodEnd => (origin as PostListNotifierProvider).periodEnd;
@override
bool get orderDesc => (origin as PostListNotifierProvider).orderDesc;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint