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,
Iterable<String>? categories,
Iterable<String>? tags,
String? realm,
}) async {
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
'take': take,
@ -134,6 +135,7 @@ class SnPostContentProvider {
if (author != null) 'author': author,
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
if (realm != null) 'realm': realm,
});
final List<SnPost> out = await _preloadRelatedDataInBatch(
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),

View File

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

View File

@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.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/user_directory.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/dialog.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';
class RealmDetailScreen extends StatefulWidget {
@ -90,7 +92,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
length: 4,
child: AppScaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
@ -102,6 +104,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
bottom: TabBar(
tabs: [
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.settings, color: Theme.of(context).appBarTheme.foregroundColor)),
],
@ -113,6 +116,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
body: TabBarView(
children: [
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers, channels: _channels),
_RealmPostListWidget(realm: _realm),
_RealmMemberListWidget(realm: _realm),
_RealmSettingsWidget(
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 {
final SnRealm? realm;