♻️ Move the post editor mode into editor itself

This commit is contained in:
LittleSheep 2025-03-08 16:00:10 +08:00
parent 1c510d63fe
commit 5c483bd3b8
7 changed files with 155 additions and 156 deletions

View File

@ -153,6 +153,11 @@
"publisherRunBy": "Run by {}", "publisherRunBy": "Run by {}",
"fieldPublisherBelongToRealm": "Belongs to", "fieldPublisherBelongToRealm": "Belongs to",
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm", "fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
"writePost": "Compose",
"postTypeStory": "Story",
"postTypeArticle": "Article",
"postTypeQuestion": "Question",
"postTypeVideo": "Video",
"writePostTypeStory": "Post a story", "writePostTypeStory": "Post a story",
"writePostTypeArticle": "Write an article", "writePostTypeArticle": "Write an article",
"writePostTypeQuestion": "Ask a question", "writePostTypeQuestion": "Ask a question",

View File

@ -137,6 +137,11 @@
"publisherRunBy": "由 {} 管理", "publisherRunBy": "由 {} 管理",
"fieldPublisherBelongToRealm": "所属领域", "fieldPublisherBelongToRealm": "所属领域",
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域", "fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
"writePost": "撰写",
"postTypeStory": "动态",
"postTypeArticle": "文章",
"postTypeQuestion": "问题",
"postTypeVideo": "视频",
"writePostTypeStory": "发动态", "writePostTypeStory": "发动态",
"writePostTypeArticle": "写文章", "writePostTypeArticle": "写文章",
"writePostTypeQuestion": "提问题", "writePostTypeQuestion": "提问题",

View File

