✨ Realm posts
This commit is contained in:
parent
671b857a79
commit
edd86eda77
@ -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)) ?? []),
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user