✨ Explore page filtered by post
This commit is contained in:
parent
2375c46852
commit
85d2eff7f8
@ -83,12 +83,16 @@ class SnPostContentProvider {
|
|||||||
int offset = 0,
|
int offset = 0,
|
||||||
String? type,
|
String? type,
|
||||||
String? author,
|
String? author,
|
||||||
|
Iterable<String>? categories,
|
||||||
|
Iterable<String>? tags,
|
||||||
}) async {
|
}) async {
|
||||||
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
|
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
|
||||||
'take': take,
|
'take': take,
|
||||||
'offset': offset,
|
'offset': offset,
|
||||||
if (type != null) 'type': type,
|
if (type != null) 'type': type,
|
||||||
if (author != null) 'author': author,
|
if (author != null) 'author': author,
|
||||||
|
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
|
||||||
|
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
|
||||||
});
|
});
|
||||||
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
||||||
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
||||||
|
@ -5,12 +5,27 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
const Map<String, IconData> kCategoryIcons = {
|
||||||
|
'technology': Symbols.tools_wrench,
|
||||||
|
'gaming': Symbols.gamepad,
|
||||||
|
'life': Symbols.nightlife,
|
||||||
|
'arts': Symbols.format_paint,
|
||||||
|
'sports': Symbols.sports_soccer,
|
||||||
|
'music': Symbols.music_note,
|
||||||
|
'news': Symbols.newspaper,
|
||||||
|
'knowledge': Symbols.book,
|
||||||
|
'literature': Symbols.book,
|
||||||
|
};
|
||||||
|
|
||||||
class ExploreScreen extends StatefulWidget {
|
class ExploreScreen extends StatefulWidget {
|
||||||
const ExploreScreen({super.key});
|
const ExploreScreen({super.key});
|
||||||
|
|
||||||
@ -24,15 +39,34 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
bool _isBusy = true;
|
bool _isBusy = true;
|
||||||
|
|
||||||
final List<SnPost> _posts = List.empty(growable: true);
|
final List<SnPost> _posts = List.empty(growable: true);
|
||||||
|
final List<SnPostCategory> _categories = List.empty(growable: true);
|
||||||
int? _postCount;
|
int? _postCount;
|
||||||
|
|
||||||
|
String? _selectedCategory;
|
||||||
|
|
||||||
|
Future<void> _fetchCategories() async {
|
||||||
|
_categories.clear();
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/categories?take=100');
|
||||||
|
_categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchPosts() async {
|
Future<void> _fetchPosts() async {
|
||||||
if (_postCount != null && _posts.length >= _postCount!) return;
|
if (_postCount != null && _posts.length >= _postCount!) return;
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
final result = await pt.listPosts(take: 10, offset: _posts.length);
|
final result = await pt.listPosts(
|
||||||
|
take: 10,
|
||||||
|
offset: _posts.length,
|
||||||
|
categories: _selectedCategory != null ? [_selectedCategory!] : null,
|
||||||
|
);
|
||||||
final out = result.$1;
|
final out = result.$1;
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -43,10 +77,17 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
if (mounted) setState(() => _isBusy = false);
|
if (mounted) setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshPosts() {
|
||||||
|
_postCount = null;
|
||||||
|
_posts.clear();
|
||||||
|
return _fetchPosts();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
|
_fetchCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -59,27 +100,20 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
type: ExpandableFabType.up,
|
type: ExpandableFabType.up,
|
||||||
childrenAnimation: ExpandableFabAnimation.none,
|
childrenAnimation: ExpandableFabAnimation.none,
|
||||||
overlayStyle: ExpandableFabOverlayStyle(
|
overlayStyle: ExpandableFabOverlayStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()),
|
||||||
.colorScheme
|
|
||||||
.surface
|
|
||||||
.withAlpha((255 * 0.5).round()),
|
|
||||||
),
|
),
|
||||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||||
child: const Icon(Symbols.add, size: 28),
|
child: const Icon(Symbols.add, size: 28),
|
||||||
fabSize: ExpandableFabSize.regular,
|
fabSize: ExpandableFabSize.regular,
|
||||||
foregroundColor:
|
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
),
|
),
|
||||||
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
||||||
child: const Icon(Symbols.close, size: 28),
|
child: const Icon(Symbols.close, size: 28),
|
||||||
fabSize: ExpandableFabSize.regular,
|
fabSize: ExpandableFabSize.regular,
|
||||||
foregroundColor:
|
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
@ -95,8 +129,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'stories',
|
'mode': 'stories',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_posts.clear();
|
_refreshPosts();
|
||||||
_fetchPosts();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@ -117,8 +150,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'articles',
|
'mode': 'articles',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_posts.clear();
|
_refreshPosts();
|
||||||
_fetchPosts();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@ -131,10 +163,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
displacement: 40 + MediaQuery.of(context).padding.top,
|
displacement: 40 + MediaQuery.of(context).padding.top,
|
||||||
onRefresh: () {
|
onRefresh: () => _refreshPosts(),
|
||||||
_posts.clear();
|
|
||||||
return _fetchPosts();
|
|
||||||
},
|
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
@ -151,6 +180,33 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(40),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: _categories.length,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final ele = _categories[idx];
|
||||||
|
return StyledWidget(ChoiceChip(
|
||||||
|
avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark),
|
||||||
|
label: Text(
|
||||||
|
'postCategory${ele.alias.capitalize()}'.trExists()
|
||||||
|
? 'postCategory${ele.alias.capitalize()}'.tr()
|
||||||
|
: ele.name,
|
||||||
|
),
|
||||||
|
selected: _selectedCategory == ele.alias,
|
||||||
|
onSelected: (value) {
|
||||||
|
_selectedCategory = value ? ele.alias : null;
|
||||||
|
_refreshPosts();
|
||||||
|
},
|
||||||
|
)).padding(horizontal: 4);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SliverInfiniteList(
|
SliverInfiniteList(
|
||||||
itemCount: _posts.length,
|
itemCount: _posts.length,
|
||||||
@ -167,8 +223,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
setState(() => _posts[idx] = data);
|
setState(() => _posts[idx] = data);
|
||||||
},
|
},
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
_posts.clear();
|
_refreshPosts();
|
||||||
_fetchPosts();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -245,7 +245,7 @@ class PostItem extends StatelessWidget {
|
|||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, bottom: 6),
|
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, top: 4, bottom: 6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user