diff --git a/lib/main.dart b/lib/main.dart index 8834759..d2f7617 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,7 @@ import 'package:solian/providers/auth.dart'; import 'package:solian/providers/content/attachment.dart'; import 'package:solian/providers/content/call.dart'; import 'package:solian/providers/content/channel.dart'; -import 'package:solian/providers/content/feed.dart'; +import 'package:solian/providers/content/posts.dart'; import 'package:solian/providers/content/realm.dart'; import 'package:solian/providers/friend.dart'; import 'package:solian/providers/account_status.dart'; @@ -97,7 +97,7 @@ class SolianApp extends StatelessWidget { void _initializeProviders(BuildContext context) async { Get.lazyPut(() => AuthProvider()); Get.lazyPut(() => FriendProvider()); - Get.lazyPut(() => FeedProvider()); + Get.lazyPut(() => PostProvider()); Get.lazyPut(() => AttachmentProvider()); Get.lazyPut(() => WebSocketProvider()); Get.lazyPut(() => StatusProvider()); diff --git a/lib/models/articles.dart b/lib/models/articles.dart deleted file mode 100644 index 41e615a..0000000 --- a/lib/models/articles.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:solian/models/account.dart'; -import 'package:solian/models/feed.dart'; -import 'package:solian/models/post.dart'; -import 'package:solian/models/realm.dart'; - -class Article { - int id; - DateTime createdAt; - DateTime updatedAt; - DateTime? deletedAt; - String alias; - String title; - String description; - String content; - List? tags; - List? categories; - List? attachments; - int? realmId; - Realm? realm; - DateTime? publishedAt; - bool? isDraft; - int authorId; - Account author; - PostMetric? metric; - - Article({ - required this.id, - required this.createdAt, - required this.updatedAt, - required this.deletedAt, - required this.alias, - required this.title, - required this.description, - required this.content, - required this.tags, - required this.categories, - required this.attachments, - required this.realmId, - required this.realm, - required this.publishedAt, - required this.isDraft, - required this.authorId, - required this.author, - required this.metric, - }); - - factory Article.fromJson(Map json) => Article( - id: json['id'], - createdAt: DateTime.parse(json['created_at']), - updatedAt: DateTime.parse(json['updated_at']), - deletedAt: json['deleted_at'] != null - ? DateTime.parse(json['deleted_at']) - : null, - alias: json['alias'], - title: json['title'], - description: json['description'], - content: json['content'], - tags: json['tags']?.map((x) => Tag.fromJson(x)).toList().cast(), - categories: json['categories'] - ?.map((x) => Category.fromJson(x)) - .toList() - .cast(), - attachments: json['attachments'] != null - ? List.from(json['attachments']) - : null, - realmId: json['realm_id'], - realm: json['realm'] != null ? Realm.fromJson(json['realm']) : null, - publishedAt: json['published_at'] != null - ? DateTime.parse(json['published_at']) - : null, - isDraft: json['is_draft'], - authorId: json['author_id'], - author: Account.fromJson(json['author']), - metric: - json['metric'] != null ? PostMetric.fromJson(json['metric']) : null, - ); - - Map toJson() => { - 'id': id, - 'created_at': createdAt.toIso8601String(), - 'updated_at': updatedAt.toIso8601String(), - 'deleted_at': deletedAt, - 'alias': alias, - 'title': title, - 'description': description, - 'content': content, - 'tags': tags, - 'categories': categories, - 'attachments': attachments, - 'realm_id': realmId, - 'realm': realm?.toJson(), - 'published_at': publishedAt?.toIso8601String(), - 'is_draft': isDraft, - 'author_id': authorId, - 'author': author.toJson(), - 'metric': metric?.toJson(), - }; -} diff --git a/lib/models/feed.dart b/lib/models/feed.dart index 2300340..5acc8a1 100644 --- a/lib/models/feed.dart +++ b/lib/models/feed.dart @@ -1,27 +1,3 @@ -class FeedRecord { - String type; - Map data; - DateTime createdAt; - - FeedRecord({ - required this.type, - required this.data, - required this.createdAt, - }); - - factory FeedRecord.fromJson(Map json) => FeedRecord( - type: json['type'], - data: json['data'], - createdAt: DateTime.parse(json['created_at']), - ); - - Map toJson() => { - 'type': type, - 'data': data, - 'created_at': createdAt.toIso8601String(), - }; -} - class Tag { int id; String alias; diff --git a/lib/models/post.dart b/lib/models/post.dart index 77f681c..77dcca2 100755 --- a/lib/models/post.dart +++ b/lib/models/post.dart @@ -7,12 +7,10 @@ class Post { DateTime createdAt; DateTime updatedAt; DateTime? deletedAt; - String alias; - String content; + dynamic body; List? tags; List? categories; List? replies; - List? attachments; int? replyId; int? repostId; int? realmId; @@ -30,12 +28,10 @@ class Post { required this.createdAt, required this.updatedAt, required this.deletedAt, - required this.alias, - required this.content, + required this.body, required this.tags, required this.categories, required this.replies, - required this.attachments, required this.replyId, required this.repostId, required this.realmId, @@ -56,17 +52,13 @@ class Post { deletedAt: json['deleted_at'] != null ? DateTime.parse(json['deleted_at']) : null, - alias: json['alias'], - content: json['content'], + body: json['body'], tags: json['tags']?.map((x) => Tag.fromJson(x)).toList().cast(), categories: json['categories'] ?.map((x) => Category.fromJson(x)) .toList() .cast(), replies: json['replies'], - attachments: json['attachments'] != null - ? List.from(json['attachments']) - : null, replyId: json['reply_id'], repostId: json['repost_id'], realmId: json['realm_id'], @@ -90,12 +82,10 @@ class Post { 'created_at': createdAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(), 'deleted_at': deletedAt, - 'alias': alias, - 'content': content, + 'body': body, 'tags': tags, 'categories': categories, 'replies': replies, - 'attachments': attachments, 'reply_id': replyId, 'repost_id': repostId, 'realm_id': realmId, diff --git a/lib/providers/content/feed.dart b/lib/providers/content/posts.dart similarity index 92% rename from lib/providers/content/feed.dart rename to lib/providers/content/posts.dart index 8ac6c79..21a8566 100644 --- a/lib/providers/content/feed.dart +++ b/lib/providers/content/posts.dart @@ -2,13 +2,13 @@ import 'package:get/get.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/services.dart'; -class FeedProvider extends GetConnect { +class PostProvider extends GetConnect { @override void onInit() { httpClient.baseUrl = ServiceFinder.buildUrl('interactive', null); } - Future listFeed(int page, + Future listRecommendations(int page, {int? realm, String? tag, category}) async { final queries = [ 'take=${10}', @@ -17,7 +17,7 @@ class FeedProvider extends GetConnect { if (category != null) 'category=$category', if (realm != null) 'realmId=$realm', ]; - final resp = await get('/feed?${queries.join('&')}'); + final resp = await get('/recommendations?${queries.join('&')}'); if (resp.statusCode != 200) { throw Exception(resp.body); } diff --git a/lib/router.dart b/lib/router.dart index 5cb908d..6794c54 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -4,8 +4,6 @@ import 'package:solian/screens/about.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/personalize.dart'; -import 'package:solian/screens/articles/article_detail.dart'; -import 'package:solian/screens/articles/article_editor.dart'; import 'package:solian/screens/channel/channel_chat.dart'; import 'package:solian/screens/channel/channel_detail.dart'; import 'package:solian/screens/channel/channel_organize.dart'; @@ -65,22 +63,12 @@ abstract class AppRouter { builder: (context, state) => const DraftBoxScreen(), ), GoRoute( - path: '/posts/view/:alias', + path: '/posts/view/:id', name: 'postDetail', builder: (context, state) => TitleShell( state: state, child: PostDetailScreen( - alias: state.pathParameters['alias']!, - ), - ), - ), - GoRoute( - path: '/articles/view/:alias', - name: 'articleDetail', - builder: (context, state) => TitleShell( - state: state, - child: ArticleDetailScreen( - alias: state.pathParameters['alias']!, + id: state.pathParameters['id']!, ), ), ), @@ -97,17 +85,6 @@ abstract class AppRouter { ); }, ), - GoRoute( - path: '/articles/editor', - name: 'articleEditor', - builder: (context, state) { - final arguments = state.extra as ArticlePublishArguments?; - return ArticlePublishScreen( - edit: arguments?.edit, - realm: arguments?.realm, - ); - }, - ) ], ); diff --git a/lib/screens/articles/article_detail.dart b/lib/screens/articles/article_detail.dart deleted file mode 100644 index 051acb3..0000000 --- a/lib/screens/articles/article_detail.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:solian/exts.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/providers/content/feed.dart'; -import 'package:solian/widgets/articles/article_item.dart'; -import 'package:solian/widgets/sized_container.dart'; - -class ArticleDetailScreen extends StatefulWidget { - final String alias; - - const ArticleDetailScreen({super.key, required this.alias}); - - @override - State createState() => _ArticleDetailScreenState(); -} - -class _ArticleDetailScreenState extends State { - Article? item; - - Future getDetail() async { - final FeedProvider provider = Get.find(); - - try { - final resp = await provider.getArticle(widget.alias); - item = Article.fromJson(resp.body); - } catch (e) { - context.showErrorDialog(e).then((_) => Navigator.pop(context)); - } - - return item; - } - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).colorScheme.surface, - child: FutureBuilder( - future: getDetail(), - builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data == null) { - return const Center( - child: CircularProgressIndicator(), - ); - } - - return CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: CenteredContainer( - child: ArticleItem( - item: item!, - isClickable: true, - isFullDate: true, - isFullContent: true, - ), - ), - ), - SliverToBoxAdapter( - child: SizedBox(height: MediaQuery.of(context).padding.bottom), - ), - ], - ); - }, - ), - ); - } -} diff --git a/lib/screens/articles/article_editor.dart b/lib/screens/articles/article_editor.dart deleted file mode 100644 index 8c836dc..0000000 --- a/lib/screens/articles/article_editor.dart +++ /dev/null @@ -1,298 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_animate/flutter_animate.dart'; -import 'package:get/get.dart'; -import 'package:solian/exts.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/models/realm.dart'; -import 'package:solian/providers/auth.dart'; -import 'package:solian/router.dart'; -import 'package:solian/theme.dart'; -import 'package:solian/widgets/app_bar_leading.dart'; -import 'package:solian/widgets/app_bar_title.dart'; -import 'package:solian/widgets/attachments/attachment_publish.dart'; -import 'package:solian/widgets/feed/feed_tags_field.dart'; -import 'package:textfield_tags/textfield_tags.dart'; -import 'package:badges/badges.dart' as badges; - -class ArticlePublishArguments { - final Article? edit; - final Realm? realm; - - ArticlePublishArguments({this.edit, this.realm}); -} - -class ArticlePublishScreen extends StatefulWidget { - final Article? edit; - final Realm? realm; - - const ArticlePublishScreen({ - super.key, - this.edit, - this.realm, - }); - - @override - State createState() => _ArticlePublishScreenState(); -} - -class _ArticlePublishScreenState extends State { - final _titleController = TextEditingController(); - final _descriptionController = TextEditingController(); - final _contentController = TextEditingController(); - final _tagsController = StringTagController(); - - bool _isBusy = false; - - List _attachments = List.empty(); - - bool _isDraft = false; - - void showAttachments() { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => AttachmentPublishPopup( - usage: 'i.attachment', - current: _attachments, - onUpdate: (value) { - setState(() => _attachments = value); - }, - ), - ); - } - - void applyPost() async { - final AuthProvider auth = Get.find(); - if (!await auth.isAuthorized) return; - if (_contentController.value.text.isEmpty) return; - - setState(() => _isBusy = true); - - final client = auth.configureClient('interactive'); - - final payload = { - 'title': _titleController.value.text, - 'description': _descriptionController.value.text, - 'content': _contentController.value.text, - 'tags': _tagsController.getTags?.map((x) => {'alias': x}).toList() ?? - List.empty(), - 'attachments': _attachments, - 'is_draft': _isDraft, - if (widget.edit != null) 'alias': widget.edit!.alias, - if (widget.realm != null) 'realm': widget.realm!.alias, - }; - - Response resp; - if (widget.edit != null) { - resp = await client.put('/articles/${widget.edit!.id}', payload); - } else { - resp = await client.post('/articles', payload); - } - if (resp.statusCode != 200) { - context.showErrorDialog(resp.bodyString); - } else { - AppRouter.instance.pop(resp.body); - } - - setState(() => _isBusy = false); - } - - void syncWidget() { - if (widget.edit != null) { - _titleController.text = widget.edit!.title; - _descriptionController.text = widget.edit!.description; - _contentController.text = widget.edit!.content; - _attachments = widget.edit!.attachments ?? List.empty(); - _isDraft = widget.edit!.isDraft ?? false; - } - } - - void cancelAction() { - AppRouter.instance.pop(); - } - - @override - void initState() { - syncWidget(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final notifyBannerActions = [ - TextButton( - onPressed: cancelAction, - child: Text('cancel'.tr), - ) - ]; - - return Material( - color: Theme.of(context).colorScheme.surface, - child: Scaffold( - appBar: AppBar( - leading: AppBarLeadingButton.adaptive(context), - title: AppBarTitle('articlePublish'.tr), - centerTitle: false, - toolbarHeight: SolianTheme.toolbarHeight(context), - actions: [ - TextButton( - onPressed: _isBusy ? null : () => applyPost(), - child: Text( - _isDraft - ? 'draftSave'.tr.toUpperCase() - : 'postAction'.tr.toUpperCase(), - ), - ) - ], - ), - body: Stack( - children: [ - ListView( - children: [ - if (_isBusy) const LinearProgressIndicator().animate().scaleX(), - if (widget.edit != null) - MaterialBanner( - leading: const Icon(Icons.edit), - leadingPadding: const EdgeInsets.only(left: 10, right: 20), - dividerColor: Colors.transparent, - content: Text('postEditingNotify'.tr), - actions: notifyBannerActions, - ), - if (widget.realm != null) - MaterialBanner( - leading: const Icon(Icons.group), - leadingPadding: const EdgeInsets.only(left: 10, right: 20), - dividerColor: Colors.transparent, - content: Text( - 'postInRealmNotify' - .trParams({'realm': '#${widget.realm!.alias}'}), - ), - actions: notifyBannerActions, - ), - const Divider(thickness: 0.3, height: 0.3) - .paddingOnly(bottom: 6), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: TextField( - maxLines: null, - autofocus: true, - autocorrect: true, - controller: _titleController, - decoration: InputDecoration.collapsed( - hintText: 'articleTitlePlaceholder'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - ), - ), - const Divider(thickness: 0.3, height: 0.3) - .paddingOnly(bottom: 6), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: TextField( - minLines: 1, - maxLines: 3, - autofocus: true, - autocorrect: true, - keyboardType: TextInputType.multiline, - controller: _descriptionController, - decoration: InputDecoration.collapsed( - hintText: 'articleDescriptionPlaceholder'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - ), - ), - const Divider(thickness: 0.3, height: 0.3) - .paddingSymmetric(vertical: 6), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: TextField( - maxLines: null, - autofocus: true, - autocorrect: true, - keyboardType: TextInputType.multiline, - controller: _contentController, - decoration: InputDecoration.collapsed( - hintText: 'articleContentPlaceholder'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - ), - ), - const SizedBox(height: 120), - ], - ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: Material( - elevation: 8, - color: Theme.of(context).colorScheme.surface, - child: Column( - children: [ - TagsField( - initialTags: - widget.edit?.tags?.map((x) => x.alias).toList(), - tagsController: _tagsController, - hintText: 'postTagsPlaceholder'.tr, - ), - const Divider(thickness: 0.3, height: 0.3), - SizedBox( - height: 56, - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - IconButton( - icon: _isDraft - ? const Icon(Icons.drive_file_rename_outline) - : const Icon(Icons.public), - color: _isDraft - ? Colors.grey.shade600 - : Colors.green.shade700, - onPressed: () { - setState(() => _isDraft = !_isDraft); - }, - ), - IconButton( - icon: badges.Badge( - badgeContent: Text( - _attachments.length.toString(), - style: const TextStyle(color: Colors.white), - ), - showBadge: _attachments.isNotEmpty, - position: badges.BadgePosition.topEnd( - top: -12, - end: -8, - ), - child: const Icon(Icons.camera_alt), - ), - color: Theme.of(context).colorScheme.primary, - onPressed: () => showAttachments(), - ), - ], - ).paddingSymmetric(horizontal: 6, vertical: 8), - ), - ], - ).paddingOnly(bottom: MediaQuery.of(context).padding.bottom), - ), - ), - ], - ), - ), - ); - } - - @override - void dispose() { - _titleController.dispose(); - _descriptionController.dispose(); - _contentController.dispose(); - _tagsController.dispose(); - super.dispose(); - } -} diff --git a/lib/screens/feed/draft_box.dart b/lib/screens/feed/draft_box.dart index c2d4067..0738378 100644 --- a/lib/screens/feed/draft_box.dart +++ b/lib/screens/feed/draft_box.dart @@ -1,17 +1,13 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/models/feed.dart'; import 'package:solian/models/pagination.dart'; import 'package:solian/models/post.dart'; -import 'package:solian/providers/content/feed.dart'; +import 'package:solian/providers/content/posts.dart'; import 'package:solian/screens/home.dart'; import 'package:solian/theme.dart'; import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_title.dart'; -import 'package:solian/widgets/articles/article_action.dart'; -import 'package:solian/widgets/articles/article_owned_list.dart'; import 'package:solian/widgets/posts/post_action.dart'; import 'package:solian/widgets/posts/post_owned_list.dart'; @@ -23,11 +19,11 @@ class DraftBoxScreen extends StatefulWidget { } class _DraftBoxScreenState extends State { - final PagingController _pagingController = + final PagingController _pagingController = PagingController(firstPageKey: 0); getPosts(int pageKey) async { - final FeedProvider provider = Get.find(); + final PostProvider provider = Get.find(); Response resp; try { @@ -43,7 +39,7 @@ class _DraftBoxScreenState extends State { return; } - final parsed = result.data?.map((e) => FeedRecord.fromJson(e)).toList(); + final parsed = result.data?.map((e) => Post.fromJson(e)).toList(); if (parsed != null && parsed.length >= 10) { _pagingController.appendPage(parsed, pageKey + parsed.length); } else if (parsed != null) { @@ -79,45 +75,25 @@ class _DraftBoxScreenState extends State { ), body: RefreshIndicator( onRefresh: () => Future.sync(() => _pagingController.refresh()), - child: PagedListView( + child: PagedListView( pagingController: _pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) { - switch (item.type) { - case 'post': - final data = Post.fromJson(item.data); - return PostOwnedListEntry( - item: data, - onTap: () async { - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) => PostAction( - item: data, - noReact: true, - ), - ).then((value) { - if (value != null) _pagingController.refresh(); - }); - }, - ).paddingOnly(left: 12, right: 12, bottom: 4); - case 'article': - final data = Article.fromJson(item.data); - return ArticleOwnedListEntry( - item: data, - onTap: () async { - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) => ArticleAction(item: data), - ).then((value) { - if (value != null) _pagingController.refresh(); - }); - }, - ).paddingOnly(left: 12, right: 12, bottom: 4); - default: - return const SizedBox(); - } + return PostOwnedListEntry( + item: item, + onTap: () async { + showModalBottomSheet( + useRootNavigator: true, + context: context, + builder: (context) => PostAction( + item: item, + noReact: true, + ), + ).then((value) { + if (value != null) _pagingController.refresh(); + }); + }, + ).paddingOnly(left: 12, right: 12, bottom: 4); }, ), ), diff --git a/lib/screens/feed/search.dart b/lib/screens/feed/search.dart index 5dbc521..163c22b 100644 --- a/lib/screens/feed/search.dart +++ b/lib/screens/feed/search.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:solian/models/feed.dart'; import 'package:solian/models/pagination.dart'; -import 'package:solian/providers/content/feed.dart'; +import 'package:solian/providers/content/posts.dart'; import 'package:solian/widgets/feed/feed_list.dart'; +import '../../models/post.dart'; + class FeedSearchScreen extends StatefulWidget { final String? tag; final String? category; @@ -17,15 +18,15 @@ class FeedSearchScreen extends StatefulWidget { } class _FeedSearchScreenState extends State { - final PagingController _pagingController = + final PagingController _pagingController = PagingController(firstPageKey: 0); getPosts(int pageKey) async { - final FeedProvider provider = Get.find(); + final PostProvider provider = Get.find(); Response resp; try { - resp = await provider.listFeed( + resp = await provider.listRecommendations( pageKey, tag: widget.tag, category: widget.category, @@ -36,7 +37,7 @@ class _FeedSearchScreenState extends State { } final PaginationResult result = PaginationResult.fromJson(resp.body); - final parsed = result.data?.map((e) => FeedRecord.fromJson(e)).toList(); + final parsed = result.data?.map((e) => Post.fromJson(e)).toList(); if (parsed != null && parsed.length >= 10) { _pagingController.appendPage(parsed, pageKey + parsed.length); } else if (parsed != null) { diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 058830a..101eb50 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:solian/models/feed.dart'; import 'package:solian/models/pagination.dart'; +import 'package:solian/models/post.dart'; import 'package:solian/providers/auth.dart'; -import 'package:solian/providers/content/feed.dart'; +import 'package:solian/providers/content/posts.dart'; import 'package:solian/router.dart'; import 'package:solian/screens/account/notification.dart'; import 'package:solian/theme.dart'; @@ -21,22 +21,22 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { - final PagingController _pagingController = + final PagingController _pagingController = PagingController(firstPageKey: 0); getPosts(int pageKey) async { - final FeedProvider provider = Get.find(); + final PostProvider provider = Get.find(); Response resp; try { - resp = await provider.listFeed(pageKey); + resp = await provider.listRecommendations(pageKey); } catch (e) { _pagingController.error = e; return; } final PaginationResult result = PaginationResult.fromJson(resp.body); - final parsed = result.data?.map((e) => FeedRecord.fromJson(e)).toList(); + final parsed = result.data?.map((e) => Post.fromJson(e)).toList(); if (parsed != null && parsed.length >= 10) { _pagingController.appendPage(parsed, pageKey + parsed.length); } else if (parsed != null) { @@ -128,20 +128,6 @@ class FeedCreationButton extends StatelessWidget { }); }, ), - PopupMenuItem( - child: ListTile( - title: Text('articleEditor'.tr), - leading: const Icon(Icons.newspaper), - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - ), - onTap: () { - AppRouter.instance.pushNamed('articleEditor').then((val) { - if (val != null && onCreated != null) { - onCreated!(); - } - }); - }, - ), if (!hideDraftBox) PopupMenuItem( child: ListTile( diff --git a/lib/screens/posts/post_detail.dart b/lib/screens/posts/post_detail.dart index d34180e..9f8ee09 100644 --- a/lib/screens/posts/post_detail.dart +++ b/lib/screens/posts/post_detail.dart @@ -2,15 +2,15 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:solian/exts.dart'; import 'package:solian/models/post.dart'; -import 'package:solian/providers/content/feed.dart'; +import 'package:solian/providers/content/posts.dart'; import 'package:solian/widgets/sized_container.dart'; import 'package:solian/widgets/posts/post_item.dart'; import 'package:solian/widgets/posts/post_replies.dart'; class PostDetailScreen extends StatefulWidget { - final String alias; + final String id; - const PostDetailScreen({super.key, required this.alias}); + const PostDetailScreen({super.key, required this.id}); @override State createState() => _PostDetailScreenState(); @@ -20,10 +20,10 @@ class _PostDetailScreenState extends State { Post? item; Future getDetail() async { - final FeedProvider provider = Get.find(); + final PostProvider provider = Get.find(); try { - final resp = await provider.getPost(widget.alias); + final resp = await provider.getPost(widget.id); item = Post.fromJson(resp.body); } catch (e) { context.showErrorDialog(e).then((_) => Navigator.pop(context)); diff --git a/lib/screens/posts/post_editor.dart b/lib/screens/posts/post_editor.dart index 6a923c4..11f46a1 100644 --- a/lib/screens/posts/post_editor.dart +++ b/lib/screens/posts/post_editor.dart @@ -82,7 +82,6 @@ class _PostPublishScreenState extends State { List.empty(), 'attachments': _attachments, 'is_draft': _isDraft, - if (widget.edit != null) 'alias': widget.edit!.alias, if (widget.reply != null) 'reply_to': widget.reply!.id, if (widget.repost != null) 'repost_to': widget.repost!.id, if (widget.realm != null) 'realm': widget.realm!.alias, @@ -90,9 +89,9 @@ class _PostPublishScreenState extends State { Response resp; if (widget.edit != null) { - resp = await client.put('/posts/${widget.edit!.id}', payload); + resp = await client.put('/stories/${widget.edit!.id}', payload); } else { - resp = await client.post('/posts', payload); + resp = await client.post('/stories', payload); } if (resp.statusCode != 200) { context.showErrorDialog(resp.bodyString); @@ -105,8 +104,8 @@ class _PostPublishScreenState extends State { void syncWidget() { if (widget.edit != null) { - _contentController.text = widget.edit!.content; - _attachments = widget.edit!.attachments ?? List.empty(); + _contentController.text = widget.edit!.body['content']; + _attachments = widget.edit!.body['attachments'] ?? List.empty(); _isDraft = widget.edit!.isDraft ?? false; } } diff --git a/lib/screens/realms/realm_view.dart b/lib/screens/realms/realm_view.dart index 177cce1..51fd581 100644 --- a/lib/screens/realms/realm_view.dart +++ b/lib/screens/realms/realm_view.dart @@ -8,7 +8,7 @@ import 'package:solian/models/post.dart'; import 'package:solian/models/realm.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/providers/content/channel.dart'; -import 'package:solian/providers/content/feed.dart'; +import 'package:solian/providers/content/posts.dart'; import 'package:solian/providers/content/realm.dart'; import 'package:solian/router.dart'; import 'package:solian/screens/channel/channel_organize.dart'; @@ -167,7 +167,7 @@ class _RealmPostListWidgetState extends State { PagingController(firstPageKey: 0); getPosts(int pageKey) async { - final FeedProvider provider = Get.find(); + final PostProvider provider = Get.find(); Response resp; try { diff --git a/lib/widgets/articles/article_action.dart b/lib/widgets/articles/article_action.dart deleted file mode 100644 index 2ee5701..0000000 --- a/lib/widgets/articles/article_action.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_animate/flutter_animate.dart'; -import 'package:get/get.dart'; -import 'package:solian/exts.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/providers/auth.dart'; -import 'package:solian/router.dart'; -import 'package:solian/screens/articles/article_editor.dart'; - -class ArticleAction extends StatefulWidget { - final Article item; - - const ArticleAction({super.key, required this.item}); - - @override - State createState() => _ArticleActionState(); -} - -class _ArticleActionState extends State { - bool _isBusy = true; - bool _canModifyContent = false; - - void checkAbleToModifyContent() async { - final AuthProvider provider = Get.find(); - if (!await provider.isAuthorized) return; - - setState(() => _isBusy = true); - - final prof = await provider.getProfile(); - setState(() { - _canModifyContent = prof.body?['id'] == widget.item.author.externalId; - _isBusy = false; - }); - } - - @override - void initState() { - super.initState(); - checkAbleToModifyContent(); - } - - @override - Widget build(BuildContext context) { - return SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'postActionList'.tr, - style: Theme.of(context).textTheme.headlineSmall, - ), - Text( - '#${widget.item.id.toString().padLeft(8, '0')}', - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ).paddingOnly(left: 24, right: 24, top: 32, bottom: 16), - if (_isBusy) const LinearProgressIndicator().animate().scaleX(), - Expanded( - child: ListView( - children: [ - if (_canModifyContent) - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - leading: const Icon(Icons.edit), - title: Text('edit'.tr), - onTap: () async { - final value = await AppRouter.instance.pushNamed( - 'articleEditor', - extra: ArticlePublishArguments(edit: widget.item), - ); - if (value != null) { - Navigator.pop(context, true); - } - }, - ), - if (_canModifyContent) - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - leading: const Icon(Icons.delete), - title: Text('delete'.tr), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => - ArticleDeletionDialog(item: widget.item), - ); - if (value != null) { - Navigator.pop(context, true); - } - }, - ), - ], - ), - ), - ], - ), - ); - } -} - -class ArticleDeletionDialog extends StatefulWidget { - final Article item; - - const ArticleDeletionDialog({super.key, required this.item}); - - @override - State createState() => _ArticleDeletionDialogState(); -} - -class _ArticleDeletionDialogState extends State { - bool _isBusy = false; - - void performAction() async { - final AuthProvider auth = Get.find(); - if (!await auth.isAuthorized) return; - - final client = auth.configureClient('interactive'); - - setState(() => _isBusy = true); - final resp = await client.delete('/articles/${widget.item.id}'); - setState(() => _isBusy = false); - - if (resp.statusCode != 200) { - context.showErrorDialog(resp.bodyString); - } else { - Navigator.pop(context, true); - } - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: Text('postDeletionConfirm'.tr), - content: Text('postDeletionConfirmCaption'.trParams({ - 'content': widget.item.content - .substring(0, min(widget.item.content.length, 60)) - .trim(), - })), - actions: [ - TextButton( - onPressed: _isBusy ? null : () => Navigator.pop(context), - child: Text('cancel'.tr), - ), - TextButton( - onPressed: _isBusy ? null : () => performAction(), - child: Text('confirm'.tr), - ), - ], - ); - } -} diff --git a/lib/widgets/articles/article_item.dart b/lib/widgets/articles/article_item.dart deleted file mode 100644 index f3551f6..0000000 --- a/lib/widgets/articles/article_item.dart +++ /dev/null @@ -1,210 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get_utils/get_utils.dart'; -import 'package:intl/intl.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/widgets/account/account_avatar.dart'; -import 'package:solian/widgets/account/account_profile_popup.dart'; -import 'package:solian/widgets/articles/article_quick_action.dart'; -import 'package:solian/widgets/markdown_text_content.dart'; -import 'package:solian/widgets/feed/feed_tags.dart'; -import 'package:timeago/timeago.dart' show format; - -class ArticleItem extends StatefulWidget { - final Article item; - final bool isClickable; - final bool isReactable; - final bool isFullDate; - final bool isFullContent; - final String? overrideAttachmentParent; - - const ArticleItem({ - super.key, - required this.item, - this.isClickable = false, - this.isReactable = true, - this.isFullDate = false, - this.isFullContent = false, - this.overrideAttachmentParent, - }); - - @override - State createState() => _ArticleItemState(); -} - -class _ArticleItemState extends State { - late final Article item; - - @override - void initState() { - item = widget.item; - super.initState(); - } - - Widget buildDate() { - if (widget.isFullDate) { - return Text(DateFormat('y/M/d H:m').format(item.createdAt.toLocal())); - } else { - return Text(format(item.createdAt.toLocal(), locale: 'en_short')); - } - } - - Widget buildHeader() { - return Row( - children: [ - Text( - item.author.nick, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - buildDate().paddingOnly(left: 4), - ], - ); - } - - Widget buildFooter() { - List labels = List.from(['article'.tr], growable: true); - if (widget.item.createdAt != widget.item.updatedAt) { - labels.add('postEdited'.trParams({ - 'date': DateFormat('yy/M/d H:m').format(item.updatedAt.toLocal()), - })); - } - if (widget.item.realm != null) { - labels.add('postInRealm'.trParams({ - 'realm': '#${widget.item.realm!.alias}', - })); - } - - List widgets = List.empty(growable: true); - - if (widget.item.tags?.isNotEmpty ?? false) { - widgets.add(FeedTagsList(tags: widget.item.tags!)); - } - if (labels.isNotEmpty) { - widgets.add(Text( - labels.join(' ยท '), - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75), - ), - )); - } - - if (widgets.isEmpty) { - return const SizedBox(); - } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: widgets, - ).paddingOnly(top: 4); - } - } - - @override - Widget build(BuildContext context) { - if (!widget.isFullContent) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AccountAvatar(content: item.author.avatar.toString()), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildHeader(), - Text(item.title, style: const TextStyle(fontSize: 15)), - Text( - item.description, - style: TextStyle( - fontSize: 13, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.8), - ), - ), - buildFooter(), - ], - ).paddingOnly(left: 12), - ), - ], - ).paddingOnly( - top: 10, - bottom: 10, - right: 16, - left: 16, - ); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - child: AccountAvatar(content: item.author.avatar.toString()), - onTap: () { - showModalBottomSheet( - useRootNavigator: true, - isScrollControlled: true, - backgroundColor: Theme.of(context).colorScheme.surface, - context: context, - builder: (context) => AccountProfilePopup( - account: item.author, - ), - ); - }, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildHeader(), - Text(item.title), - Text( - item.description, - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.8), - ), - ), - ], - ).paddingOnly(left: 12), - ) - ], - ).paddingOnly( - top: 10, - right: 16, - left: 16, - ), - MarkdownTextContent(content: item.content).paddingOnly( - left: 20, - right: 20, - top: 10, - bottom: 8, - ), - buildFooter().paddingOnly(left: 20), - if (widget.isReactable) - ArticleQuickAction( - isReactable: widget.isReactable, - item: widget.item, - onReact: (symbol, changes) { - setState(() { - item.metric!.reactionList[symbol] = - (item.metric!.reactionList[symbol] ?? 0) + changes; - }); - }, - ).paddingOnly( - top: 6, - left: 16, - right: 16, - bottom: 10, - ) - else - const SizedBox(height: 10), - ], - ); - } -} diff --git a/lib/widgets/articles/article_list.dart b/lib/widgets/articles/article_list.dart deleted file mode 100644 index 559a115..0000000 --- a/lib/widgets/articles/article_list.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/router.dart'; -import 'package:solian/widgets/articles/article_action.dart'; -import 'package:solian/widgets/articles/article_item.dart'; -import 'package:solian/widgets/sized_container.dart'; - -class ArticleListWidget extends StatelessWidget { - final bool isShowEmbed; - final bool isClickable; - final bool isNestedClickable; - final PagingController controller; - - const ArticleListWidget({ - super.key, - required this.controller, - this.isShowEmbed = true, - this.isClickable = true, - this.isNestedClickable = true, - }); - - @override - Widget build(BuildContext context) { - return PagedSliverList.separated( - addRepaintBoundaries: true, - pagingController: controller, - builderDelegate: PagedChildBuilderDelegate
( - itemBuilder: (context, item, index) { - return CenteredContainer( - child: ArticleListEntryWidget( - isClickable: isClickable, - item: item, - onUpdate: () { - controller.refresh(); - }, - ), - ); - }, - ), - separatorBuilder: (_, __) => const Divider(thickness: 0.3, height: 0.3), - ); - } -} - -class ArticleListEntryWidget extends StatelessWidget { - final bool isClickable; - final Article item; - final Function onUpdate; - - const ArticleListEntryWidget({ - super.key, - required this.isClickable, - required this.item, - required this.onUpdate, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - child: ArticleItem( - key: Key('a${item.alias}'), - item: item, - ).paddingSymmetric(vertical: 8), - onTap: () { - if (!isClickable) return; - AppRouter.instance.pushNamed( - 'articleDetail', - pathParameters: {'alias': item.alias}, - ); - }, - onLongPress: () { - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) => ArticleAction(item: item), - ).then((value) { - if (value != null) onUpdate(); - }); - }, - ); - } -} diff --git a/lib/widgets/articles/article_owned_list.dart b/lib/widgets/articles/article_owned_list.dart deleted file mode 100644 index 59799a0..0000000 --- a/lib/widgets/articles/article_owned_list.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/widgets/articles/article_item.dart'; - -class ArticleOwnedListEntry extends StatelessWidget { - final Article item; - final Function onTap; - - const ArticleOwnedListEntry({ - super.key, - required this.item, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return Card( - child: InkWell( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ArticleItem( - key: Key('a${item.alias}'), - item: item, - isClickable: false, - isReactable: false, - ), - ], - ), - onTap: () => onTap(), - ), - ); - } -} diff --git a/lib/widgets/articles/article_quick_action.dart b/lib/widgets/articles/article_quick_action.dart deleted file mode 100644 index 68c633c..0000000 --- a/lib/widgets/articles/article_quick_action.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:solian/exts.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/models/reaction.dart'; -import 'package:solian/providers/auth.dart'; -import 'package:solian/widgets/posts/post_reaction.dart'; - -class ArticleQuickAction extends StatefulWidget { - final Article item; - final bool isReactable; - final void Function(String symbol, int num) onReact; - - const ArticleQuickAction({ - super.key, - required this.item, - this.isReactable = true, - required this.onReact, - }); - - @override - State createState() => _ArticleQuickActionState(); -} - -class _ArticleQuickActionState extends State { - bool _isSubmitting = false; - - void showReactMenu() { - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) => PostReactionPopup( - reactionList: widget.item.metric!.reactionList, - onReact: (key, value) { - doWidgetReact(key, value.attitude); - }, - ), - ); - } - - Future doWidgetReact(String symbol, int attitude) async { - if (!widget.isReactable) return; - - final AuthProvider auth = Get.find(); - - if (_isSubmitting) return; - if (!await auth.isAuthorized) return; - - final client = auth.configureClient('interactive'); - - setState(() => _isSubmitting = true); - - final resp = await client.post('/articles/${widget.item.alias}/react', { - 'symbol': symbol, - 'attitude': attitude, - }); - if (resp.statusCode == 201) { - widget.onReact(symbol, 1); - context.showSnackbar('reactCompleted'.tr); - } else if (resp.statusCode == 204) { - widget.onReact(symbol, -1); - context.showSnackbar('reactUncompleted'.tr); - } else { - context.showErrorDialog(resp.bodyString); - } - - setState(() => _isSubmitting = false); - } - - @override - void initState() { - super.initState(); - - if (!widget.isReactable && widget.item.metric!.reactionList.isEmpty) { - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onReact('thumb_up', 0); - }); - } - } - - @override - Widget build(BuildContext context) { - const density = VisualDensity(horizontal: -4, vertical: -3); - - return SizedBox( - height: 32, - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: ListView( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: [ - ...widget.item.metric!.reactionList.entries.map((x) { - final info = reactions[x.key]; - return Padding( - padding: const EdgeInsets.only(right: 8), - child: ActionChip( - avatar: Text(info!.icon), - label: Text(x.value.toString()), - tooltip: ':${x.key}:', - visualDensity: density, - onPressed: _isSubmitting - ? null - : () => doWidgetReact(x.key, info.attitude), - ), - ); - }), - if (widget.isReactable) - ActionChip( - avatar: const Icon(Icons.add_reaction, color: Colors.teal), - label: Text('reactAdd'.tr), - visualDensity: density, - onPressed: () => showReactMenu(), - ), - ], - ), - ) - ], - ), - ); - } -} diff --git a/lib/widgets/feed/feed_list.dart b/lib/widgets/feed/feed_list.dart index d9c09b1..c90b614 100644 --- a/lib/widgets/feed/feed_list.dart +++ b/lib/widgets/feed/feed_list.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:solian/models/articles.dart'; -import 'package:solian/models/feed.dart'; import 'package:solian/models/post.dart'; -import 'package:solian/widgets/articles/article_list.dart'; import 'package:solian/widgets/sized_container.dart'; import 'package:solian/widgets/posts/post_list.dart'; @@ -11,7 +8,7 @@ class FeedListWidget extends StatelessWidget { final bool isShowEmbed; final bool isClickable; final bool isNestedClickable; - final PagingController controller; + final PagingController controller; const FeedListWidget({ super.key, @@ -23,38 +20,23 @@ class FeedListWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return PagedSliverList.separated( + return PagedSliverList.separated( addRepaintBoundaries: true, pagingController: controller, - builderDelegate: PagedChildBuilderDelegate( + builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) { return SizedContainer( child: Builder( builder: (context) { - switch (item.type) { - case 'post': - final data = Post.fromJson(item.data); - return PostListEntryWidget( - isShowEmbed: isShowEmbed, - isNestedClickable: isNestedClickable, - isClickable: isClickable, - item: data, - onUpdate: () { - controller.refresh(); - }, - ); - case 'article': - final data = Article.fromJson(item.data); - return ArticleListEntryWidget( - isClickable: isClickable, - item: data, - onUpdate: () { - controller.refresh(); - }, - ); - default: - return const SizedBox(); - } + return PostListEntryWidget( + isShowEmbed: isShowEmbed, + isNestedClickable: isNestedClickable, + isClickable: isClickable, + item: item, + onUpdate: () { + controller.refresh(); + }, + ); }, ), ); diff --git a/lib/widgets/posts/post_action.dart b/lib/widgets/posts/post_action.dart index 5de8a45..cad8de3 100644 --- a/lib/widgets/posts/post_action.dart +++ b/lib/widgets/posts/post_action.dart @@ -173,8 +173,8 @@ class _PostDeletionDialogState extends State { return AlertDialog( title: Text('postDeletionConfirm'.tr), content: Text('postDeletionConfirmCaption'.trParams({ - 'content': widget.item.content - .substring(0, min(widget.item.content.length, 60)) + 'content': widget.item.body['content'] + .substring(0, min(widget.item.body['content'].length, 60)) .trim(), })), actions: [ diff --git a/lib/widgets/posts/post_item.dart b/lib/widgets/posts/post_item.dart index be7e1fb..e2c7236 100644 --- a/lib/widgets/posts/post_item.dart +++ b/lib/widgets/posts/post_item.dart @@ -86,7 +86,7 @@ class _PostItemState extends State { } if (widget.item.realm != null) { labels.add('postInRealm'.trParams({ - 'realm': '#${widget.item.realm!.alias}', + 'realm': '#${widget.item.realm!.id}', })); } @@ -141,7 +141,7 @@ class _PostItemState extends State { child: PostItem( item: widget.item.replyTo!, isCompact: true, - overrideAttachmentParent: widget.item.alias, + overrideAttachmentParent: widget.item.id.toString(), ).paddingSymmetric(vertical: 8), ), ], @@ -173,7 +173,7 @@ class _PostItemState extends State { child: PostItem( item: widget.item.repostTo!, isCompact: true, - overrideAttachmentParent: widget.item.alias, + overrideAttachmentParent: widget.item.id.toString(), ).paddingSymmetric(vertical: 8), ), ], @@ -182,7 +182,7 @@ class _PostItemState extends State { @override Widget build(BuildContext context) { - final hasAttachment = item.attachments?.isNotEmpty ?? false; + final hasAttachment = item.body['attachments']?.isNotEmpty ?? false; if (widget.isCompact) { return Column( @@ -190,7 +190,7 @@ class _PostItemState extends State { children: [ buildHeader().paddingSymmetric(horizontal: 12), MarkdownTextContent( - content: item.content, + content: item.body['content'], isSelectable: widget.isContentSelectable, ).paddingOnly( left: 16, @@ -199,7 +199,7 @@ class _PostItemState extends State { bottom: hasAttachment ? 4 : 0, ), buildFooter().paddingOnly(left: 16), - if (item.attachments?.isNotEmpty ?? false) + if (item.body['attachments']?.isNotEmpty ?? false) Row( children: [ Icon( @@ -209,7 +209,7 @@ class _PostItemState extends State { ).paddingOnly(right: 6), Text( 'postAttachmentTip'.trParams( - {'count': item.attachments!.length.toString()}, + {'count': item.body['attachments']!.length.toString()}, ), style: TextStyle(color: _unFocusColor), ) @@ -245,7 +245,7 @@ class _PostItemState extends State { children: [ buildHeader(), MarkdownTextContent( - content: item.content, + content: item.body['content'], isSelectable: widget.isContentSelectable, ).paddingOnly(left: 12, right: 8), if (widget.item.replyTo != null && widget.isShowEmbed) @@ -256,7 +256,7 @@ class _PostItemState extends State { AppRouter.instance.pushNamed( 'postDetail', pathParameters: { - 'alias': widget.item.replyTo!.alias, + 'id': widget.item.replyTo!.id.toString(), }, ); }, @@ -269,7 +269,7 @@ class _PostItemState extends State { AppRouter.instance.pushNamed( 'postDetail', pathParameters: { - 'alias': widget.item.repostTo!.alias, + 'alias': widget.item.repostTo!.id.toString(), }, ); }, @@ -292,8 +292,8 @@ class _PostItemState extends State { maxWidth: 640, ), child: AttachmentList( - parentId: widget.item.alias, - attachmentsId: item.attachments ?? List.empty(), + parentId: widget.item.id.toString(), + attachmentsId: item.body['attachments'].cast() ?? List.empty(), divided: true, ), ), diff --git a/lib/widgets/posts/post_list.dart b/lib/widgets/posts/post_list.dart index 10c5c37..63e8279 100644 --- a/lib/widgets/posts/post_list.dart +++ b/lib/widgets/posts/post_list.dart @@ -66,7 +66,7 @@ class PostListEntryWidget extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( child: PostItem( - key: Key('p${item.alias}'), + key: Key('p${item.id}'), item: item, isShowEmbed: isShowEmbed, isClickable: isNestedClickable, @@ -75,7 +75,7 @@ class PostListEntryWidget extends StatelessWidget { if (!isClickable) return; AppRouter.instance.pushNamed( 'postDetail', - pathParameters: {'alias': item.alias}, + pathParameters: {'id': item.id.toString()}, ); }, onLongPress: () { diff --git a/lib/widgets/posts/post_owned_list.dart b/lib/widgets/posts/post_owned_list.dart index ca6dd53..b3fe37d 100644 --- a/lib/widgets/posts/post_owned_list.dart +++ b/lib/widgets/posts/post_owned_list.dart @@ -22,7 +22,7 @@ class PostOwnedListEntry extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ PostItem( - key: Key('p${item.alias}'), + key: Key('p${item.id}'), item: item, isShowEmbed: false, isClickable: false, diff --git a/lib/widgets/posts/post_quick_action.dart b/lib/widgets/posts/post_quick_action.dart index b236e50..bebdf67 100644 --- a/lib/widgets/posts/post_quick_action.dart +++ b/lib/widgets/posts/post_quick_action.dart @@ -53,7 +53,7 @@ class _PostQuickActionState extends State { setState(() => _isSubmitting = true); - final resp = await client.post('/posts/${widget.item.alias}/react', { + final resp = await client.post('/posts/${widget.item.id}/react', { 'symbol': symbol, 'attitude': attitude, }); diff --git a/lib/widgets/posts/post_replies.dart b/lib/widgets/posts/post_replies.dart index 3c9af2d..c098746 100644 --- a/lib/widgets/posts/post_replies.dart +++ b/lib/widgets/posts/post_replies.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:solian/models/pagination.dart'; import 'package:solian/models/post.dart'; -import 'package:solian/providers/content/feed.dart'; +import 'package:solian/providers/content/posts.dart'; import 'package:solian/widgets/posts/post_list.dart'; class PostReplyList extends StatefulWidget { @@ -23,11 +23,11 @@ class _PostReplyListState extends State { PagingController(firstPageKey: 0); Future getReplies(int pageKey) async { - final FeedProvider provider = Get.find(); + final PostProvider provider = Get.find(); Response resp; try { - resp = await provider.listPostReplies(widget.item.alias, pageKey); + resp = await provider.listPostReplies(widget.item.id.toString(), pageKey); } catch (e) { _pagingController.error = e; return;