Post editor on device draft

This commit is contained in:
LittleSheep 2024-12-29 22:27:07 +08:00
parent 7ba5260246
commit 5c7929e618
7 changed files with 140 additions and 9 deletions

View File

@ -467,6 +467,7 @@
"accountStatusLastSeen": "Last seen at {}",
"postArticle": "Article on the Solar Network",
"postStory": "Story on the Solar Network",
"postLocalDraftRestored": "Restored from device",
"articleWrittenAt": "Written at {}",
"articleEditedAt": "Edited at {}",
"attachmentSaved": "Saved to album",

View File

@ -465,6 +465,7 @@
"accountStatusLastSeen": "最后一次上线于 {}",
"postArticle": "Solar Network 上的文章",
"postStory": "Solar Network 上的故事",
"postLocalDraftRestored": "从本地恢复草稿",
"articleWrittenAt": "发表于 {}",
"articleEditedAt": "编辑于 {}",
"attachmentSaved": "已保存到相册",

View File

@ -456,6 +456,7 @@
"accountJoinedAt": "加入於 {}",
"accountBirthday": "出生於 {}",
"accountBadge": "徽章",
"accountCheckInNoRecords": "暫無運勢記錄",
"badgeCompanyStaff": "索爾辛茨士大夫 · 員工",
"badgeSiteMigration": "Solar Network 原住民",
"accountStatus": "狀態",
@ -464,6 +465,7 @@
"accountStatusLastSeen": "最後一次上線於 {}",
"postArticle": "Solar Network 上的文章",
"postStory": "Solar Network 上的故事",
"postLocalDraftRestored": "從本地恢復草稿",
"articleWrittenAt": "發表於 {}",
"articleEditedAt": "編輯於 {}",
"attachmentSaved": "已保存到相冊",

View File

@ -456,6 +456,7 @@
"accountJoinedAt": "加入於 {}",
"accountBirthday": "出生於 {}",
"accountBadge": "徽章",
"accountCheckInNoRecords": "暫無運勢記錄",
"badgeCompanyStaff": "索爾辛茨士大夫 · 員工",
"badgeSiteMigration": "Solar Network 原住民",
"accountStatus": "狀態",
@ -464,6 +465,7 @@
"accountStatusLastSeen": "最後一次上線於 {}",
"postArticle": "Solar Network 上的文章",
"postStory": "Solar Network 上的故事",
"postLocalDraftRestored": "從本地恢復草稿",
"articleWrittenAt": "發表於 {}",
"articleEditedAt": "編輯於 {}",
"attachmentSaved": "已保存到相冊",

View File

