✨ Explore subscription filter card
This commit is contained in:
@@ -22,6 +22,7 @@ class SliverPostList extends HookConsumerWidget {
|
||||
final PostItemType itemType;
|
||||
final Color? backgroundColor;
|
||||
final EdgeInsets? padding;
|
||||
final EdgeInsets? itemPadding;
|
||||
final bool isOpenable;
|
||||
final Function? onRefresh;
|
||||
final Function(SnPost)? onUpdate;
|
||||
@@ -34,6 +35,7 @@ class SliverPostList extends HookConsumerWidget {
|
||||
this.itemType = PostItemType.regular,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
this.itemPadding,
|
||||
this.isOpenable = true,
|
||||
this.onRefresh,
|
||||
this.onUpdate,
|
||||
@@ -74,17 +76,17 @@ class SliverPostList extends HookConsumerWidget {
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
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) {
|
||||
case PostItemType.creator:
|
||||
return PostItemCreator(
|
||||
@@ -97,7 +99,8 @@ class SliverPostList extends HookConsumerWidget {
|
||||
);
|
||||
case PostItemType.regular:
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
margin:
|
||||
itemPadding ?? EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: PostActionableItem(item: post, borderRadius: 8),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,153 +3,129 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.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: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 {
|
||||
final List<String> initialSelectedPublisherIds;
|
||||
final List<String> initialSelectedPublisherNames;
|
||||
final ValueChanged<List<String>> onSelectedPublishersChanged;
|
||||
final bool hideSearch;
|
||||
|
||||
const PostSubscriptionFilterWidget({
|
||||
super.key,
|
||||
required this.initialSelectedPublisherIds,
|
||||
required this.initialSelectedPublisherNames,
|
||||
required this.onSelectedPublishersChanged,
|
||||
this.hideSearch = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedPublisherIds = useState<List<String>>(
|
||||
initialSelectedPublisherIds,
|
||||
final selectedPublisherNames = useState<List<String>>(
|
||||
initialSelectedPublisherNames,
|
||||
);
|
||||
final showSubscriptions = useState<bool>(false);
|
||||
|
||||
final subscriptionsAsync = ref.watch(subscriptionsProvider);
|
||||
|
||||
void updateSelection() {
|
||||
onSelectedPublishersChanged(selectedPublisherIds.value);
|
||||
onSelectedPublishersChanged(selectedPublisherNames.value);
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text('filterBySubscriptions'.tr()),
|
||||
leading: const Icon(Symbols.subscriptions),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(const Radius.circular(8)),
|
||||
),
|
||||
trailing: Icon(
|
||||
showSubscriptions.value
|
||||
? Symbols.expand_less
|
||||
: Symbols.expand_more,
|
||||
),
|
||||
onTap: () {
|
||||
showSubscriptions.value = !showSubscriptions.value;
|
||||
},
|
||||
),
|
||||
if (showSubscriptions.value) ...[
|
||||
const Divider(height: 1),
|
||||
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()),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
spacing: 16,
|
||||
children: [
|
||||
const Icon(Symbols.subscriptions, size: 20),
|
||||
Text(
|
||||
'exploreFilterSubscriptions'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, top: 12),
|
||||
const Gap(12),
|
||||
subscriptionsAsync.when(
|
||||
data: (subscriptions) {
|
||||
if (subscriptions.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text('noSubscriptions'.tr()),
|
||||
),
|
||||
if (subscriptionsAsync.hasValue &&
|
||||
subscriptionsAsync.value!.isNotEmpty) ...[
|
||||
const Gap(12),
|
||||
Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
selectedPublisherIds.value = subscriptionsAsync
|
||||
.value!
|
||||
.map((s) => s.publisherId)
|
||||
.toList();
|
||||
updateSelection();
|
||||
},
|
||||
child: Text('selectAll'.tr()),
|
||||
),
|
||||
const Gap(8),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
selectedPublisherIds.value = [];
|
||||
updateSelection();
|
||||
},
|
||||
child: Text('selectNone'.tr()),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: subscriptions.map((subscription) {
|
||||
final isSelected = selectedPublisherNames.value.contains(
|
||||
subscription.publisher.name,
|
||||
);
|
||||
final publisher = subscription.publisher;
|
||||
|
||||
return CheckboxListTile(
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
title: Text(publisher.nick),
|
||||
subtitle: Text('@${publisher.name}'),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
],
|
||||
],
|
||||
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