Realm posts

This commit is contained in:
LittleSheep 2025-02-22 13:13:38 +08:00
parent 671b857a79
commit edd86eda77
3 changed files with 157 additions and 37 deletions

View File

@ -126,6 +126,7 @@ class SnPostContentProvider {
String? author, String? author,
Iterable<String>? categories, Iterable<String>? categories,
Iterable<String>? tags, Iterable<String>? tags,
String? realm,
}) 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,
@ -134,6 +135,7 @@ class SnPostContentProvider {
if (author != null) 'author': author, if (author != null) 'author': author,
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','), if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','), if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
if (realm != null) 'realm': realm,
}); });
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)) ?? []),

View File

@ -368,14 +368,17 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
'articles' => _PostArticleEditor( 'articles' => _PostArticleEditor(
controller: _writeController, controller: _writeController,
onTapPublisher: _showPublisherPopup, onTapPublisher: _showPublisherPopup,
onTapRealm: _showRealmPopup,
), ),
'questions' => _PostQuestionEditor( 'questions' => _PostQuestionEditor(
controller: _writeController, controller: _writeController,
onTapPublisher: _showPublisherPopup, onTapPublisher: _showPublisherPopup,
onTapRealm: _showRealmPopup,
), ),
'videos' => _PostVideoEditor( 'videos' => _PostVideoEditor(
controller: _writeController, controller: _writeController,
onTapPublisher: _showPublisherPopup, onTapPublisher: _showPublisherPopup,
onTapRealm: _showRealmPopup,
), ),
_ => const Placeholder(), _ => const Placeholder(),
}) })
@ -494,7 +497,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
), ),
if (_writeController.mode == 'articles') if (_writeController.mode == 'articles')
IconButton( IconButton(
icon: Icon(Symbols.image, color: Theme.of(context).colorScheme.primary), icon: Icon(Symbols.full_coverage, color: Theme.of(context).colorScheme.primary),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: _writeController.thumbnail == null backgroundColor: _writeController.thumbnail == null
? null ? null
@ -686,7 +689,7 @@ class _PostStoryEditor extends StatelessWidget {
), ),
), ),
), ),
const Gap(10), const Gap(11),
Material( Material(
elevation: 1, elevation: 1,
borderRadius: const BorderRadius.all(Radius.circular(24)), borderRadius: const BorderRadius.all(Radius.circular(24)),
@ -744,8 +747,9 @@ class _PostStoryEditor extends StatelessWidget {
class _PostArticleEditor extends StatelessWidget { class _PostArticleEditor extends StatelessWidget {
final PostWriteController controller; final PostWriteController controller;
final Function? onTapPublisher; final Function? onTapPublisher;
final Function? onTapRealm;
const _PostArticleEditor({required this.controller, this.onTapPublisher}); const _PostArticleEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -766,6 +770,21 @@ class _PostArticleEditor extends StatelessWidget {
], ],
), ),
), ),
Material(
elevation: 1,
borderRadius: const BorderRadius.all(Radius.circular(24)),
child: GestureDetector(
onTap: () {
onTapRealm?.call();
},
child: AccountImage(
content: controller.realm?.avatar,
fallbackWidget: const Icon(Symbols.globe, size: 20),
radius: 14,
),
),
),
const Gap(8),
], ],
).padding(horizontal: 12, vertical: 8), ).padding(horizontal: 12, vertical: 8),
onTap: () { onTap: () {
@ -885,8 +904,9 @@ class _PostArticleEditor extends StatelessWidget {
class _PostQuestionEditor extends StatelessWidget { class _PostQuestionEditor extends StatelessWidget {
final PostWriteController controller; final PostWriteController controller;
final Function? onTapPublisher; final Function? onTapPublisher;
final Function? onTapRealm;
const _PostQuestionEditor({required this.controller, this.onTapPublisher}); const _PostQuestionEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -896,17 +916,36 @@ class _PostQuestionEditor extends StatelessWidget {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Material( Column(
elevation: 1, children: [
borderRadius: const BorderRadius.all(Radius.circular(24)), Material(
child: GestureDetector( elevation: 2,
onTap: () { borderRadius: const BorderRadius.all(Radius.circular(24)),
onTapPublisher?.call(); child: GestureDetector(
}, onTap: () {
child: AccountImage( onTapPublisher?.call();
content: controller.publisher?.avatar, },
child: AccountImage(
content: controller.publisher?.avatar,
),
),
), ),
), const Gap(11),
Material(
elevation: 1,
borderRadius: const BorderRadius.all(Radius.circular(24)),
child: GestureDetector(
onTap: () {
onTapRealm?.call();
},
child: AccountImage(
content: controller.realm?.avatar,
fallbackWidget: const Icon(Symbols.globe, size: 20),
radius: 14,
),
),
),
],
), ),
Expanded( Expanded(
child: Column( child: Column(
@ -960,8 +999,9 @@ class _PostQuestionEditor extends StatelessWidget {
class _PostVideoEditor extends StatelessWidget { class _PostVideoEditor extends StatelessWidget {
final PostWriteController controller; final PostWriteController controller;
final Function? onTapPublisher; final Function? onTapPublisher;
final Function? onTapRealm;
const _PostVideoEditor({required this.controller, this.onTapPublisher}); const _PostVideoEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
void _selectVideo(BuildContext context) async { void _selectVideo(BuildContext context) async {
final video = await showDialog<SnAttachment?>( final video = await showDialog<SnAttachment?>(
@ -1049,28 +1089,36 @@ class _PostVideoEditor extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
Material( Column(
color: Theme.of(context).colorScheme.surfaceContainerHigh, children: [
child: InkWell( Material(
child: Row( elevation: 2,
children: [ borderRadius: const BorderRadius.all(Radius.circular(24)),
AccountImage(content: controller.publisher?.avatar, radius: 20), child: GestureDetector(
const Gap(8), onTap: () {
Expanded( onTapPublisher?.call();
child: Column( },
crossAxisAlignment: CrossAxisAlignment.start, child: AccountImage(
children: [ content: controller.publisher?.avatar,
Text(controller.publisher?.nick ?? 'loading'.tr()).bold(),
Text('@${controller.publisher?.name}'),
],
),
), ),
], ),
).padding(horizontal: 12, vertical: 8), ),
onTap: () { const Gap(11),
onTapPublisher?.call(); Material(
}, elevation: 1,
), borderRadius: const BorderRadius.all(Radius.circular(24)),
child: GestureDetector(
onTap: () {
onTapRealm?.call();
},
child: AccountImage(
content: controller.realm?.avatar,
fallbackWidget: const Icon(Symbols.globe, size: 20),
radius: 14,
),
),
),
],
), ),
const Gap(16), const Gap(16),
TextField( TextField(

View File

@ -5,6 +5,7 @@ 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:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
@ -16,6 +17,7 @@ import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_select.dart'; import 'package:surface/widgets/account/account_select.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.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';
class RealmDetailScreen extends StatefulWidget { class RealmDetailScreen extends StatefulWidget {
@ -90,7 +92,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultTabController( return DefaultTabController(
length: 3, length: 4,
child: AppScaffold( child: AppScaffold(
body: NestedScrollView( body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
@ -102,6 +104,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
bottom: TabBar( bottom: TabBar(
tabs: [ tabs: [
Tab(icon: Icon(Symbols.home, color: Theme.of(context).appBarTheme.foregroundColor)), Tab(icon: Icon(Symbols.home, color: Theme.of(context).appBarTheme.foregroundColor)),
Tab(icon: Icon(Symbols.explore, color: Theme.of(context).appBarTheme.foregroundColor)),
Tab(icon: Icon(Symbols.group, color: Theme.of(context).appBarTheme.foregroundColor)), Tab(icon: Icon(Symbols.group, color: Theme.of(context).appBarTheme.foregroundColor)),
Tab(icon: Icon(Symbols.settings, color: Theme.of(context).appBarTheme.foregroundColor)), Tab(icon: Icon(Symbols.settings, color: Theme.of(context).appBarTheme.foregroundColor)),
], ],
@ -113,6 +116,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
body: TabBarView( body: TabBarView(
children: [ children: [
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers, channels: _channels), _RealmDetailHomeWidget(realm: _realm, publishers: _publishers, channels: _channels),
_RealmPostListWidget(realm: _realm),
_RealmMemberListWidget(realm: _realm), _RealmMemberListWidget(realm: _realm),
_RealmSettingsWidget( _RealmSettingsWidget(
realm: _realm, realm: _realm,
@ -232,6 +236,72 @@ class _RealmDetailHomeWidget extends StatelessWidget {
} }
} }
class _RealmPostListWidget extends StatefulWidget {
final SnRealm? realm;
const _RealmPostListWidget({this.realm});
@override
State<_RealmPostListWidget> createState() => _RealmPostListWidgetState();
}
class _RealmPostListWidgetState extends State<_RealmPostListWidget> {
bool _isBusy = false;
int? _totalCount;
final List<SnPost> _posts = List.empty(growable: true);
Future<void> _fetchPosts() async {
setState(() => _isBusy = true);
try {
final pt = context.read<SnPostContentProvider>();
final out = await pt.listPosts(
take: 10,
offset: _posts.length,
realm: widget.realm?.id.toString(),
);
_totalCount = out.$2;
_posts.addAll(out.$1);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
Widget build(BuildContext context) {
return MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: _fetchPosts,
child: InfiniteList(
itemCount: _posts.length,
isLoading: _isBusy,
hasReachedMax: _totalCount != null && _posts.length >= _totalCount!,
onFetchData: _fetchPosts,
itemBuilder: (context, idx) {
final post = _posts[idx];
return OpenablePostItem(
data: post,
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
onDeleted: () {
setState(() => _posts.removeAt(idx));
},
);
},
separatorBuilder: (_, __) => const Gap(8),
),
),
).padding(top: 8);
}
}
class _RealmMemberListWidget extends StatefulWidget { class _RealmMemberListWidget extends StatefulWidget {
final SnRealm? realm; final SnRealm? realm;