@ -211,9 +211,6 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter
@ -259,7 +256,6 @@ DEPENDENCIES:
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_compress (from `.symlinks/plugins/video_compress/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
@ -347,8 +343,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_compress:
@ -407,7 +401,6 @@ SPEC CHECKSUMS:
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe

View File

@ -1,3 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:math' as math;
@ -8,6 +11,7 @@ import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart';
@ -151,8 +155,18 @@ class PostWriteController extends ChangeNotifier {
final TextEditingController aliasController = TextEditingController();
PostWriteController() {
titleController.addListener(() => notifyListeners());
descriptionController.addListener(() => notifyListeners());
titleController.addListener(() {
_temporaryPlanSave();
notifyListeners();
});
descriptionController.addListener(() {
_temporaryPlanSave();
notifyListeners();
});
contentController.addListener(() {
_temporaryPlanSave();
});
_temporaryLoad();
}
String mode = kTitleMap.keys.first;
@ -298,6 +312,81 @@ class PostWriteController extends ChangeNotifier {
return compressedAttachment;
}
static const kTemporaryStorageKey = 'int_draft_post';
Timer? _temporarySaveTimer;
void _temporaryPlanSave() {
_temporarySaveTimer?.cancel();
_temporarySaveTimer = Timer(const Duration(seconds: 1), () {
_temporarySave();
log("[PostWriter] Temporary save saved.");
});
}
void _temporarySave() {
SharedPreferences.getInstance().then((prefs) {
if (titleController.text.isEmpty &&
descriptionController.text.isEmpty &&
contentController.text.isEmpty &&
thumbnail == null &&
attachments.isEmpty) {
prefs.remove(kTemporaryStorageKey);
return;
}
prefs.setString(
kTemporaryStorageKey,
jsonEncode({
'publisher': publisher,
'content': contentController.text,
if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
if (titleController.text.isNotEmpty) 'title': titleController.text,
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text,
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(),
'attachments': attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(),
'tags': tags.map((ele) => {'alias': ele}).toList(),
'categories': categories.map((ele) => {'alias': ele}).toList(),
'visibility': visibility,
'visible_users_list': visibleUsers,
'invisible_users_list': invisibleUsers,
if (publishedAt != null) 'published_at': publishedAt!.toUtc().toIso8601String(),
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(),
if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
}),
);
});
}
bool temporaryRestored = false;
void _temporaryLoad() {
SharedPreferences.getInstance().then((prefs) {
final raw = prefs.getString(kTemporaryStorageKey);
if (raw == null) return;
final data = jsonDecode(raw);
contentController.text = data['content'];
aliasController.text = data['alias'] ?? '';
titleController.text = data['title'] ?? '';
descriptionController.text = data['description'] ?? '';
if (data['thumbnail'] != null) thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
attachments
.addAll(data['attachments'].map((ele) => PostWriteMedia(SnAttachment.fromJson(ele))).cast<PostWriteMedia>());
tags = List.from(data['tags'].map((ele) => ele['alias']));
categories = List.from(data['categories'].map((ele) => ele['alias']));
visibility = data['visibility'];
visibleUsers = List.from(data['visible_users_list'] ?? []);
invisibleUsers = List.from(data['invisible_users_list'] ?? []);
if (data['published_at'] != null) publishedAt = DateTime.tryParse(data['published_at'])?.toLocal();
if (data['published_until'] != 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;
temporaryRestored = true;
notifyListeners();
});
}
Future<void> uploadSingleAttachment(BuildContext context, int idx) async {
if (isBusy) return;
@ -417,6 +506,7 @@ class PostWriteController extends ChangeNotifier {
method: editingPost != null ? 'PUT' : 'POST',
),
);
reset();
} catch (err) {
if (!context.mounted) return;
context.showErrorDialog(err);
@ -465,56 +555,67 @@ class PostWriteController extends ChangeNotifier {
void setPublisher(SnPublisher? item) {
publisher = item;
_temporaryPlanSave();
notifyListeners();
}
void setPublishedAt(DateTime? value) {
publishedAt = value;
_temporaryPlanSave();
notifyListeners();
}
void setPublishedUntil(DateTime? value) {
publishedUntil = value;
_temporaryPlanSave();
notifyListeners();
}
void setTags(List<String> value) {
tags = value;
_temporaryPlanSave();
notifyListeners();
}
void setCategories(List<String> value) {
categories = value;
_temporaryPlanSave();
notifyListeners();
}
void setVisibility(int value) {
visibility = value;
_temporaryPlanSave();
notifyListeners();
}
void setVisibleUsers(List<int> value) {
visibleUsers = value;
_temporaryPlanSave();
notifyListeners();
}
void setInvisibleUsers(List<int> value) {
invisibleUsers = value;
_temporaryPlanSave();
notifyListeners();
}
void setProgress(double? value) {
progress = value;
_temporaryPlanSave();
notifyListeners();
}
void setIsBusy(bool value) {
isBusy = value;
_temporaryPlanSave();
notifyListeners();
}
void setMode(String value) {
mode = value;
_temporaryPlanSave();
notifyListeners();
}
@ -532,6 +633,8 @@ class PostWriteController extends ChangeNotifier {
replyingPost = null;
repostingPost = null;
mode = kTitleMap.keys.first;
temporaryRestored = false;
SharedPreferences.getInstance().then((prefs) => prefs.remove(kTemporaryStorageKey));
notifyListeners();
}

View File

@ -364,6 +364,35 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 28, right: 22),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / MediaQuery.of(context).devicePixelRatio,
),
),
),
child: _writeController.temporaryRestored
? Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Icons.restore, size: 20),
const Gap(8),
Expanded(child: Text('postLocalDraftRestored').tr()),
InkWell(
child: Text('dialogDismiss').tr(),
onTap: () {
_writeController.reset();
},
),
],
)
: const SizedBox.shrink(),
)
.height(_writeController.temporaryRestored ? 32 : 0, animate: true)
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
LoadingIndicator(isActive: _isLoading),
if (_writeController.isBusy && _writeController.progress != null)
TweenAnimationBuilder<double>(