Draft box

This commit is contained in:
2024-07-09 22:39:44 +08:00
parent a0fe3f918e
commit fa600d6c69
21 changed files with 914 additions and 676 deletions

View File

@ -315,4 +315,15 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
),
);
}
@override
void dispose() {
_usernameController.dispose();
_nicknameController.dispose();
_firstNameController.dispose();
_lastNameController.dispose();
_descriptionController.dispose();
_birthdayController.dispose();
super.dispose();
}
}

View File

@ -9,7 +9,7 @@ import 'package:solian/router.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/attachments/attachment_publish.dart';
import 'package:solian/widgets/posts/tags_field.dart';
import 'package:solian/widgets/feed/feed_tags_field.dart';
import 'package:solian/widgets/prev_page.dart';
import 'package:textfield_tags/textfield_tags.dart';

View File

@ -4,13 +4,13 @@ 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/auth.dart';
import 'package:solian/providers/content/post.dart';
import 'package:solian/providers/content/feed.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/posts/feed_list.dart';
import 'package:solian/widgets/feed/feed_list.dart';
class FeedScreen extends StatefulWidget {
const FeedScreen({super.key});
@ -24,7 +24,7 @@ class _FeedScreenState extends State<FeedScreen> {
PagingController(firstPageKey: 0);
getPosts(int pageKey) async {
final PostProvider provider = Get.find();
final FeedProvider provider = Get.find();
Response resp;
try {
@ -46,7 +46,6 @@ class _FeedScreenState extends State<FeedScreen> {
@override
void initState() {
super.initState();
_pagingController.addPageRequestListener(getPosts);
}
@ -85,12 +84,23 @@ class _FeedScreenState extends State<FeedScreen> {
),
);
}
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
}
class FeedCreationButton extends StatelessWidget {
final bool hideDraftBox;
final Function? onCreated;
const FeedCreationButton({super.key, this.onCreated});
const FeedCreationButton({
super.key,
this.hideDraftBox = false,
this.onCreated,
});
@override
Widget build(BuildContext context) {
@ -131,16 +141,17 @@ class FeedCreationButton extends StatelessWidget {
});
},
),
PopupMenuItem(
child: ListTile(
title: Text('draftBoxOpen'.tr),
leading: const Icon(Icons.drafts),
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
if (!hideDraftBox)
PopupMenuItem(
child: ListTile(
title: Text('draftBoxOpen'.tr),
leading: const Icon(Icons.drafts),
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
),
onTap: () {
AppRouter.instance.pushNamed('draftBox');
},
),
onTap: () {
AppRouter.instance.goNamed('draftBox');
},
),
],
);
}

View File

@ -1,13 +1,59 @@
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/content/feed.dart';
import 'package:solian/screens/feed.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/posts/post_action.dart';
import 'package:solian/widgets/posts/post_owned_list.dart';
import 'package:solian/widgets/prev_page.dart';
class DraftBoxScreen extends StatelessWidget {
class DraftBoxScreen extends StatefulWidget {
const DraftBoxScreen({super.key});
@override
State<DraftBoxScreen> createState() => _DraftBoxScreenState();
}
class _DraftBoxScreenState extends State<DraftBoxScreen> {
final PagingController<int, FeedRecord> _pagingController =
PagingController(firstPageKey: 0);
getPosts(int pageKey) async {
final FeedProvider provider = Get.find();
Response resp;
try {
resp = await provider.listDraft(pageKey);
} catch (e) {
_pagingController.error = e;
return;
}
final PaginationResult result = PaginationResult.fromJson(resp.body);
if (result.count == 0) {
_pagingController.appendLastPage([]);
return;
}
final parsed = result.data?.map((e) => FeedRecord.fromJson(e)).toList();
if (parsed != null && parsed.length >= 10) {
_pagingController.appendPage(parsed, pageKey + parsed.length);
} else if (parsed != null) {
_pagingController.appendLastPage(parsed);
}
}
@override
void initState() {
super.initState();
_pagingController.addPageRequestListener(getPosts);
}
@override
Widget build(BuildContext context) {
return Material(
@ -20,6 +66,7 @@ class DraftBoxScreen extends StatelessWidget {
leading: const PrevPageButton(),
actions: [
FeedCreationButton(
hideDraftBox: true,
onCreated: () {},
),
SizedBox(
@ -27,7 +74,44 @@ class DraftBoxScreen extends StatelessWidget {
),
],
),
body: RefreshIndicator(
onRefresh: () => Future.sync(() => _pagingController.refresh()),
child: PagedListView<int, FeedRecord>(
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);
default:
return const SizedBox();
}
},
),
),
),
),
);
}
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
}

View File