@ -71,7 +71,8 @@ class PostWriteMedia {
} }
} }
PostWriteMedia.fromBytes(this.raw, this.name, this.type, {this.attachment, this.file}); PostWriteMedia.fromBytes(this.raw, this.name, this.type,
{this.attachment, this.file});
bool get isEmpty => attachment == null && file == null && raw == null; bool get isEmpty => attachment == null && file == null && raw == null;
@ -105,7 +106,8 @@ class PostWriteMedia {
}) { }) {
if (attachment != null) { if (attachment != null) {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final ImageProvider provider = UniversalImage.provider(sn.getAttachmentUrl(attachment!.rid)); final ImageProvider provider =
UniversalImage.provider(sn.getAttachmentUrl(attachment!.rid));
if (width != null && height != null && !kIsWeb) { if (width != null && height != null && !kIsWeb) {
return ResizeImage( return ResizeImage(
provider, provider,
@ -116,7 +118,8 @@ class PostWriteMedia {
} }
return provider; return provider;
} else if (file != null) { } else if (file != null) {
final ImageProvider provider = kIsWeb ? NetworkImage(file!.path) : FileImage(File(file!.path)); final ImageProvider provider =
kIsWeb ? NetworkImage(file!.path) : FileImage(File(file!.path));
if (width != null && height != null) { if (width != null && height != null) {
return ResizeImage( return ResizeImage(
provider, provider,
@ -159,11 +162,14 @@ class PostWriteController extends ChangeNotifier {
final TextEditingController aliasController = TextEditingController(); final TextEditingController aliasController = TextEditingController();
final TextEditingController rewardController = TextEditingController(); final TextEditingController rewardController = TextEditingController();
ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration( ContentInsertionConfiguration get contentInsertionConfiguration =>
ContentInsertionConfiguration(
onContentInserted: (KeyboardInsertedContent content) { onContentInserted: (KeyboardInsertedContent content) {
if (content.hasData) { if (content.hasData) {
addAttachments( addAttachments([
[PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]); PostWriteMedia.fromBytes(content.data!,
'attachmentInsertedImage'.tr(), SnMediaType.image)
]);
} }
}, },
); );
@ -193,7 +199,8 @@ class PostWriteController extends ChangeNotifier {
String get description => descriptionController.text; String get description => descriptionController.text;
bool get isRelatedNull => ![editingPost, repostingPost, replyingPost].any((ele) => ele != null); bool get isRelatedNull =>
![editingPost, repostingPost, replyingPost].any((ele) => ele != null);
bool isLoading = false, isBusy = false; bool isLoading = false, isBusy = false;
double? progress; double? progress;
@ -237,14 +244,18 @@ class PostWriteController extends ChangeNotifier {
publishedAt = post.publishedAt; publishedAt = post.publishedAt;
publishedUntil = post.publishedUntil; publishedUntil = post.publishedUntil;
visibleUsers = List.from(post.visibleUsersList ?? [], growable: true); visibleUsers = List.from(post.visibleUsersList ?? [], growable: true);
invisibleUsers = List.from(post.invisibleUsersList ?? [], growable: true); invisibleUsers =
List.from(post.invisibleUsersList ?? [], growable: true);
visibility = post.visibility; visibility = post.visibility;
tags = List.from(post.tags.map((ele) => ele.alias), growable: true); tags = List.from(post.tags.map((ele) => ele.alias), growable: true);
categories = List.from(post.categories.map((ele) => ele.alias), growable: true); categories =
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []); List.from(post.categories.map((ele) => ele.alias), growable: true);
attachments.addAll(
post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
poll = post.preload?.poll; poll = post.preload?.poll;
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { if (post.preload?.thumbnail != null &&
(post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
thumbnail = PostWriteMedia(post.preload!.thumbnail); thumbnail = PostWriteMedia(post.preload!.thumbnail);
} }
if (post.preload?.realm != null) { if (post.preload?.realm != null) {
@ -272,7 +283,8 @@ class PostWriteController extends ChangeNotifier {
} }
} }
Future<SnAttachment> _uploadAttachment(BuildContext context, PostWriteMedia media, Future<SnAttachment> _uploadAttachment(
BuildContext context, PostWriteMedia media,
{bool isCompressed = false}) async { {bool isCompressed = false}) async {
final attach = context.read<SnAttachmentProvider>(); final attach = context.read<SnAttachmentProvider>();
@ -281,7 +293,9 @@ class PostWriteController extends ChangeNotifier {
media.name, media.name,
'interactive', 'interactive',
null, null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null, mimetype: media.raw != null && media.type == SnMediaType.image
? 'image/png'
: null,
); );
var item = await attach.chunkedUploadParts( var item = await attach.chunkedUploadParts(
@ -297,9 +311,11 @@ class PostWriteController extends ChangeNotifier {
if (media.type == SnMediaType.video && !isCompressed && context.mounted) { if (media.type == SnMediaType.video && !isCompressed && context.mounted) {
try { try {
final compressedAttachment = await _tryCompressVideoCopy(context, media); final compressedAttachment =
await _tryCompressVideoCopy(context, media);
if (compressedAttachment != null) { if (compressedAttachment != null) {
item = await attach.updateOne(item, compressedId: compressedAttachment.id); item = await attach.updateOne(item,
compressedId: compressedAttachment.id);
} }
} catch (err) { } catch (err) {
if (context.mounted) context.showErrorDialog(err); if (context.mounted) context.showErrorDialog(err);
@ -309,8 +325,10 @@ class PostWriteController extends ChangeNotifier {
return item; return item;
} }
Future<SnAttachment?> _tryCompressVideoCopy(BuildContext context, PostWriteMedia media) async { Future<SnAttachment?> _tryCompressVideoCopy(
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) return null; BuildContext context, PostWriteMedia media) async {
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS || Platform.isMacOS))
return null;
if (media.type != SnMediaType.video) return null; if (media.type != SnMediaType.video) return null;
if (media.file == null) return null; if (media.file == null) return null;
if (VideoCompress.isCompressing) return null; if (VideoCompress.isCompressing) return null;
@ -334,7 +352,8 @@ class PostWriteController extends ChangeNotifier {
if (!context.mounted) return null; if (!context.mounted) return null;
final compressedMedia = PostWriteMedia.fromFile(XFile(mediaInfo.path!)); final compressedMedia = PostWriteMedia.fromFile(XFile(mediaInfo.path!));
final compressedAttachment = await _uploadAttachment(context, compressedMedia, isCompressed: true); final compressedAttachment =
await _uploadAttachment(context, compressedMedia, isCompressed: true);
return compressedAttachment; return compressedAttachment;
} }
@ -370,18 +389,25 @@ class PostWriteController extends ChangeNotifier {
'content': contentController.text, 'content': contentController.text,
if (aliasController.text.isNotEmpty) 'alias': aliasController.text, if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
if (titleController.text.isNotEmpty) 'title': titleController.text, if (titleController.text.isNotEmpty) 'title': titleController.text,
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text, if (descriptionController.text.isNotEmpty)
'description': descriptionController.text,
if (rewardController.text.isNotEmpty) 'reward': rewardController.text, if (rewardController.text.isNotEmpty) 'reward': rewardController.text,
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(), if (thumbnail != null && thumbnail!.attachment != null)
'attachments': 'thumbnail': thumbnail!.attachment!.toJson(),
attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(growable: true), 'attachments': attachments
.where((e) => e.attachment != null)
.map((e) => e.attachment!.toJson())
.toList(growable: true),
'tags': tags.map((ele) => {'alias': ele}).toList(growable: true), 'tags': tags.map((ele) => {'alias': ele}).toList(growable: true),
'categories': categories.map((ele) => {'alias': ele}).toList(growable: true), 'categories':
categories.map((ele) => {'alias': ele}).toList(growable: true),
'visibility': visibility, 'visibility': visibility,
'visible_users_list': visibleUsers, 'visible_users_list': visibleUsers,
'invisible_users_list': invisibleUsers, 'invisible_users_list': invisibleUsers,
if (publishedAt != null) 'published_at': publishedAt!.toUtc().toIso8601String(), if (publishedAt != null)
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(), 'published_at': publishedAt!.toUtc().toIso8601String(),
if (publishedUntil != null)
'published_until': publishedAt!.toUtc().toIso8601String(),
if (replyingPost != null) 'reply_to': replyingPost!.toJson(), if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
if (repostingPost != null) 'repost_to': repostingPost!.toJson(), if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
if (poll != null) 'poll': poll!.toJson(), if (poll != null) 'poll': poll!.toJson(),
@ -391,6 +417,12 @@ class PostWriteController extends ChangeNotifier {
}); });
} }
bool get isNotEmpty =>
title.isNotEmpty ||
description.isNotEmpty ||
contentController.text.isNotEmpty ||
attachments.isNotEmpty;
bool temporaryRestored = false; bool temporaryRestored = false;
void _temporaryLoad() { void _temporaryLoad() {
@ -403,18 +435,24 @@ class PostWriteController extends ChangeNotifier {
titleController.text = data['title'] ?? ''; titleController.text = data['title'] ?? '';
descriptionController.text = data['description'] ?? ''; descriptionController.text = data['description'] ?? '';
rewardController.text = data['reward']?.toString() ?? ''; rewardController.text = data['reward']?.toString() ?? '';
if (data['thumbnail'] != null) thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail'])); if (data['thumbnail'] != null)
attachments thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
.addAll(data['attachments'].map((ele) => PostWriteMedia(SnAttachment.fromJson(ele))).cast<PostWriteMedia>()); attachments.addAll(data['attachments']
.map((ele) => PostWriteMedia(SnAttachment.fromJson(ele)))
.cast<PostWriteMedia>());
tags = List.from(data['tags'].map((ele) => ele['alias'])); tags = List.from(data['tags'].map((ele) => ele['alias']));
categories = List.from(data['categories'].map((ele) => ele['alias'])); categories = List.from(data['categories'].map((ele) => ele['alias']));
visibility = data['visibility']; visibility = data['visibility'];
visibleUsers = List.from(data['visible_users_list'] ?? []); visibleUsers = List.from(data['visible_users_list'] ?? []);
invisibleUsers = List.from(data['invisible_users_list'] ?? []); invisibleUsers = List.from(data['invisible_users_list'] ?? []);
if (data['published_at'] != null) publishedAt = DateTime.tryParse(data['published_at'])?.toLocal(); if (data['published_at'] != null)
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal(); publishedAt = DateTime.tryParse(data['published_at'])?.toLocal();
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null; if (data['published_until'] != null)
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null; publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
replyingPost =
data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
repostingPost =
data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null; poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
realm = data['realm'] != null ? SnRealm.fromJson(data['realm']) : null; realm = data['realm'] != null ? SnRealm.fromJson(data['realm']) : null;
temporaryRestored = true; temporaryRestored = true;
@ -463,7 +501,9 @@ class PostWriteController extends ChangeNotifier {
media.name, media.name,
'interactive', 'interactive',
null, null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null, mimetype: media.raw != null && media.type == SnMediaType.image
? 'image/png'
: null,
); );
var item = await attach.chunkedUploadParts( var item = await attach.chunkedUploadParts(
@ -472,16 +512,20 @@ class PostWriteController extends ChangeNotifier {
place.$2, place.$2,
onProgress: (value) { onProgress: (value) {
// Calculate overall progress for attachments // Calculate overall progress for attachments
progress = math.max(((i + value) / attachments.length) * kAttachmentProgressWeight, value); progress = math.max(
((i + value) / attachments.length) * kAttachmentProgressWeight,
value);
notifyListeners(); notifyListeners();
}, },
); );
try { try {
if (context.mounted) { if (context.mounted) {
final compressedAttachment = await _tryCompressVideoCopy(context, media); final compressedAttachment =
await _tryCompressVideoCopy(context, media);
if (compressedAttachment != null) { if (compressedAttachment != null) {
item = await attach.updateOne(item, compressedId: compressedAttachment.id); item = await attach.updateOne(item,
compressedId: compressedAttachment.id);
} }
} }
} catch (err) { } catch (err) {
@ -518,16 +562,23 @@ class PostWriteController extends ChangeNotifier {
'content': contentController.text, 'content': contentController.text,
if (aliasController.text.isNotEmpty) 'alias': aliasController.text, if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
if (titleController.text.isNotEmpty) 'title': titleController.text, if (titleController.text.isNotEmpty) 'title': titleController.text,
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text, if (descriptionController.text.isNotEmpty)
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.rid, 'description': descriptionController.text,
'attachments': attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(), if (thumbnail != null && thumbnail!.attachment != null)
'thumbnail': thumbnail!.attachment!.rid,
'attachments': attachments
.where((e) => e.attachment != null)
.map((e) => e.attachment!.rid)
.toList(),
'tags': tags.map((ele) => {'alias': ele}).toList(), 'tags': tags.map((ele) => {'alias': ele}).toList(),
'categories': categories.map((ele) => {'alias': ele}).toList(), 'categories': categories.map((ele) => {'alias': ele}).toList(),
'visibility': visibility, 'visibility': visibility,
'visible_users_list': visibleUsers, 'visible_users_list': visibleUsers,
'invisible_users_list': invisibleUsers, 'invisible_users_list': invisibleUsers,
if (publishedAt != null) 'published_at': publishedAt!.toUtc().toIso8601String(), if (publishedAt != null)
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(), 'published_at': publishedAt!.toUtc().toIso8601String(),
if (publishedUntil != null)
'published_until': publishedAt!.toUtc().toIso8601String(),
if (replyingPost != null) 'reply_to': replyingPost!.id, if (replyingPost != null) 'reply_to': replyingPost!.id,
if (repostingPost != null) 'repost_to': repostingPost!.id, if (repostingPost != null) 'repost_to': repostingPost!.id,
if (reward != null) 'reward': reward, if (reward != null) 'reward': reward,
@ -536,11 +587,14 @@ class PostWriteController extends ChangeNotifier {
if (realm != null) 'realm': realm!.id, if (realm != null) 'realm': realm!.id,
}, },
onSendProgress: (count, total) { onSendProgress: (count, total) {
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2); progress =
baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
notifyListeners(); notifyListeners();
}, },
onReceiveProgress: (count, total) { onReceiveProgress: (count, total) {
progress = baseProgressVal + (kPostingProgressWeight / 2) + (count / total) * (kPostingProgressWeight / 2); progress = baseProgressVal +
(kPostingProgressWeight / 2) +
(count / total) * (kPostingProgressWeight / 2);
notifyListeners(); notifyListeners();
}, },
options: Options( options: Options(
@ -683,7 +737,8 @@ class PostWriteController extends ChangeNotifier {
repostingPost = null; repostingPost = null;
mode = kTitleMap.keys.first; mode = kTitleMap.keys.first;
temporaryRestored = false; temporaryRestored = false;
SharedPreferences.getInstance().then((prefs) => prefs.remove(kTemporaryStorageKey)); SharedPreferences.getInstance()
.then((prefs) => prefs.remove(kTemporaryStorageKey));
notifyListeners(); notifyListeners();
} }

View File

@ -66,10 +66,9 @@ final _appRoutes = [
builder: (context, state) => const ExploreScreen(), builder: (context, state) => const ExploreScreen(),
routes: [ routes: [
GoRoute( GoRoute(
path: '/write/:mode', path: '/write',
name: 'postEditor', name: 'postEditor',
builder: (context, state) => PostEditorScreen( builder: (context, state) => PostEditorScreen(
mode: state.pathParameters['mode']!,
postEditId: int.tryParse( postEditId: int.tryParse(
state.uri.queryParameters['editing'] ?? '', state.uri.queryParameters['editing'] ?? '',
), ),

View File

@ -204,7 +204,6 @@ class _ChatScreenState extends State<ChatScreen> {
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
closeButtonBuilder: DefaultFloatingActionButtonBuilder( closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28), child: const Icon(Symbols.close, size: 28),
@ -213,7 +212,6 @@ class _ChatScreenState extends State<ChatScreen> {
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
children: [ children: [
Row( Row(

View File

@ -111,7 +111,6 @@ class _ExploreScreenState extends State<ExploreScreen>
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
closeButtonBuilder: DefaultFloatingActionButtonBuilder( closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28), child: const Icon(Symbols.close, size: 28),
@ -120,90 +119,24 @@ class _ExploreScreenState extends State<ExploreScreen>
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
children: [ children: [
Row( Row(
children: [ children: [
Text('writePostTypeStory').tr(), Text('writePost').tr(),
const Gap(20), const Gap(20),
FloatingActionButton( FloatingActionButton(
heroTag: null, heroTag: null,
tooltip: 'writePostTypeStory'.tr(), tooltip: 'writePost'.tr(),
onPressed: () { onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: { GoRouter.of(context).pushNamed('postEditor').then((value) {
'mode': 'stories',
}).then((value) {
if (value == true) { if (value == true) {
refreshPosts(); refreshPosts();
} }
}); });
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
}, },
child: const Icon(Symbols.post_rounded), child: const Icon(Symbols.edit),
),
],
),
Row(
children: [
Text('writePostTypeArticle').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'writePostTypeArticle'.tr(),
onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: {
'mode': 'articles',
}).then((value) {
if (value == true) {
refreshPosts();
}
});
_fabKey.currentState!.toggle();
},
child: const Icon(Symbols.news),
),
],
),
Row(
children: [
Text('writePostTypeQuestion').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'writePostTypeQuestion'.tr(),
onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: {
'mode': 'questions',
}).then((value) {
if (value == true) {
refreshPosts();
}
});
_fabKey.currentState!.toggle();
},
child: const Icon(Symbols.question_answer),
),
],
),
Row(
children: [
Text('writePostTypeVideo').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'writePostTypeVideo'.tr(),
onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: {
'mode': 'videos',
}).then((value) {
if (value == true) {
refreshPosts();
}
});
_fabKey.currentState!.toggle();
},
child: const Icon(Symbols.video_call),
), ),
], ],
), ),

View File

@ -18,6 +18,7 @@ import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_realm.dart';
import 'package:surface/types/attachment.dart'; import 'package:surface/types/attachment.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/types/realm.dart'; import 'package:surface/types/realm.dart';
@ -36,7 +37,8 @@ import 'package:provider/provider.dart';
import 'package:surface/widgets/post/post_poll_editor.dart'; import 'package:surface/widgets/post/post_poll_editor.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../../providers/sn_realm.dart'; const kPostTypes = ['Story', 'Article', 'Question', 'Video'];
const kPostTypeAliases = ['stories', 'articles', 'questions', 'videos'];
class PostEditorExtra { class PostEditorExtra {
final String? text; final String? text;
@ -53,7 +55,6 @@ class PostEditorExtra {
} }
class PostEditorScreen extends StatefulWidget { class PostEditorScreen extends StatefulWidget {
final String mode;
final int? postEditId; final int? postEditId;
final int? postReplyId; final int? postReplyId;
final int? postRepostId; final int? postRepostId;
@ -61,7 +62,6 @@ class PostEditorScreen extends StatefulWidget {
const PostEditorScreen({ const PostEditorScreen({
super.key, super.key,
required this.mode,
required this.postEditId, required this.postEditId,
required this.postReplyId, required this.postReplyId,
required this.postRepostId, required this.postRepostId,
@ -72,7 +72,10 @@ class PostEditorScreen extends StatefulWidget {
State<PostEditorScreen> createState() => _PostEditorScreenState(); State<PostEditorScreen> createState() => _PostEditorScreenState();
} }
class _PostEditorScreenState extends State<PostEditorScreen> { class _PostEditorScreenState extends State<PostEditorScreen>
with SingleTickerProviderStateMixin {
late final TabController _tabController =
TabController(length: 4, vsync: this);
late final PostWriteController _writeController = PostWriteController( late final PostWriteController _writeController = PostWriteController(
doLoadFromTemporary: widget.postEditId == null, doLoadFromTemporary: widget.postEditId == null,
); );
@ -209,6 +212,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
@override @override
void dispose() { void dispose() {
_tabController.dispose();
_writeController.dispose(); _writeController.dispose();
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) { if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) {
hotKeyManager.unregister(_pasteHotKey); hotKeyManager.unregister(_pasteHotKey);
@ -220,14 +224,13 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
void initState() { void initState() {
super.initState(); super.initState();
_registerHotKey(); _registerHotKey();
if (!PostWriteController.kTitleMap.keys.contains(widget.mode)) {
context.showErrorDialog('Unknown post type');
Navigator.pop(context);
} else {
_writeController.setMode(widget.mode);
}
_fetchRealms(); _fetchRealms();
_fetchPublishers(); _fetchPublishers();
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
_writeController.setMode(kPostTypeAliases[_tabController.index]);
}
});
_writeController.fetchRelatedPost( _writeController.fetchRelatedPost(
context, context,
editing: widget.postEditId, editing: widget.postEditId,
@ -255,26 +258,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
title: RichText( title: Text(
textAlign: TextAlign.center, _writeController.title.isNotEmpty
text: TextSpan(children: [ ? _writeController.title
TextSpan( : 'untitled'.tr(),
text: _writeController.title.isNotEmpty
? _writeController.title
: 'untitled'.tr(),
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
const TextSpan(text: '\n'),
TextSpan(
text: PostWriteController.kTitleMap[widget.mode]!.tr(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
]),
maxLines: 2,
), ),
actions: [ actions: [
IconButton( IconButton(
@ -283,6 +270,24 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
), ),
const Gap(8), const Gap(8),
], ],
bottom: _writeController.isNotEmpty
? null
: TabBar(
controller: _tabController,
tabs: [
for (final type in kPostTypes)
Tab(
child: Text(
'postType$type'.tr(),
style: TextStyle(
color: Theme.of(context)
.appBarTheme
.foregroundColor!,
),
),
),
],
),
), ),
body: Column( body: Column(
children: [ children: [
@ -374,7 +379,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
SingleChildScrollView( SingleChildScrollView(
padding: EdgeInsets.only(bottom: 160), padding: EdgeInsets.only(bottom: 160),
child: StyledWidget(switch (_writeController.mode) { child: switch (_writeController.mode) {
'stories' => _PostStoryEditor( 'stories' => _PostStoryEditor(
controller: _writeController, controller: _writeController,
onTapPublisher: _showPublisherPopup, onTapPublisher: _showPublisherPopup,
@ -396,8 +401,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
onTapRealm: _showRealmPopup, onTapRealm: _showRealmPopup,
), ),
_ => const Placeholder(), _ => const Placeholder(),
}) },
.padding(top: 8),
), ),
if (_writeController.attachments.isNotEmpty || if (_writeController.attachments.isNotEmpty ||
_writeController.thumbnail != null) _writeController.thumbnail != null)
@ -720,7 +724,7 @@ class _PostStoryEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 8),
constraints: const BoxConstraints(maxWidth: 640), constraints: const BoxConstraints(maxWidth: 640),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -969,7 +973,7 @@ class _PostQuestionEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 8),
constraints: const BoxConstraints(maxWidth: 640), constraints: const BoxConstraints(maxWidth: 640),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -1053,7 +1057,7 @@ class _PostQuestionEditor extends StatelessWidget {
), ),
), ),
], ],
).padding(top: 8), ),
); );
} }
} }
@ -1154,7 +1158,7 @@ class _PostVideoEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 8),
constraints: const BoxConstraints(maxWidth: 640), constraints: const BoxConstraints(maxWidth: 640),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,