2024-07-30 08:29:30 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:get/get.dart';
|
|
|
|
import 'package:solian/models/post.dart';
|
|
|
|
import 'package:solian/models/realm.dart';
|
2024-07-30 12:49:01 +00:00
|
|
|
import 'package:solian/widgets/attachments/attachment_editor.dart';
|
2024-07-30 18:00:03 +00:00
|
|
|
import 'package:solian/widgets/posts/editor/post_editor_categories_tags.dart';
|
2024-08-01 07:49:42 +00:00
|
|
|
import 'package:solian/widgets/posts/editor/post_editor_date.dart';
|
2024-07-30 06:49:26 +00:00
|
|
|
import 'package:solian/widgets/posts/editor/post_editor_overview.dart';
|
2024-08-01 07:21:43 +00:00
|
|
|
import 'package:solian/widgets/posts/editor/post_editor_publish_zone.dart';
|
2024-08-10 17:57:58 +00:00
|
|
|
import 'package:solian/widgets/posts/editor/post_editor_thumbnail.dart';
|
2024-07-30 12:49:01 +00:00
|
|
|
import 'package:solian/widgets/posts/editor/post_editor_visibility.dart';
|
2024-07-30 08:29:30 +00:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2024-07-30 06:49:26 +00:00
|
|
|
|
|
|
|
class PostEditorController extends GetxController {
|
2024-07-30 08:29:30 +00:00
|
|
|
late final SharedPreferences _prefs;
|
|
|
|
|
2024-08-17 10:44:20 +00:00
|
|
|
final aliasController = TextEditingController();
|
2024-07-30 06:49:26 +00:00
|
|
|
final titleController = TextEditingController();
|
|
|
|
final descriptionController = TextEditingController();
|
|
|
|
final contentController = TextEditingController();
|
|
|
|
|
|
|
|
RxInt mode = 0.obs;
|
|
|
|
RxInt contentLength = 0.obs;
|
|
|
|
|
|
|
|
Rx<Post?> editTo = Rx(null);
|
|
|
|
Rx<Post?> replyTo = Rx(null);
|
|
|
|
Rx<Post?> repostTo = Rx(null);
|
|
|
|
Rx<Realm?> realmZone = Rx(null);
|
2024-08-01 07:49:42 +00:00
|
|
|
Rx<DateTime?> publishedAt = Rx(null);
|
|
|
|
Rx<DateTime?> publishedUntil = Rx(null);
|
2024-08-18 14:51:52 +00:00
|
|
|
RxList<String> attachments = RxList<String>.empty(growable: true);
|
2024-07-30 18:00:03 +00:00
|
|
|
RxList<String> tags = RxList<String>.empty(growable: true);
|
2024-08-18 14:51:52 +00:00
|
|
|
Rx<String?> thumbnail = Rx(null);
|
2024-07-30 06:49:26 +00:00
|
|
|
|
2024-07-30 12:49:01 +00:00
|
|
|
RxList<int> visibleUsers = RxList.empty(growable: true);
|
|
|
|
RxList<int> invisibleUsers = RxList.empty(growable: true);
|
|
|
|
|
|
|
|
RxInt visibility = 0.obs;
|
2024-07-30 06:49:26 +00:00
|
|
|
RxBool isDraft = false.obs;
|
|
|
|
|
2024-07-30 08:29:30 +00:00
|
|
|
RxBool isRestoreFromLocal = false.obs;
|
|
|
|
Rx<DateTime?> lastSaveTime = Rx(null);
|
2024-10-14 15:08:53 +00:00
|
|
|
Future? _saveFuture;
|
2024-07-30 08:29:30 +00:00
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
PostEditorController() {
|
2024-07-30 08:29:30 +00:00
|
|
|
SharedPreferences.getInstance().then((inst) {
|
|
|
|
_prefs = inst;
|
2024-10-14 15:08:53 +00:00
|
|
|
});
|
|
|
|
contentController.addListener(() {
|
|
|
|
contentLength.value = contentController.text.length;
|
|
|
|
_saveFuture ??= Future.delayed(
|
|
|
|
const Duration(seconds: 1),
|
|
|
|
() {
|
2024-07-30 08:29:30 +00:00
|
|
|
if (isNotEmpty) {
|
|
|
|
localSave();
|
|
|
|
lastSaveTime.value = DateTime.now();
|
|
|
|
lastSaveTime.refresh();
|
|
|
|
} else if (_prefs.containsKey('post_editor_local_save')) {
|
|
|
|
localClear();
|
|
|
|
lastSaveTime.value = null;
|
|
|
|
}
|
2024-10-14 15:08:53 +00:00
|
|
|
_saveFuture = null;
|
2024-07-30 08:29:30 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2024-07-30 06:49:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> editOverview(BuildContext context) {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => PostEditorOverviewDialog(
|
|
|
|
controller: this,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-07-30 12:49:01 +00:00
|
|
|
Future<void> editVisibility(BuildContext context) {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => PostEditorVisibilityDialog(
|
|
|
|
controller: this,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-07-30 18:00:03 +00:00
|
|
|
Future<void> editCategoriesAndTags(BuildContext context) {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => PostEditorCategoriesDialog(
|
|
|
|
controller: this,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-08-01 07:21:43 +00:00
|
|
|
Future<void> editPublishZone(BuildContext context) {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => PostEditorPublishZoneDialog(
|
|
|
|
controller: this,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-08-01 07:49:42 +00:00
|
|
|
Future<void> editPublishDate(BuildContext context) {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => PostEditorDateDialog(
|
|
|
|
controller: this,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
Future<void> editAttachment(BuildContext context) {
|
|
|
|
return showModalBottomSheet(
|
|
|
|
context: context,
|
2024-07-30 12:49:01 +00:00
|
|
|
builder: (context) => AttachmentEditorPopup(
|
2024-08-18 14:51:52 +00:00
|
|
|
pool: 'interactive',
|
2024-08-01 14:13:08 +00:00
|
|
|
initialAttachments: attachments,
|
2024-08-18 14:51:52 +00:00
|
|
|
onAdd: (String value) {
|
2024-08-01 14:13:08 +00:00
|
|
|
attachments.add(value);
|
|
|
|
},
|
2024-08-18 14:51:52 +00:00
|
|
|
onRemove: (String value) {
|
2024-08-01 14:13:08 +00:00
|
|
|
attachments.remove(value);
|
2024-07-30 06:49:26 +00:00
|
|
|
},
|
2024-10-19 08:55:14 +00:00
|
|
|
onInsert: (String str) {
|
|
|
|
final text = contentController.text;
|
|
|
|
final selection = contentController.selection;
|
|
|
|
final newText = text.replaceRange(
|
|
|
|
selection.start,
|
|
|
|
selection.end,
|
|
|
|
str,
|
|
|
|
);
|
|
|
|
contentController.value = TextEditingValue(
|
|
|
|
text: newText,
|
|
|
|
selection: TextSelection.collapsed(
|
|
|
|
offset: selection.baseOffset + str.length,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
2024-07-30 06:49:26 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-08-10 17:57:58 +00:00
|
|
|
Future<void> editThumbnail(BuildContext context) {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => PostEditorThumbnailDialog(
|
|
|
|
controller: this,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
void toggleDraftMode() {
|
|
|
|
isDraft.value = !isDraft.value;
|
|
|
|
}
|
|
|
|
|
2024-07-30 08:29:30 +00:00
|
|
|
void localSave() {
|
|
|
|
_prefs.setString(
|
|
|
|
'post_editor_local_save',
|
|
|
|
jsonEncode({
|
|
|
|
...payload,
|
|
|
|
'reply_to': replyTo.value?.toJson(),
|
|
|
|
'repost_to': repostTo.value?.toJson(),
|
|
|
|
'edit_to': editTo.value?.toJson(),
|
|
|
|
'realm': realmZone.value?.toJson(),
|
2024-07-30 08:44:04 +00:00
|
|
|
'type': type,
|
2024-07-30 08:29:30 +00:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-09-13 12:22:10 +00:00
|
|
|
Future<bool> localRead() async {
|
|
|
|
final inst = await SharedPreferences.getInstance();
|
|
|
|
if (inst.containsKey('post_editor_local_save')) {
|
|
|
|
isRestoreFromLocal.value = true;
|
|
|
|
payload = jsonDecode(inst.getString('post_editor_local_save')!);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2024-07-30 08:29:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void localClear() {
|
|
|
|
_prefs.remove('post_editor_local_save');
|
|
|
|
}
|
|
|
|
|
|
|
|
void currentClear() {
|
2024-08-18 14:51:52 +00:00
|
|
|
aliasController.clear();
|
2024-07-30 08:29:30 +00:00
|
|
|
titleController.clear();
|
|
|
|
descriptionController.clear();
|
|
|
|
contentController.clear();
|
|
|
|
attachments.clear();
|
2024-07-30 18:00:03 +00:00
|
|
|
tags.clear();
|
2024-07-30 12:49:01 +00:00
|
|
|
visibleUsers.clear();
|
|
|
|
invisibleUsers.clear();
|
|
|
|
visibility.value = 0;
|
2024-08-13 02:54:42 +00:00
|
|
|
thumbnail.value = null;
|
2024-08-01 14:13:08 +00:00
|
|
|
publishedAt.value = null;
|
|
|
|
publishedUntil.value = null;
|
2024-07-30 08:29:30 +00:00
|
|
|
isDraft.value = false;
|
|
|
|
isRestoreFromLocal.value = false;
|
|
|
|
lastSaveTime.value = null;
|
|
|
|
contentLength.value = 0;
|
|
|
|
editTo.value = null;
|
|
|
|
replyTo.value = null;
|
|
|
|
repostTo.value = null;
|
|
|
|
realmZone.value = null;
|
|
|
|
}
|
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
set editTarget(Post? value) {
|
|
|
|
if (value == null) {
|
|
|
|
editTo.value = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-30 08:44:04 +00:00
|
|
|
type = value.type;
|
2024-07-30 06:49:26 +00:00
|
|
|
editTo.value = value;
|
2024-08-17 10:44:20 +00:00
|
|
|
realmZone.value = value.realm;
|
2024-07-30 06:49:26 +00:00
|
|
|
isDraft.value = value.isDraft ?? false;
|
2024-08-17 10:44:20 +00:00
|
|
|
aliasController.text = value.alias ?? '';
|
2024-07-30 06:49:26 +00:00
|
|
|
titleController.text = value.body['title'] ?? '';
|
|
|
|
descriptionController.text = value.body['description'] ?? '';
|
|
|
|
contentController.text = value.body['content'] ?? '';
|
2024-08-01 07:49:42 +00:00
|
|
|
publishedAt.value = value.publishedAt;
|
|
|
|
publishedUntil.value = value.publishedUntil;
|
2024-08-17 10:44:20 +00:00
|
|
|
tags.value = List.from(
|
|
|
|
value.body['tags']?.map((x) => x['alias']).toList() ?? List.empty(),
|
|
|
|
growable: true,
|
|
|
|
);
|
2024-08-01 03:49:28 +00:00
|
|
|
tags.refresh();
|
2024-08-17 10:44:20 +00:00
|
|
|
attachments.value = List.from(
|
|
|
|
value.body['attachments'] ?? List.empty(),
|
|
|
|
growable: true,
|
|
|
|
);
|
2024-07-30 06:49:26 +00:00
|
|
|
attachments.refresh();
|
2024-08-10 18:07:09 +00:00
|
|
|
thumbnail.value = value.body['thumbnail'];
|
2024-07-30 06:49:26 +00:00
|
|
|
|
|
|
|
contentLength.value = contentController.text.length;
|
|
|
|
}
|
|
|
|
|
2024-07-30 08:44:04 +00:00
|
|
|
String get typeEndpoint {
|
2024-07-30 12:49:01 +00:00
|
|
|
switch (mode.value) {
|
2024-07-30 08:44:04 +00:00
|
|
|
case 0:
|
|
|
|
return 'stories';
|
|
|
|
case 1:
|
|
|
|
return 'articles';
|
|
|
|
default:
|
|
|
|
return 'stories';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String get type {
|
2024-07-30 12:49:01 +00:00
|
|
|
switch (mode.value) {
|
2024-07-30 08:44:04 +00:00
|
|
|
case 0:
|
|
|
|
return 'story';
|
|
|
|
case 1:
|
|
|
|
return 'article';
|
|
|
|
default:
|
|
|
|
return 'story';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set type(String value) {
|
|
|
|
switch (value) {
|
|
|
|
case 'story':
|
|
|
|
mode.value = 0;
|
|
|
|
case 'article':
|
|
|
|
mode.value = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
String? get title {
|
|
|
|
if (titleController.text.isEmpty) return null;
|
|
|
|
return titleController.text;
|
|
|
|
}
|
|
|
|
|
|
|
|
String? get description {
|
|
|
|
if (descriptionController.text.isEmpty) return null;
|
|
|
|
return descriptionController.text;
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, dynamic> get payload {
|
|
|
|
return {
|
2024-08-17 10:44:20 +00:00
|
|
|
'alias': aliasController.text,
|
2024-07-30 06:49:26 +00:00
|
|
|
'title': title,
|
|
|
|
'description': description,
|
|
|
|
'content': contentController.text,
|
2024-08-10 17:57:58 +00:00
|
|
|
'thumbnail': thumbnail.value,
|
2024-08-01 03:49:28 +00:00
|
|
|
'tags': tags.map((x) => {'alias': x}).toList(),
|
2024-07-30 06:49:26 +00:00
|
|
|
'attachments': attachments,
|
2024-07-30 12:49:01 +00:00
|
|
|
'visible_users': visibleUsers,
|
|
|
|
'invisible_users': invisibleUsers,
|
|
|
|
'visibility': visibility.value,
|
2024-08-01 07:49:42 +00:00
|
|
|
'published_at': publishedAt.value?.toUtc().toIso8601String() ??
|
|
|
|
DateTime.now().toUtc().toIso8601String(),
|
|
|
|
'published_until': publishedUntil.value?.toUtc().toIso8601String(),
|
2024-07-30 06:49:26 +00:00
|
|
|
'is_draft': isDraft.value,
|
|
|
|
if (replyTo.value != null) 'reply_to': replyTo.value!.id,
|
|
|
|
if (repostTo.value != null) 'repost_to': repostTo.value!.id,
|
|
|
|
if (realmZone.value != null) 'realm': realmZone.value!.alias,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-07-30 08:29:30 +00:00
|
|
|
set payload(Map<String, dynamic> value) {
|
2024-07-30 08:44:04 +00:00
|
|
|
type = value['type'];
|
2024-08-17 10:44:20 +00:00
|
|
|
tags.value = List.from(
|
|
|
|
value['tags'].map((x) => x['alias']).toList(),
|
|
|
|
growable: true,
|
|
|
|
);
|
|
|
|
aliasController.text = value['alias'] ?? '';
|
2024-07-30 08:29:30 +00:00
|
|
|
titleController.text = value['title'] ?? '';
|
|
|
|
descriptionController.text = value['description'] ?? '';
|
|
|
|
contentController.text = value['content'] ?? '';
|
2024-08-17 10:44:20 +00:00
|
|
|
attachments.value = List.from(
|
|
|
|
value['attachments'] ?? List.empty(),
|
|
|
|
growable: true,
|
|
|
|
);
|
2024-07-30 08:29:30 +00:00
|
|
|
attachments.refresh();
|
2024-08-10 17:57:58 +00:00
|
|
|
thumbnail.value = value['thumbnail'];
|
2024-07-30 12:49:01 +00:00
|
|
|
visibility.value = value['visibility'];
|
2024-07-30 08:29:30 +00:00
|
|
|
isDraft.value = value['is_draft'];
|
2024-07-30 12:49:01 +00:00
|
|
|
if (value['visible_users'] != null) {
|
2024-08-17 10:44:20 +00:00
|
|
|
visibleUsers.value = List.from(
|
|
|
|
value['visible_users'],
|
|
|
|
growable: true,
|
|
|
|
);
|
2024-07-30 12:49:01 +00:00
|
|
|
}
|
|
|
|
if (value['invisible_users'] != null) {
|
2024-08-17 10:44:20 +00:00
|
|
|
invisibleUsers.value = List.from(
|
|
|
|
value['invisible_users'],
|
|
|
|
growable: true,
|
|
|
|
);
|
2024-07-30 12:49:01 +00:00
|
|
|
}
|
2024-08-01 07:49:42 +00:00
|
|
|
if (value['published_at'] != null) {
|
|
|
|
publishedAt.value = DateTime.parse(value['published_at']).toLocal();
|
|
|
|
}
|
|
|
|
if (value['published_until'] != null) {
|
|
|
|
publishedAt.value = DateTime.parse(value['published_until']).toLocal();
|
|
|
|
}
|
2024-07-30 08:29:30 +00:00
|
|
|
if (value['reply_to'] != null) {
|
|
|
|
replyTo.value = Post.fromJson(value['reply_to']);
|
|
|
|
}
|
|
|
|
if (value['repost_to'] != null) {
|
|
|
|
repostTo.value = Post.fromJson(value['repost_to']);
|
|
|
|
}
|
|
|
|
if (value['edit_to'] != null) {
|
|
|
|
editTo.value = Post.fromJson(value['edit_to']);
|
|
|
|
}
|
|
|
|
if (value['realm'] != null) {
|
|
|
|
realmZone.value = Realm.fromJson(value['realm']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
bool get isEmpty {
|
|
|
|
if (contentController.text.isEmpty) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-30 08:29:30 +00:00
|
|
|
bool get isNotEmpty {
|
|
|
|
return [
|
2024-08-17 10:44:20 +00:00
|
|
|
aliasController.text.isNotEmpty,
|
2024-07-30 08:29:30 +00:00
|
|
|
titleController.text.isNotEmpty,
|
|
|
|
descriptionController.text.isNotEmpty,
|
|
|
|
contentController.text.isNotEmpty,
|
|
|
|
attachments.isNotEmpty,
|
2024-08-10 17:57:58 +00:00
|
|
|
tags.isNotEmpty,
|
|
|
|
thumbnail.value != null,
|
2024-07-30 08:29:30 +00:00
|
|
|
].any((x) => x);
|
|
|
|
}
|
|
|
|
|
2024-07-30 06:49:26 +00:00
|
|
|
@override
|
|
|
|
void dispose() {
|
2024-07-30 18:00:03 +00:00
|
|
|
titleController.dispose();
|
|
|
|
descriptionController.dispose();
|
2024-07-30 06:49:26 +00:00
|
|
|
contentController.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
}
|