@ -3,8 +3,8 @@ 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/post.dart';
import 'package:solian/widgets/posts/feed_list.dart';
import 'package:solian/providers/content/feed.dart';
import 'package:solian/widgets/feed/feed_list.dart';
class FeedSearchScreen extends StatefulWidget {
final String? tag;
@ -21,7 +21,7 @@ class _FeedSearchScreenState extends State<FeedSearchScreen> {
PagingController(firstPageKey: 0);
getPosts(int pageKey) async {
final PostProvider provider = Get.find();
final FeedProvider provider = Get.find();
Response resp;
try {
@ -68,7 +68,8 @@ class _FeedSearchScreenState extends State<FeedSearchScreen> {
ListTile(
leading: const Icon(Icons.category),
tileColor: Theme.of(context).colorScheme.surfaceContainer,
title: Text('feedSearchWithCategory'.trParams({'key': widget.category!})),
title: Text('feedSearchWithCategory'
.trParams({'key': widget.category!})),
),
Expanded(
child: RefreshIndicator(
@ -85,4 +86,10 @@ class _FeedSearchScreenState extends State<FeedSearchScreen> {
),
);
}
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
}

View File

@ -2,7 +2,7 @@ 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/post.dart';
import 'package:solian/providers/content/feed.dart';
import 'package:solian/widgets/centered_container.dart';
import 'package:solian/widgets/posts/post_item.dart';
import 'package:solian/widgets/posts/post_replies.dart';
@ -20,7 +20,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
Post? item;
Future<Post?> getDetail() async {
final PostProvider provider = Get.find();
final FeedProvider provider = Get.find();
try {
final resp = await provider.getPost(widget.alias);

View File

@ -11,7 +11,7 @@ import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/attachments/attachment_publish.dart';
import 'package:solian/widgets/posts/post_item.dart';
import 'package:solian/widgets/posts/tags_field.dart';
import 'package:solian/widgets/feed/feed_tags_field.dart';
import 'package:solian/widgets/prev_page.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'package:badges/badges.dart' as badges;
@ -147,95 +147,94 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
)
],
),
body: SafeArea(
top: false,
child: 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.reply != null)
ExpansionTile(
leading: const FaIcon(
FontAwesomeIcons.reply,
size: 18,
).paddingOnly(left: 2),
title: Text('postReplyingNotify'.trParams(
{'username': '@${widget.reply!.author.name}'},
)),
collapsedBackgroundColor:
Theme.of(context).colorScheme.surfaceContainer,
children: [
PostItem(
item: widget.reply!,
isReactable: false,
).paddingOnly(bottom: 8),
],
),
if (widget.repost != null)
ExpansionTile(
leading: const FaIcon(
FontAwesomeIcons.retweet,
size: 18,
).paddingOnly(left: 2),
title: Text('postRepostingNotify'.trParams(
{'username': '@${widget.repost!.author.name}'},
)),
collapsedBackgroundColor:
Theme.of(context).colorScheme.surfaceContainer,
children: [
PostItem(
item: widget.repost!,
isReactable: false,
).paddingOnly(bottom: 8),
],
),
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: 8),
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: 'postContentPlaceholder'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
body: Stack(
children: [
ListView(
children: [
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
if (widget.edit != null && widget.edit!.isDraft != true)
MaterialBanner(
leading: const Icon(Icons.edit),
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text('postEditingNotify'.tr),
actions: notifyBannerActions,
),
],
),
Positioned(
bottom: 0,
left: 0,
right: 0,
if (widget.reply != null)
ExpansionTile(
leading: const FaIcon(
FontAwesomeIcons.reply,
size: 18,
).paddingOnly(left: 2),
title: Text('postReplyingNotify'.trParams(
{'username': '@${widget.reply!.author.name}'},
)),
collapsedBackgroundColor:
Theme.of(context).colorScheme.surfaceContainer,
children: [
PostItem(
item: widget.reply!,
isReactable: false,
).paddingOnly(bottom: 8),
],
),
if (widget.repost != null)
ExpansionTile(
leading: const FaIcon(
FontAwesomeIcons.retweet,
size: 18,
).paddingOnly(left: 2),
title: Text('postRepostingNotify'.trParams(
{'username': '@${widget.repost!.author.name}'},
)),
collapsedBackgroundColor:
Theme.of(context).colorScheme.surfaceContainer,
children: [
PostItem(
item: widget.repost!,
isReactable: false,
).paddingOnly(bottom: 8),
],
),
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: 8),
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: 'postContentPlaceholder'.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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -274,10 +273,10 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
).paddingSymmetric(horizontal: 6, vertical: 8),
),
],
),
).paddingOnly(bottom: MediaQuery.of(context).padding.bottom),
),
],
),
),
],
),
),
);

View File

@ -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/post.dart';
import 'package:solian/providers/content/feed.dart';
import 'package:solian/providers/content/realm.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/channel/channel_organize.dart';
@ -165,7 +165,7 @@ class _RealmPostListWidgetState extends State<RealmPostListWidget> {
PagingController(firstPageKey: 0);
getPosts(int pageKey) async {
final PostProvider provider = Get.find();
final FeedProvider provider = Get.find();
Response resp;
try {