♻️ Refactored post draft system
This commit is contained in:
parent
b89cffeb18
commit
47c31ddec2
@ -410,6 +410,8 @@
|
|||||||
"articleDrafts": "Article drafts",
|
"articleDrafts": "Article drafts",
|
||||||
"postDrafts": "Post drafts",
|
"postDrafts": "Post drafts",
|
||||||
"saveDraft": "Save draft",
|
"saveDraft": "Save draft",
|
||||||
|
"draftSaved": "Draft saved",
|
||||||
|
"draftSaveFailed": "Failed to save draft",
|
||||||
"clearAllDrafts": "Clear All Drafts",
|
"clearAllDrafts": "Clear All Drafts",
|
||||||
"clearAllDraftsConfirm": "Are you sure you want to delete all drafts? This action cannot be undone.",
|
"clearAllDraftsConfirm": "Are you sure you want to delete all drafts? This action cannot be undone.",
|
||||||
"clearAll": "Clear All",
|
"clearAll": "Clear All",
|
||||||
@ -441,6 +443,7 @@
|
|||||||
"contactMethodDelete": "Delete Contact",
|
"contactMethodDelete": "Delete Contact",
|
||||||
"contactMethodNew": "New Contact Method",
|
"contactMethodNew": "New Contact Method",
|
||||||
"contactMethodContentEmpty": "Contact content cannot be empty",
|
"contactMethodContentEmpty": "Contact content cannot be empty",
|
||||||
|
"postContentEmpty": "Post content cannot be empty",
|
||||||
"contactMethodVerificationSent": "Verification code sent to your contact method",
|
"contactMethodVerificationSent": "Verification code sent to your contact method",
|
||||||
"contactMethodVerificationNeeded": "The contact method is added, but not verified yet. You can verify it by tapping it and select verify.",
|
"contactMethodVerificationNeeded": "The contact method is added, but not verified yet. You can verify it by tapping it and select verify.",
|
||||||
"accountContactMethod": "Contact Methods",
|
"accountContactMethod": "Contact Methods",
|
||||||
|
@ -319,5 +319,21 @@
|
|||||||
"processingPayment": "处理付款中...",
|
"processingPayment": "处理付款中...",
|
||||||
"pleaseWait": "请稍候",
|
"pleaseWait": "请稍候",
|
||||||
"paymentFailed": "付款失败,请重试。",
|
"paymentFailed": "付款失败,请重试。",
|
||||||
"paymentSuccess": "付款成功完成!"
|
"paymentSuccess": "付款成功完成!",
|
||||||
|
"drafts": "草稿",
|
||||||
|
"noDrafts": "暂无草稿",
|
||||||
|
"articleDrafts": "文章草稿",
|
||||||
|
"postDrafts": "帖子草稿",
|
||||||
|
"saveDraft": "保存草稿",
|
||||||
|
"draftSaved": "草稿已保存",
|
||||||
|
"draftSaveFailed": "保存草稿失败",
|
||||||
|
"clearAllDrafts": "清空所有草稿",
|
||||||
|
"clearAllDraftsConfirm": "确定要删除所有草稿吗?此操作无法撤销。",
|
||||||
|
"clearAll": "清空全部",
|
||||||
|
"untitled": "无标题",
|
||||||
|
"noContent": "无内容",
|
||||||
|
"justNow": "刚刚",
|
||||||
|
"minutesAgo": "{} 分钟前",
|
||||||
|
"hoursAgo": "{} 小时前",
|
||||||
|
"postContentEmpty": "帖子内容不能为空"
|
||||||
}
|
}
|
@ -334,5 +334,21 @@
|
|||||||
"membershipFeatureAllNova": "所有新星功能",
|
"membershipFeatureAllNova": "所有新星功能",
|
||||||
"membershipFeatureExclusiveContent": "獨家內容",
|
"membershipFeatureExclusiveContent": "獨家內容",
|
||||||
"membershipFeatureVipSupport": "VIP 支援",
|
"membershipFeatureVipSupport": "VIP 支援",
|
||||||
"membershipCurrentBadge": "目前"
|
"membershipCurrentBadge": "目前",
|
||||||
|
"drafts": "草稿",
|
||||||
|
"noDrafts": "暫無草稿",
|
||||||
|
"articleDrafts": "文章草稿",
|
||||||
|
"postDrafts": "貼文草稿",
|
||||||
|
"saveDraft": "儲存草稿",
|
||||||
|
"draftSaved": "草稿已儲存",
|
||||||
|
"draftSaveFailed": "儲存草稿失敗",
|
||||||
|
"clearAllDrafts": "清空所有草稿",
|
||||||
|
"clearAllDraftsConfirm": "確定要刪除所有草稿嗎?此操作無法復原。",
|
||||||
|
"clearAll": "清空全部",
|
||||||
|
"untitled": "無標題",
|
||||||
|
"noContent": "無內容",
|
||||||
|
"justNow": "剛剛",
|
||||||
|
"minutesAgo": "{} 分鐘前",
|
||||||
|
"hoursAgo": "{} 小時前",
|
||||||
|
"postContentEmpty": "貼文內容不能為空"
|
||||||
}
|
}
|
@ -1,24 +1,8 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
class ComposeDrafts extends Table {
|
class PostDrafts extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get title => text().withDefault(const Constant(''))();
|
TextColumn get post => text()(); // Store SnPost model as JSON string
|
||||||
TextColumn get description => text().withDefault(const Constant(''))();
|
|
||||||
TextColumn get content => text().withDefault(const Constant(''))();
|
|
||||||
TextColumn get attachmentIds => text().withDefault(const Constant('[]'))(); // JSON array as string
|
|
||||||
IntColumn get visibility => integer().withDefault(const Constant(0))(); // 0=public, 1=unlisted, 2=friends, 3=selected, 4=private
|
|
||||||
DateTimeColumn get lastModified => dateTime()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {id};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArticleDrafts extends Table {
|
|
||||||
TextColumn get id => text()();
|
|
||||||
TextColumn get title => text().withDefault(const Constant(''))();
|
|
||||||
TextColumn get description => text().withDefault(const Constant(''))();
|
|
||||||
TextColumn get content => text().withDefault(const Constant(''))();
|
|
||||||
IntColumn get visibility => integer().withDefault(const Constant(0))(); // 0=public, 1=unlisted, 2=friends, 3=private
|
|
||||||
DateTimeColumn get lastModified => dateTime()();
|
DateTimeColumn get lastModified => dateTime()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2,16 +2,17 @@ import 'dart:convert';
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:island/database/message.dart';
|
import 'package:island/database/message.dart';
|
||||||
import 'package:island/database/draft.dart';
|
import 'package:island/database/draft.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
|
||||||
part 'drift_db.g.dart';
|
part 'drift_db.g.dart';
|
||||||
|
|
||||||
// Define the database
|
// Define the database
|
||||||
@DriftDatabase(tables: [ChatMessages, ComposeDrafts, ArticleDrafts])
|
@DriftDatabase(tables: [ChatMessages, PostDrafts])
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
AppDatabase(super.e);
|
AppDatabase(super.e);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 3;
|
int get schemaVersion => 4;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@ -23,10 +24,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
// Add isRead column with default value false
|
// Add isRead column with default value false
|
||||||
await m.addColumn(chatMessages, chatMessages.isRead);
|
await m.addColumn(chatMessages, chatMessages.isRead);
|
||||||
}
|
}
|
||||||
if (from < 3) {
|
if (from < 4) {
|
||||||
// Add draft tables
|
// Drop old draft tables if they exist
|
||||||
await m.createTable(composeDrafts);
|
await m.createTable(postDrafts);
|
||||||
await m.createTable(articleDrafts);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -98,51 +98,23 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods for compose drafts
|
// Methods for post drafts
|
||||||
Future<List<ComposeDraft>> getAllComposeDrafts() {
|
Future<List<SnPost>> getAllPostDrafts() async {
|
||||||
return (select(composeDrafts)
|
final drafts = await select(postDrafts).get();
|
||||||
..orderBy([(d) => OrderingTerm.desc(d.lastModified)]))
|
return drafts
|
||||||
.get();
|
.map((draft) => SnPost.fromJson(jsonDecode(draft.post)))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ComposeDraft?> getComposeDraft(String id) {
|
Future<void> addPostDraft(PostDraftsCompanion entry) async {
|
||||||
return (select(composeDrafts)..where((d) => d.id.equals(id)))
|
await into(postDrafts).insert(entry, mode: InsertMode.replace);
|
||||||
.getSingleOrNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> saveComposeDraft(ComposeDraftsCompanion draft) {
|
Future<void> deletePostDraft(String id) async {
|
||||||
return into(composeDrafts).insert(draft, mode: InsertMode.insertOrReplace);
|
await (delete(postDrafts)..where((tbl) => tbl.id.equals(id))).go();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteComposeDraft(String id) {
|
Future<void> clearAllPostDrafts() async {
|
||||||
return (delete(composeDrafts)..where((d) => d.id.equals(id))).go();
|
await delete(postDrafts).go();
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> clearAllComposeDrafts() {
|
|
||||||
return delete(composeDrafts).go();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods for article drafts
|
|
||||||
Future<List<ArticleDraft>> getAllArticleDrafts() {
|
|
||||||
return (select(articleDrafts)
|
|
||||||
..orderBy([(d) => OrderingTerm.desc(d.lastModified)]))
|
|
||||||
.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ArticleDraft?> getArticleDraft(String id) {
|
|
||||||
return (select(articleDrafts)..where((d) => d.id.equals(id)))
|
|
||||||
.getSingleOrNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> saveArticleDraft(ArticleDraftsCompanion draft) {
|
|
||||||
return into(articleDrafts).insert(draft, mode: InsertMode.insertOrReplace);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> deleteArticleDraft(String id) {
|
|
||||||
return (delete(articleDrafts)..where((d) => d.id.equals(id))).go();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> clearAllArticleDrafts() {
|
|
||||||
return delete(articleDrafts).go();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -9,36 +9,36 @@ part 'post.g.dart';
|
|||||||
sealed class SnPost with _$SnPost {
|
sealed class SnPost with _$SnPost {
|
||||||
const factory SnPost({
|
const factory SnPost({
|
||||||
required String id,
|
required String id,
|
||||||
required String? title,
|
String? title,
|
||||||
required String? description,
|
String? description,
|
||||||
required String? language,
|
String? language,
|
||||||
required DateTime? editedAt,
|
DateTime? editedAt,
|
||||||
required DateTime publishedAt,
|
@Default(null) DateTime? publishedAt,
|
||||||
required int visibility,
|
@Default(0) int visibility,
|
||||||
required String? content,
|
String? content,
|
||||||
required int type,
|
@Default(0) int type,
|
||||||
required Map<String, dynamic>? meta,
|
Map<String, dynamic>? meta,
|
||||||
required int viewsUnique,
|
@Default(0) int viewsUnique,
|
||||||
required int viewsTotal,
|
@Default(0) int viewsTotal,
|
||||||
required int upvotes,
|
@Default(0) int upvotes,
|
||||||
required int downvotes,
|
@Default(0) int downvotes,
|
||||||
required int repliesCount,
|
@Default(0) int repliesCount,
|
||||||
required String? threadedPostId,
|
String? threadedPostId,
|
||||||
required SnPost? threadedPost,
|
SnPost? threadedPost,
|
||||||
required String? repliedPostId,
|
String? repliedPostId,
|
||||||
required SnPost? repliedPost,
|
SnPost? repliedPost,
|
||||||
required String? forwardedPostId,
|
String? forwardedPostId,
|
||||||
required SnPost? forwardedPost,
|
SnPost? forwardedPost,
|
||||||
required List<SnCloudFile> attachments,
|
@Default([]) List<SnCloudFile> attachments,
|
||||||
required SnPublisher publisher,
|
@Default(SnPublisher()) SnPublisher publisher,
|
||||||
@Default({}) Map<String, int> reactionsCount,
|
@Default({}) Map<String, int> reactionsCount,
|
||||||
required List<dynamic> reactions,
|
@Default([]) List<dynamic> reactions,
|
||||||
required List<dynamic> tags,
|
@Default([]) List<dynamic> tags,
|
||||||
required List<dynamic> categories,
|
@Default([]) List<dynamic> categories,
|
||||||
required List<dynamic> collections,
|
@Default([]) List<dynamic> collections,
|
||||||
required DateTime createdAt,
|
@Default(null) DateTime? createdAt,
|
||||||
required DateTime updatedAt,
|
@Default(null) DateTime? updatedAt,
|
||||||
required DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
@Default(false) bool isTruncated,
|
@Default(false) bool isTruncated,
|
||||||
}) = _SnPost;
|
}) = _SnPost;
|
||||||
|
|
||||||
@ -48,20 +48,20 @@ sealed class SnPost with _$SnPost {
|
|||||||
@freezed
|
@freezed
|
||||||
sealed class SnPublisher with _$SnPublisher {
|
sealed class SnPublisher with _$SnPublisher {
|
||||||
const factory SnPublisher({
|
const factory SnPublisher({
|
||||||
required String id,
|
@Default('') String id,
|
||||||
required int type,
|
@Default(0) int type,
|
||||||
required String name,
|
@Default('') String name,
|
||||||
required String nick,
|
@Default('') String nick,
|
||||||
@Default('') String bio,
|
@Default('') String bio,
|
||||||
required SnCloudFile? picture,
|
SnCloudFile? picture,
|
||||||
required SnCloudFile? background,
|
SnCloudFile? background,
|
||||||
required SnAccount? account,
|
SnAccount? account,
|
||||||
required String? accountId,
|
String? accountId,
|
||||||
required DateTime createdAt,
|
@Default(null) DateTime? createdAt,
|
||||||
required DateTime updatedAt,
|
@Default(null) DateTime? updatedAt,
|
||||||
required DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
required String? realmId,
|
String? realmId,
|
||||||
required SnVerificationMark? verification,
|
SnVerificationMark? verification,
|
||||||
}) = _SnPublisher;
|
}) = _SnPublisher;
|
||||||
|
|
||||||
factory SnPublisher.fromJson(Map<String, dynamic> json) =>
|
factory SnPublisher.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPost {
|
mixin _$SnPost {
|
||||||
|
|
||||||
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -49,7 +49,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
|
|||||||
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, bool isTruncated
|
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -66,15 +66,15 @@ class _$SnPostCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: cast_nullable_to_non_nullable
|
as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
|
as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
|
||||||
as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
|
as int,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
|
||||||
@ -96,9 +96,9 @@ as Map<String, int>,reactions: null == reactions ? _self.reactions : reactions /
|
|||||||
as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
|
as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
));
|
));
|
||||||
@ -156,7 +156,7 @@ $SnPublisherCopyWith<$Res> get publisher {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPost implements SnPost {
|
class _SnPost implements SnPost {
|
||||||
const _SnPost({required this.id, required this.title, required this.description, required this.language, required this.editedAt, required this.publishedAt, required this.visibility, required this.content, required this.type, required final Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.repliesCount, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final List<SnCloudFile> attachments, required this.publisher, final Map<String, int> reactionsCount = const {}, required final List<dynamic> reactions, required final List<dynamic> tags, required final List<dynamic> categories, required final List<dynamic> collections, required this.createdAt, required this.updatedAt, required this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, final List<SnCloudFile> attachments = const [], this.publisher = const SnPublisher(), final Map<String, int> reactionsCount = const {}, final List<dynamic> reactions = const [], final List<dynamic> tags = const [], final List<dynamic> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
||||||
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@ -164,10 +164,10 @@ class _SnPost implements SnPost {
|
|||||||
@override final String? description;
|
@override final String? description;
|
||||||
@override final String? language;
|
@override final String? language;
|
||||||
@override final DateTime? editedAt;
|
@override final DateTime? editedAt;
|
||||||
@override final DateTime publishedAt;
|
@override@JsonKey() final DateTime? publishedAt;
|
||||||
@override final int visibility;
|
@override@JsonKey() final int visibility;
|
||||||
@override final String? content;
|
@override final String? content;
|
||||||
@override final int type;
|
@override@JsonKey() final int type;
|
||||||
final Map<String, dynamic>? _meta;
|
final Map<String, dynamic>? _meta;
|
||||||
@override Map<String, dynamic>? get meta {
|
@override Map<String, dynamic>? get meta {
|
||||||
final value = _meta;
|
final value = _meta;
|
||||||
@ -177,11 +177,11 @@ class _SnPost implements SnPost {
|
|||||||
return EqualUnmodifiableMapView(value);
|
return EqualUnmodifiableMapView(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override final int viewsUnique;
|
@override@JsonKey() final int viewsUnique;
|
||||||
@override final int viewsTotal;
|
@override@JsonKey() final int viewsTotal;
|
||||||
@override final int upvotes;
|
@override@JsonKey() final int upvotes;
|
||||||
@override final int downvotes;
|
@override@JsonKey() final int downvotes;
|
||||||
@override final int repliesCount;
|
@override@JsonKey() final int repliesCount;
|
||||||
@override final String? threadedPostId;
|
@override final String? threadedPostId;
|
||||||
@override final SnPost? threadedPost;
|
@override final SnPost? threadedPost;
|
||||||
@override final String? repliedPostId;
|
@override final String? repliedPostId;
|
||||||
@ -189,13 +189,13 @@ class _SnPost implements SnPost {
|
|||||||
@override final String? forwardedPostId;
|
@override final String? forwardedPostId;
|
||||||
@override final SnPost? forwardedPost;
|
@override final SnPost? forwardedPost;
|
||||||
final List<SnCloudFile> _attachments;
|
final List<SnCloudFile> _attachments;
|
||||||
@override List<SnCloudFile> get attachments {
|
@override@JsonKey() List<SnCloudFile> get attachments {
|
||||||
if (_attachments is EqualUnmodifiableListView) return _attachments;
|
if (_attachments is EqualUnmodifiableListView) return _attachments;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_attachments);
|
return EqualUnmodifiableListView(_attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override final SnPublisher publisher;
|
@override@JsonKey() final SnPublisher publisher;
|
||||||
final Map<String, int> _reactionsCount;
|
final Map<String, int> _reactionsCount;
|
||||||
@override@JsonKey() Map<String, int> get reactionsCount {
|
@override@JsonKey() Map<String, int> get reactionsCount {
|
||||||
if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount;
|
if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount;
|
||||||
@ -204,35 +204,35 @@ class _SnPost implements SnPost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> _reactions;
|
final List<dynamic> _reactions;
|
||||||
@override List<dynamic> get reactions {
|
@override@JsonKey() List<dynamic> get reactions {
|
||||||
if (_reactions is EqualUnmodifiableListView) return _reactions;
|
if (_reactions is EqualUnmodifiableListView) return _reactions;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_reactions);
|
return EqualUnmodifiableListView(_reactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> _tags;
|
final List<dynamic> _tags;
|
||||||
@override List<dynamic> get tags {
|
@override@JsonKey() List<dynamic> get tags {
|
||||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_tags);
|
return EqualUnmodifiableListView(_tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> _categories;
|
final List<dynamic> _categories;
|
||||||
@override List<dynamic> get categories {
|
@override@JsonKey() List<dynamic> get categories {
|
||||||
if (_categories is EqualUnmodifiableListView) return _categories;
|
if (_categories is EqualUnmodifiableListView) return _categories;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_categories);
|
return EqualUnmodifiableListView(_categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> _collections;
|
final List<dynamic> _collections;
|
||||||
@override List<dynamic> get collections {
|
@override@JsonKey() List<dynamic> get collections {
|
||||||
if (_collections is EqualUnmodifiableListView) return _collections;
|
if (_collections is EqualUnmodifiableListView) return _collections;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_collections);
|
return EqualUnmodifiableListView(_collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override final DateTime createdAt;
|
@override@JsonKey() final DateTime? createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override@JsonKey() final DateTime? updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
@override@JsonKey() final bool isTruncated;
|
@override@JsonKey() final bool isTruncated;
|
||||||
|
|
||||||
@ -269,7 +269,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, bool isTruncated
|
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -286,15 +286,15 @@ class __$SnPostCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||||
return _then(_SnPost(
|
return _then(_SnPost(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
as String?,language: freezed == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: cast_nullable_to_non_nullable
|
as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
|
as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
|
||||||
as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
|
as int,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
|
||||||
@ -316,9 +316,9 @@ as Map<String, int>,reactions: null == reactions ? _self._reactions : reactions
|
|||||||
as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
|
as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
));
|
));
|
||||||
@ -376,7 +376,7 @@ $SnPublisherCopyWith<$Res> get publisher {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPublisher {
|
mixin _$SnPublisher {
|
||||||
|
|
||||||
String get id; int get type; String get name; String get nick; String get bio; SnCloudFile? get picture; SnCloudFile? get background; SnAccount? get account; String? get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String? get realmId; SnVerificationMark? get verification;
|
String get id; int get type; String get name; String get nick; String get bio; SnCloudFile? get picture; SnCloudFile? get background; SnAccount? get account; String? get accountId; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; String? get realmId; SnVerificationMark? get verification;
|
||||||
/// Create a copy of SnPublisher
|
/// Create a copy of SnPublisher
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -409,7 +409,7 @@ abstract mixin class $SnPublisherCopyWith<$Res> {
|
|||||||
factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl;
|
factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -426,7 +426,7 @@ class _$SnPublisherCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPublisher
|
/// Create a copy of SnPublisher
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@ -437,9 +437,9 @@ as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_
|
|||||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
as SnVerificationMark?,
|
as SnVerificationMark?,
|
||||||
@ -501,20 +501,20 @@ $SnVerificationMarkCopyWith<$Res>? get verification {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPublisher implements SnPublisher {
|
class _SnPublisher implements SnPublisher {
|
||||||
const _SnPublisher({required this.id, required this.type, required this.name, required this.nick, this.bio = '', required this.picture, required this.background, required this.account, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.realmId, required this.verification});
|
const _SnPublisher({this.id = '', this.type = 0, this.name = '', this.nick = '', this.bio = '', this.picture, this.background, this.account, this.accountId, this.createdAt = null, this.updatedAt = null, this.deletedAt, this.realmId, this.verification});
|
||||||
factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json);
|
factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override@JsonKey() final String id;
|
||||||
@override final int type;
|
@override@JsonKey() final int type;
|
||||||
@override final String name;
|
@override@JsonKey() final String name;
|
||||||
@override final String nick;
|
@override@JsonKey() final String nick;
|
||||||
@override@JsonKey() final String bio;
|
@override@JsonKey() final String bio;
|
||||||
@override final SnCloudFile? picture;
|
@override final SnCloudFile? picture;
|
||||||
@override final SnCloudFile? background;
|
@override final SnCloudFile? background;
|
||||||
@override final SnAccount? account;
|
@override final SnAccount? account;
|
||||||
@override final String? accountId;
|
@override final String? accountId;
|
||||||
@override final DateTime createdAt;
|
@override@JsonKey() final DateTime? createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override@JsonKey() final DateTime? updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
@override final String? realmId;
|
@override final String? realmId;
|
||||||
@override final SnVerificationMark? verification;
|
@override final SnVerificationMark? verification;
|
||||||
@ -552,7 +552,7 @@ abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith
|
|||||||
factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl;
|
factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -569,7 +569,7 @@ class __$SnPublisherCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPublisher
|
/// Create a copy of SnPublisher
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
||||||
return _then(_SnPublisher(
|
return _then(_SnPublisher(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@ -580,9 +580,9 @@ as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_
|
|||||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
as SnVerificationMark?,
|
as SnVerificationMark?,
|
||||||
|
@ -15,16 +15,19 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
|||||||
json['edited_at'] == null
|
json['edited_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['edited_at'] as String),
|
: DateTime.parse(json['edited_at'] as String),
|
||||||
publishedAt: DateTime.parse(json['published_at'] as String),
|
publishedAt:
|
||||||
visibility: (json['visibility'] as num).toInt(),
|
json['published_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['published_at'] as String),
|
||||||
|
visibility: (json['visibility'] as num?)?.toInt() ?? 0,
|
||||||
content: json['content'] as String?,
|
content: json['content'] as String?,
|
||||||
type: (json['type'] as num).toInt(),
|
type: (json['type'] as num?)?.toInt() ?? 0,
|
||||||
meta: json['meta'] as Map<String, dynamic>?,
|
meta: json['meta'] as Map<String, dynamic>?,
|
||||||
viewsUnique: (json['views_unique'] as num).toInt(),
|
viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0,
|
||||||
viewsTotal: (json['views_total'] as num).toInt(),
|
viewsTotal: (json['views_total'] as num?)?.toInt() ?? 0,
|
||||||
upvotes: (json['upvotes'] as num).toInt(),
|
upvotes: (json['upvotes'] as num?)?.toInt() ?? 0,
|
||||||
downvotes: (json['downvotes'] as num).toInt(),
|
downvotes: (json['downvotes'] as num?)?.toInt() ?? 0,
|
||||||
repliesCount: (json['replies_count'] as num).toInt(),
|
repliesCount: (json['replies_count'] as num?)?.toInt() ?? 0,
|
||||||
threadedPostId: json['threaded_post_id'] as String?,
|
threadedPostId: json['threaded_post_id'] as String?,
|
||||||
threadedPost:
|
threadedPost:
|
||||||
json['threaded_post'] == null
|
json['threaded_post'] == null
|
||||||
@ -41,21 +44,31 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
|||||||
? null
|
? null
|
||||||
: SnPost.fromJson(json['forwarded_post'] as Map<String, dynamic>),
|
: SnPost.fromJson(json['forwarded_post'] as Map<String, dynamic>),
|
||||||
attachments:
|
attachments:
|
||||||
(json['attachments'] as List<dynamic>)
|
(json['attachments'] as List<dynamic>?)
|
||||||
.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList() ??
|
||||||
publisher: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
const [],
|
||||||
|
publisher:
|
||||||
|
json['publisher'] == null
|
||||||
|
? const SnPublisher()
|
||||||
|
: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||||
reactionsCount:
|
reactionsCount:
|
||||||
(json['reactions_count'] as Map<String, dynamic>?)?.map(
|
(json['reactions_count'] as Map<String, dynamic>?)?.map(
|
||||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||||
) ??
|
) ??
|
||||||
const {},
|
const {},
|
||||||
reactions: json['reactions'] as List<dynamic>,
|
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
||||||
tags: json['tags'] as List<dynamic>,
|
tags: json['tags'] as List<dynamic>? ?? const [],
|
||||||
categories: json['categories'] as List<dynamic>,
|
categories: json['categories'] as List<dynamic>? ?? const [],
|
||||||
collections: json['collections'] as List<dynamic>,
|
collections: json['collections'] as List<dynamic>? ?? const [],
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt:
|
||||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
json['created_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt:
|
||||||
|
json['updated_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['updated_at'] as String),
|
||||||
deletedAt:
|
deletedAt:
|
||||||
json['deleted_at'] == null
|
json['deleted_at'] == null
|
||||||
? null
|
? null
|
||||||
@ -69,7 +82,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
|||||||
'description': instance.description,
|
'description': instance.description,
|
||||||
'language': instance.language,
|
'language': instance.language,
|
||||||
'edited_at': instance.editedAt?.toIso8601String(),
|
'edited_at': instance.editedAt?.toIso8601String(),
|
||||||
'published_at': instance.publishedAt.toIso8601String(),
|
'published_at': instance.publishedAt?.toIso8601String(),
|
||||||
'visibility': instance.visibility,
|
'visibility': instance.visibility,
|
||||||
'content': instance.content,
|
'content': instance.content,
|
||||||
'type': instance.type,
|
'type': instance.type,
|
||||||
@ -92,17 +105,17 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
|||||||
'tags': instance.tags,
|
'tags': instance.tags,
|
||||||
'categories': instance.categories,
|
'categories': instance.categories,
|
||||||
'collections': instance.collections,
|
'collections': instance.collections,
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt?.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'is_truncated': instance.isTruncated,
|
'is_truncated': instance.isTruncated,
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
_SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String? ?? '',
|
||||||
type: (json['type'] as num).toInt(),
|
type: (json['type'] as num?)?.toInt() ?? 0,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String? ?? '',
|
||||||
nick: json['nick'] as String,
|
nick: json['nick'] as String? ?? '',
|
||||||
bio: json['bio'] as String? ?? '',
|
bio: json['bio'] as String? ?? '',
|
||||||
picture:
|
picture:
|
||||||
json['picture'] == null
|
json['picture'] == null
|
||||||
@ -117,8 +130,14 @@ _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
|||||||
? null
|
? null
|
||||||
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
||||||
accountId: json['account_id'] as String?,
|
accountId: json['account_id'] as String?,
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt:
|
||||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
json['created_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt:
|
||||||
|
json['updated_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['updated_at'] as String),
|
||||||
deletedAt:
|
deletedAt:
|
||||||
json['deleted_at'] == null
|
json['deleted_at'] == null
|
||||||
? null
|
? null
|
||||||
@ -143,8 +162,8 @@ Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
|
|||||||
'background': instance.background?.toJson(),
|
'background': instance.background?.toJson(),
|
||||||
'account': instance.account?.toJson(),
|
'account': instance.account?.toJson(),
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt?.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'realm_id': instance.realmId,
|
'realm_id': instance.realmId,
|
||||||
'verification': instance.verification?.toJson(),
|
'verification': instance.verification?.toJson(),
|
||||||
|
@ -102,6 +102,7 @@ Future<ThemeData> createAppTheme(
|
|||||||
),
|
),
|
||||||
snackBarTheme: SnackBarThemeData(
|
snackBarTheme: SnackBarThemeData(
|
||||||
behavior: useM3 ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
|
behavior: useM3 ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
|
||||||
|
width: 560,
|
||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
@ -61,7 +61,7 @@ class EventCalanderScreen extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
margin: EdgeInsets.all(16),
|
margin: EdgeInsets.only(left: 16, right: 16, top: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Use the reusable EventCalendarWidget
|
// Use the reusable EventCalendarWidget
|
||||||
@ -77,7 +77,6 @@ class EventCalanderScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Add the fortune graph widget
|
// Add the fortune graph widget
|
||||||
const Divider(height: 1),
|
|
||||||
FortuneGraphWidget(
|
FortuneGraphWidget(
|
||||||
events: events,
|
events: events,
|
||||||
constrainWidth: true,
|
constrainWidth: true,
|
||||||
|
@ -62,8 +62,6 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
@QueryParam('type') this.type,
|
@QueryParam('type') this.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
// Determine the compose type: auto-detect from edited post or use query parameter
|
// Determine the compose type: auto-detect from edited post or use query parameter
|
||||||
@ -96,7 +94,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
// Only auto-save for new posts, not edits
|
// Only auto-save for new posts, not edits
|
||||||
state.startAutoSave(ref);
|
state.startAutoSave(ref, postType: 0);
|
||||||
}
|
}
|
||||||
return () => state.stopAutoSave();
|
return () => state.stopAutoSave();
|
||||||
}, [state]);
|
}, [state]);
|
||||||
@ -118,14 +116,14 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
final drafts = ref.read(composeStorageNotifierProvider);
|
final drafts = ref.read(composeStorageNotifierProvider);
|
||||||
if (drafts.isNotEmpty) {
|
if (drafts.isNotEmpty) {
|
||||||
final mostRecentDraft = drafts.values.reduce(
|
final mostRecentDraft = drafts.values.reduce(
|
||||||
(a, b) => a.lastModified.isAfter(b.lastModified) ? a : b,
|
(a, b) => (a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0)) ? a : b,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only load if the draft has meaningful content
|
// Only load if the draft has meaningful content
|
||||||
if (!mostRecentDraft.isEmpty) {
|
if (mostRecentDraft.content?.isNotEmpty == true || mostRecentDraft.title?.isNotEmpty == true) {
|
||||||
state.titleController.text = mostRecentDraft.title;
|
state.titleController.text = mostRecentDraft.title ?? '';
|
||||||
state.descriptionController.text = mostRecentDraft.description;
|
state.descriptionController.text = mostRecentDraft.description ?? '';
|
||||||
state.contentController.text = mostRecentDraft.content;
|
state.contentController.text = mostRecentDraft.content ?? '';
|
||||||
state.visibility.value = mostRecentDraft.visibility;
|
state.visibility.value = mostRecentDraft.visibility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,9 +160,10 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
Widget buildWideAttachmentGrid() {
|
Widget buildWideAttachmentGrid() {
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
),
|
),
|
||||||
@ -245,17 +244,16 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => DraftManagerSheet(
|
(context) => DraftManagerSheet(
|
||||||
isArticle: false,
|
|
||||||
onDraftSelected: (draftId) {
|
onDraftSelected: (draftId) {
|
||||||
final draft =
|
final draft =
|
||||||
ref.read(
|
ref.read(
|
||||||
composeStorageNotifierProvider,
|
composeStorageNotifierProvider,
|
||||||
)[draftId];
|
)[draftId];
|
||||||
if (draft != null) {
|
if (draft != null) {
|
||||||
state.titleController.text = draft.title;
|
state.titleController.text = draft.title ?? '';
|
||||||
state.descriptionController.text =
|
state.descriptionController.text =
|
||||||
draft.description;
|
draft.description ?? '';
|
||||||
state.contentController.text = draft.content;
|
state.contentController.text = draft.content ?? '';
|
||||||
state.visibility.value = draft.visibility;
|
state.visibility.value = draft.visibility;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -320,7 +318,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
// Main content area
|
// Main content area
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 480),
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -9,6 +9,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
|
||||||
import 'package:island/screens/creators/publishers.dart';
|
import 'package:island/screens/creators/publishers.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@ -21,6 +22,7 @@ import 'package:island/widgets/post/compose_settings_sheet.dart';
|
|||||||
import 'package:island/services/compose_storage_db.dart';
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
import 'package:island/widgets/post/publishers_modal.dart';
|
import 'package:island/widgets/post/publishers_modal.dart';
|
||||||
import 'package:island/widgets/post/draft_manager.dart';
|
import 'package:island/widgets/post/draft_manager.dart';
|
||||||
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@ -71,7 +73,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
// Only auto-save for new articles, not edits
|
// Only auto-save for new articles, not edits
|
||||||
autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
||||||
_saveArticleDraft(ref, state);
|
ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return () {
|
return () {
|
||||||
@ -79,7 +81,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
state.stopAutoSave();
|
state.stopAutoSave();
|
||||||
// Save final draft before disposing
|
// Save final draft before disposing
|
||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
_saveArticleDraft(ref, state);
|
ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1);
|
||||||
}
|
}
|
||||||
ComposeLogic.dispose(state);
|
ComposeLogic.dispose(state);
|
||||||
autoSaveTimer?.cancel();
|
autoSaveTimer?.cancel();
|
||||||
@ -100,17 +102,22 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
// Try to load the most recent article draft
|
// Try to load the most recent article draft
|
||||||
final drafts = ref.read(articleStorageNotifierProvider);
|
final drafts = ref.read(composeStorageNotifierProvider);
|
||||||
if (drafts.isNotEmpty) {
|
if (drafts.isNotEmpty) {
|
||||||
final mostRecentDraft = drafts.values.reduce(
|
final mostRecentDraft = drafts.values.reduce(
|
||||||
(a, b) => a.lastModified.isAfter(b.lastModified) ? a : b,
|
(a, b) =>
|
||||||
|
(a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0))
|
||||||
|
? a
|
||||||
|
: b,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only load if the draft has meaningful content
|
// Only load if the draft has meaningful content
|
||||||
if (!mostRecentDraft.isEmpty) {
|
if (mostRecentDraft.content?.isNotEmpty == true ||
|
||||||
state.titleController.text = mostRecentDraft.title;
|
mostRecentDraft.title?.isNotEmpty == true) {
|
||||||
state.descriptionController.text = mostRecentDraft.description;
|
state.titleController.text = mostRecentDraft.title ?? '';
|
||||||
state.contentController.text = mostRecentDraft.content;
|
state.descriptionController.text =
|
||||||
|
mostRecentDraft.description ?? '';
|
||||||
|
state.contentController.text = mostRecentDraft.content ?? '';
|
||||||
state.visibility.value = mostRecentDraft.visibility;
|
state.visibility.value = mostRecentDraft.visibility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -356,7 +363,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
return PopScope(
|
return PopScope(
|
||||||
onPopInvoked: (_) {
|
onPopInvoked: (_) {
|
||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
_saveArticleDraft(ref, state);
|
ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: AppScaffold(
|
child: AppScaffold(
|
||||||
@ -383,17 +390,17 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => DraftManagerSheet(
|
(context) => DraftManagerSheet(
|
||||||
isArticle: true,
|
|
||||||
onDraftSelected: (draftId) {
|
onDraftSelected: (draftId) {
|
||||||
final draft =
|
final draft =
|
||||||
ref.read(
|
ref.read(
|
||||||
articleStorageNotifierProvider,
|
composeStorageNotifierProvider,
|
||||||
)[draftId];
|
)[draftId];
|
||||||
if (draft != null) {
|
if (draft != null) {
|
||||||
state.titleController.text = draft.title;
|
state.titleController.text = draft.title ?? '';
|
||||||
state.descriptionController.text =
|
state.descriptionController.text =
|
||||||
draft.description;
|
draft.description ?? '';
|
||||||
state.contentController.text = draft.content;
|
state.contentController.text =
|
||||||
|
draft.content ?? '';
|
||||||
state.visibility.value = draft.visibility;
|
state.visibility.value = draft.visibility;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -404,7 +411,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.save),
|
icon: const Icon(Symbols.save),
|
||||||
onPressed: () => _saveArticleDraft(ref, state),
|
onPressed: () => ComposeLogic.saveDraft(ref, state, postType: 1),
|
||||||
tooltip: 'saveDraft'.tr(),
|
tooltip: 'saveDraft'.tr(),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -524,7 +531,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
ComposeLogic.handlePaste(state);
|
ComposeLogic.handlePaste(state);
|
||||||
} else if (isSave && isModifierPressed) {
|
} else if (isSave && isModifierPressed) {
|
||||||
_saveArticleDraft(ref, state);
|
ComposeLogic.saveDraft(ref, state, postType: 1);
|
||||||
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
|
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
|
||||||
ComposeLogic.performAction(
|
ComposeLogic.performAction(
|
||||||
ref,
|
ref,
|
||||||
@ -537,23 +544,5 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to save article draft
|
// Helper method to save article draft
|
||||||
Future<void> _saveArticleDraft(WidgetRef ref, ComposeState state) async {
|
|
||||||
try {
|
|
||||||
final draft = ArticleDraftModel(
|
|
||||||
id: state.draftId,
|
|
||||||
title: state.titleController.text,
|
|
||||||
description: state.descriptionController.text,
|
|
||||||
content: state.contentController.text,
|
|
||||||
visibility: state.visibility.value,
|
|
||||||
lastModified: DateTime.now(),
|
|
||||||
);
|
|
||||||
|
|
||||||
await ref.read(articleStorageNotifierProvider.notifier).saveDraft(draft);
|
|
||||||
} catch (e) {
|
|
||||||
log('[ArticleCompose] Failed to save draft, error: $e');
|
|
||||||
// Silently fail for auto-save to avoid disrupting user experience
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,183 +1,16 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:island/database/drift_db.dart';
|
import 'package:island/database/drift_db.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/pods/database.dart';
|
import 'package:island/pods/database.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:island/models/file.dart';
|
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
|
||||||
|
|
||||||
part 'compose_storage_db.g.dart';
|
part 'compose_storage_db.g.dart';
|
||||||
|
|
||||||
class ComposeDraftModel {
|
|
||||||
final String id;
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
final String content;
|
|
||||||
final List<UniversalFile> attachments;
|
|
||||||
final int visibility;
|
|
||||||
final DateTime lastModified;
|
|
||||||
|
|
||||||
ComposeDraftModel({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.description,
|
|
||||||
required this.content,
|
|
||||||
required this.attachments,
|
|
||||||
required this.visibility,
|
|
||||||
required this.lastModified,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'id': id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'content': content,
|
|
||||||
'attachments': attachments.map((e) => e.toJson()).toList(),
|
|
||||||
'visibility': visibility,
|
|
||||||
'lastModified': lastModified.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
factory ComposeDraftModel.fromJson(Map<String, dynamic> json) => ComposeDraftModel(
|
|
||||||
id: json['id'] as String,
|
|
||||||
title: json['title'] as String? ?? '',
|
|
||||||
description: json['description'] as String? ?? '',
|
|
||||||
content: json['content'] as String? ?? '',
|
|
||||||
attachments: (json['attachments'] as List? ?? [])
|
|
||||||
.map((e) => UniversalFile.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
visibility: json['visibility'] as int? ?? 0,
|
|
||||||
lastModified: DateTime.parse(json['lastModified'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
factory ComposeDraftModel.fromDbRow(ComposeDraft row) => ComposeDraftModel(
|
|
||||||
id: row.id,
|
|
||||||
title: row.title,
|
|
||||||
description: row.description,
|
|
||||||
content: row.content,
|
|
||||||
attachments: (jsonDecode(row.attachmentIds) as List)
|
|
||||||
.map((e) => UniversalFile.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
visibility: row.visibility,
|
|
||||||
lastModified: row.lastModified,
|
|
||||||
);
|
|
||||||
|
|
||||||
ComposeDraftsCompanion toDbCompanion() => ComposeDraftsCompanion(
|
|
||||||
id: Value(id),
|
|
||||||
title: Value(title),
|
|
||||||
description: Value(description),
|
|
||||||
content: Value(content),
|
|
||||||
attachmentIds: Value(jsonEncode(attachments.map((e) => e.toJson()).toList())),
|
|
||||||
visibility: Value(visibility),
|
|
||||||
lastModified: Value(lastModified),
|
|
||||||
);
|
|
||||||
|
|
||||||
ComposeDraftModel copyWith({
|
|
||||||
String? id,
|
|
||||||
String? title,
|
|
||||||
String? description,
|
|
||||||
String? content,
|
|
||||||
List<UniversalFile>? attachments,
|
|
||||||
int? visibility,
|
|
||||||
DateTime? lastModified,
|
|
||||||
}) {
|
|
||||||
return ComposeDraftModel(
|
|
||||||
id: id ?? this.id,
|
|
||||||
title: title ?? this.title,
|
|
||||||
description: description ?? this.description,
|
|
||||||
content: content ?? this.content,
|
|
||||||
attachments: attachments ?? this.attachments,
|
|
||||||
visibility: visibility ?? this.visibility,
|
|
||||||
lastModified: lastModified ?? this.lastModified,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isEmpty =>
|
|
||||||
title.isEmpty &&
|
|
||||||
description.isEmpty &&
|
|
||||||
content.isEmpty &&
|
|
||||||
attachments.isEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArticleDraftModel {
|
|
||||||
final String id;
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
final String content;
|
|
||||||
final int visibility;
|
|
||||||
final DateTime lastModified;
|
|
||||||
|
|
||||||
ArticleDraftModel({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.description,
|
|
||||||
required this.content,
|
|
||||||
required this.visibility,
|
|
||||||
required this.lastModified,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'id': id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'content': content,
|
|
||||||
'visibility': visibility,
|
|
||||||
'lastModified': lastModified.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
factory ArticleDraftModel.fromJson(Map<String, dynamic> json) => ArticleDraftModel(
|
|
||||||
id: json['id'] as String,
|
|
||||||
title: json['title'] as String? ?? '',
|
|
||||||
description: json['description'] as String? ?? '',
|
|
||||||
content: json['content'] as String? ?? '',
|
|
||||||
visibility: json['visibility'] as int? ?? 0,
|
|
||||||
lastModified: DateTime.parse(json['lastModified'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
factory ArticleDraftModel.fromDbRow(ArticleDraft row) => ArticleDraftModel(
|
|
||||||
id: row.id,
|
|
||||||
title: row.title,
|
|
||||||
description: row.description,
|
|
||||||
content: row.content,
|
|
||||||
visibility: row.visibility,
|
|
||||||
lastModified: row.lastModified,
|
|
||||||
);
|
|
||||||
|
|
||||||
ArticleDraftsCompanion toDbCompanion() => ArticleDraftsCompanion(
|
|
||||||
id: Value(id),
|
|
||||||
title: Value(title),
|
|
||||||
description: Value(description),
|
|
||||||
content: Value(content),
|
|
||||||
visibility: Value(visibility),
|
|
||||||
lastModified: Value(lastModified),
|
|
||||||
);
|
|
||||||
|
|
||||||
ArticleDraftModel copyWith({
|
|
||||||
String? id,
|
|
||||||
String? title,
|
|
||||||
String? description,
|
|
||||||
String? content,
|
|
||||||
int? visibility,
|
|
||||||
DateTime? lastModified,
|
|
||||||
}) {
|
|
||||||
return ArticleDraftModel(
|
|
||||||
id: id ?? this.id,
|
|
||||||
title: title ?? this.title,
|
|
||||||
description: description ?? this.description,
|
|
||||||
content: content ?? this.content,
|
|
||||||
visibility: visibility ?? this.visibility,
|
|
||||||
lastModified: lastModified ?? this.lastModified,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isEmpty => title.isEmpty && description.isEmpty && content.isEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
class ComposeStorageNotifier extends _$ComposeStorageNotifier {
|
class ComposeStorageNotifier extends _$ComposeStorageNotifier {
|
||||||
@override
|
@override
|
||||||
Map<String, ComposeDraftModel> build() {
|
Map<String, SnPost> build() {
|
||||||
_loadDrafts();
|
_loadDrafts();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -185,10 +18,9 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier {
|
|||||||
void _loadDrafts() async {
|
void _loadDrafts() async {
|
||||||
try {
|
try {
|
||||||
final database = ref.read(databaseProvider);
|
final database = ref.read(databaseProvider);
|
||||||
final dbDrafts = await database.getAllComposeDrafts();
|
final dbDrafts = await database.getAllPostDrafts();
|
||||||
final drafts = <String, ComposeDraftModel>{};
|
final drafts = <String, SnPost>{};
|
||||||
for (final dbDraft in dbDrafts) {
|
for (final draft in dbDrafts) {
|
||||||
final draft = ComposeDraftModel.fromDbRow(dbDraft);
|
|
||||||
drafts[draft.id] = draft;
|
drafts[draft.id] = draft;
|
||||||
}
|
}
|
||||||
state = drafts;
|
state = drafts;
|
||||||
@ -198,52 +30,22 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveDraft(ComposeDraftModel draft) async {
|
Future<void> saveDraft(SnPost draft) async {
|
||||||
if (draft.isEmpty) {
|
final updatedDraft = draft.copyWith(updatedAt: DateTime.now());
|
||||||
await deleteDraft(draft.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload all attachments that are not yet uploaded
|
|
||||||
final uploadedAttachments = <UniversalFile>[];
|
|
||||||
final serverUrl = ref.read(serverUrlProvider);
|
|
||||||
final token = ref.read(tokenProvider);
|
|
||||||
|
|
||||||
for (final attachment in draft.attachments) {
|
|
||||||
if (!attachment.isOnCloud) {
|
|
||||||
try {
|
|
||||||
final completer = putMediaToCloud(
|
|
||||||
fileData: attachment,
|
|
||||||
atk: token?.token ?? '',
|
|
||||||
baseUrl: serverUrl,
|
|
||||||
);
|
|
||||||
final uploadedFile = await completer.future;
|
|
||||||
if (uploadedFile != null) {
|
|
||||||
uploadedAttachments.add(UniversalFile.fromAttachment(uploadedFile));
|
|
||||||
} else {
|
|
||||||
uploadedAttachments.add(attachment);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// If upload fails, keep the original file
|
|
||||||
uploadedAttachments.add(attachment);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uploadedAttachments.add(attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final updatedDraft = draft.copyWith(
|
|
||||||
attachments: uploadedAttachments,
|
|
||||||
lastModified: DateTime.now(),
|
|
||||||
);
|
|
||||||
state = {...state, updatedDraft.id: updatedDraft};
|
state = {...state, updatedDraft.id: updatedDraft};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final database = ref.read(databaseProvider);
|
final database = ref.read(databaseProvider);
|
||||||
await database.saveComposeDraft(updatedDraft.toDbCompanion());
|
await database.addPostDraft(
|
||||||
|
PostDraftsCompanion(
|
||||||
|
id: Value(updatedDraft.id),
|
||||||
|
post: Value(jsonEncode(updatedDraft.toJson())),
|
||||||
|
lastModified: Value(updatedDraft.updatedAt ?? DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Revert state on error
|
// Revert state on error
|
||||||
final newState = Map<String, ComposeDraftModel>.from(state);
|
final newState = Map<String, SnPost>.from(state);
|
||||||
newState.remove(updatedDraft.id);
|
newState.remove(updatedDraft.id);
|
||||||
state = newState;
|
state = newState;
|
||||||
rethrow;
|
rethrow;
|
||||||
@ -252,13 +54,13 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier {
|
|||||||
|
|
||||||
Future<void> deleteDraft(String id) async {
|
Future<void> deleteDraft(String id) async {
|
||||||
final oldDraft = state[id];
|
final oldDraft = state[id];
|
||||||
final newState = Map<String, ComposeDraftModel>.from(state);
|
final newState = Map<String, SnPost>.from(state);
|
||||||
newState.remove(id);
|
newState.remove(id);
|
||||||
state = newState;
|
state = newState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final database = ref.read(databaseProvider);
|
final database = ref.read(databaseProvider);
|
||||||
await database.deleteComposeDraft(id);
|
await database.deletePostDraft(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Revert state on error
|
// Revert state on error
|
||||||
if (oldDraft != null) {
|
if (oldDraft != null) {
|
||||||
@ -268,13 +70,13 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ComposeDraftModel? getDraft(String id) {
|
SnPost? getDraft(String id) {
|
||||||
return state[id];
|
return state[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ComposeDraftModel> getAllDrafts() {
|
List<SnPost> getAllDrafts() {
|
||||||
final drafts = state.values.toList();
|
final drafts = state.values.toList();
|
||||||
drafts.sort((a, b) => b.lastModified.compareTo(a.lastModified));
|
drafts.sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
|
||||||
return drafts;
|
return drafts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,94 +85,7 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final database = ref.read(databaseProvider);
|
final database = ref.read(databaseProvider);
|
||||||
await database.clearAllComposeDrafts();
|
await database.clearAllPostDrafts();
|
||||||
} catch (e) {
|
|
||||||
// If clearing fails, we might want to reload from database
|
|
||||||
_loadDrafts();
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@riverpod
|
|
||||||
class ArticleStorageNotifier extends _$ArticleStorageNotifier {
|
|
||||||
@override
|
|
||||||
Map<String, ArticleDraftModel> build() {
|
|
||||||
_loadDrafts();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadDrafts() async {
|
|
||||||
try {
|
|
||||||
final database = ref.read(databaseProvider);
|
|
||||||
final dbDrafts = await database.getAllArticleDrafts();
|
|
||||||
final drafts = <String, ArticleDraftModel>{};
|
|
||||||
for (final dbDraft in dbDrafts) {
|
|
||||||
final draft = ArticleDraftModel.fromDbRow(dbDraft);
|
|
||||||
drafts[draft.id] = draft;
|
|
||||||
}
|
|
||||||
state = drafts;
|
|
||||||
} catch (e) {
|
|
||||||
// If there's an error loading drafts, start with empty state
|
|
||||||
state = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveDraft(ArticleDraftModel draft) async {
|
|
||||||
if (draft.isEmpty) {
|
|
||||||
await deleteDraft(draft.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final updatedDraft = draft.copyWith(lastModified: DateTime.now());
|
|
||||||
state = {...state, updatedDraft.id: updatedDraft};
|
|
||||||
|
|
||||||
try {
|
|
||||||
final database = ref.read(databaseProvider);
|
|
||||||
await database.saveArticleDraft(updatedDraft.toDbCompanion());
|
|
||||||
} catch (e) {
|
|
||||||
// Revert state on error
|
|
||||||
final newState = Map<String, ArticleDraftModel>.from(state);
|
|
||||||
newState.remove(updatedDraft.id);
|
|
||||||
state = newState;
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteDraft(String id) async {
|
|
||||||
final oldDraft = state[id];
|
|
||||||
final newState = Map<String, ArticleDraftModel>.from(state);
|
|
||||||
newState.remove(id);
|
|
||||||
state = newState;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final database = ref.read(databaseProvider);
|
|
||||||
await database.deleteArticleDraft(id);
|
|
||||||
} catch (e) {
|
|
||||||
// Revert state on error
|
|
||||||
if (oldDraft != null) {
|
|
||||||
state = {...state, id: oldDraft};
|
|
||||||
}
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleDraftModel? getDraft(String id) {
|
|
||||||
return state[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ArticleDraftModel> getAllDrafts() {
|
|
||||||
final drafts = state.values.toList();
|
|
||||||
drafts.sort((a, b) => b.lastModified.compareTo(a.lastModified));
|
|
||||||
return drafts;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearAllDrafts() async {
|
|
||||||
state = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
final database = ref.read(databaseProvider);
|
|
||||||
await database.clearAllArticleDrafts();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If clearing fails, we might want to reload from database
|
// If clearing fails, we might want to reload from database
|
||||||
_loadDrafts();
|
_loadDrafts();
|
||||||
|
@ -7,13 +7,13 @@ part of 'compose_storage_db.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$composeStorageNotifierHash() =>
|
String _$composeStorageNotifierHash() =>
|
||||||
r'fcdb006dca44d30916a20804922e93d0caad49ca';
|
r'3de7a01a93d999d45a32fb68617b77f194589686';
|
||||||
|
|
||||||
/// See also [ComposeStorageNotifier].
|
/// See also [ComposeStorageNotifier].
|
||||||
@ProviderFor(ComposeStorageNotifier)
|
@ProviderFor(ComposeStorageNotifier)
|
||||||
final composeStorageNotifierProvider = AutoDisposeNotifierProvider<
|
final composeStorageNotifierProvider = AutoDisposeNotifierProvider<
|
||||||
ComposeStorageNotifier,
|
ComposeStorageNotifier,
|
||||||
Map<String, ComposeDraftModel>
|
Map<String, SnPost>
|
||||||
>.internal(
|
>.internal(
|
||||||
ComposeStorageNotifier.new,
|
ComposeStorageNotifier.new,
|
||||||
name: r'composeStorageNotifierProvider',
|
name: r'composeStorageNotifierProvider',
|
||||||
@ -25,28 +25,6 @@ final composeStorageNotifierProvider = AutoDisposeNotifierProvider<
|
|||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef _$ComposeStorageNotifier =
|
typedef _$ComposeStorageNotifier = AutoDisposeNotifier<Map<String, SnPost>>;
|
||||||
AutoDisposeNotifier<Map<String, ComposeDraftModel>>;
|
|
||||||
String _$articleStorageNotifierHash() =>
|
|
||||||
r'21ee0f8ee87528bebf8f5f4b0b2892cd8058e230';
|
|
||||||
|
|
||||||
/// See also [ArticleStorageNotifier].
|
|
||||||
@ProviderFor(ArticleStorageNotifier)
|
|
||||||
final articleStorageNotifierProvider = AutoDisposeNotifierProvider<
|
|
||||||
ArticleStorageNotifier,
|
|
||||||
Map<String, ArticleDraftModel>
|
|
||||||
>.internal(
|
|
||||||
ArticleStorageNotifier.new,
|
|
||||||
name: r'articleStorageNotifierProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$articleStorageNotifierHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$ArticleStorageNotifier =
|
|
||||||
AutoDisposeNotifier<Map<String, ArticleDraftModel>>;
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -15,6 +14,7 @@ import 'package:island/services/compose_storage_db.dart';
|
|||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
class ComposeState {
|
class ComposeState {
|
||||||
final ValueNotifier<List<UniversalFile>> attachments;
|
final ValueNotifier<List<UniversalFile>> attachments;
|
||||||
@ -40,10 +40,10 @@ class ComposeState {
|
|||||||
required this.draftId,
|
required this.draftId,
|
||||||
});
|
});
|
||||||
|
|
||||||
void startAutoSave(WidgetRef ref) {
|
void startAutoSave(WidgetRef ref, {int postType = 0}) {
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
_autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
_autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
||||||
ComposeLogic.saveDraft(ref, this);
|
ComposeLogic.saveDraftWithoutUpload(ref, this, postType: postType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +96,11 @@ class ComposeLogic {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ComposeState createStateFromDraft(ComposeDraftModel draft) {
|
static ComposeState createStateFromDraft(SnPost draft) {
|
||||||
return ComposeState(
|
return ComposeState(
|
||||||
attachments: ValueNotifier<List<UniversalFile>>([]),
|
attachments: ValueNotifier<List<UniversalFile>>(
|
||||||
|
draft.attachments.map((e) => UniversalFile.fromAttachment(e)).toList(),
|
||||||
|
),
|
||||||
titleController: TextEditingController(text: draft.title),
|
titleController: TextEditingController(text: draft.title),
|
||||||
descriptionController: TextEditingController(text: draft.description),
|
descriptionController: TextEditingController(text: draft.description),
|
||||||
contentController: TextEditingController(text: draft.content),
|
contentController: TextEditingController(text: draft.content),
|
||||||
@ -110,29 +112,247 @@ class ComposeLogic {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> saveDraft(WidgetRef ref, ComposeState state, {int postType = 0}) async {
|
||||||
|
final hasContent =
|
||||||
|
state.titleController.text.trim().isNotEmpty ||
|
||||||
|
state.descriptionController.text.trim().isNotEmpty ||
|
||||||
|
state.contentController.text.trim().isNotEmpty;
|
||||||
|
final hasAttachments = state.attachments.value.isNotEmpty;
|
||||||
|
|
||||||
|
if (!hasContent && !hasAttachments) {
|
||||||
static Future<void> saveDraft(WidgetRef ref, ComposeState state) async {
|
return; // Don't save empty posts
|
||||||
try {
|
|
||||||
// Check if the auto-save timer is still active (widget not disposed)
|
|
||||||
if (state._autoSaveTimer == null) {
|
|
||||||
return; // Widget has been disposed, don't save
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final draft = ComposeDraftModel(
|
try {
|
||||||
|
if (state._autoSaveTimer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload any local attachments first
|
||||||
|
final baseUrl = ref.watch(serverUrlProvider);
|
||||||
|
final token = await getToken(ref.watch(tokenProvider));
|
||||||
|
if (token == null) throw ArgumentError('Token is null');
|
||||||
|
|
||||||
|
for (int i = 0; i < state.attachments.value.length; i++) {
|
||||||
|
final attachment = state.attachments.value[i];
|
||||||
|
if (attachment.data is! SnCloudFile) {
|
||||||
|
try {
|
||||||
|
final cloudFile =
|
||||||
|
await putMediaToCloud(
|
||||||
|
fileData: attachment,
|
||||||
|
atk: token,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
filename: attachment.data.name ?? (postType == 1 ? 'Article media' : 'Post media'),
|
||||||
|
mimetype:
|
||||||
|
attachment.data.mimeType ??
|
||||||
|
ComposeLogic.getMimeTypeFromFileType(attachment.type),
|
||||||
|
).future;
|
||||||
|
if (cloudFile != null) {
|
||||||
|
// Update attachments list with cloud file
|
||||||
|
final clone = List.of(state.attachments.value);
|
||||||
|
clone[i] = UniversalFile(data: cloudFile, type: attachment.type);
|
||||||
|
state.attachments.value = clone;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log('[ComposeLogic] Failed to upload attachment: $err');
|
||||||
|
// Continue with other attachments even if one fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final draft = SnPost(
|
||||||
id: state.draftId,
|
id: state.draftId,
|
||||||
title: state.titleController.text,
|
title: state.titleController.text,
|
||||||
description: state.descriptionController.text,
|
description: state.descriptionController.text,
|
||||||
content: state.contentController.text,
|
language: null,
|
||||||
attachments: state.attachments.value,
|
editedAt: null,
|
||||||
|
publishedAt: DateTime.now(),
|
||||||
visibility: state.visibility.value,
|
visibility: state.visibility.value,
|
||||||
lastModified: DateTime.now(),
|
content: state.contentController.text,
|
||||||
|
type: postType,
|
||||||
|
meta: null,
|
||||||
|
viewsUnique: 0,
|
||||||
|
viewsTotal: 0,
|
||||||
|
upvotes: 0,
|
||||||
|
downvotes: 0,
|
||||||
|
repliesCount: 0,
|
||||||
|
threadedPostId: null,
|
||||||
|
threadedPost: null,
|
||||||
|
repliedPostId: null,
|
||||||
|
repliedPost: null,
|
||||||
|
forwardedPostId: null,
|
||||||
|
forwardedPost: null,
|
||||||
|
attachments:
|
||||||
|
state.attachments.value
|
||||||
|
.map((e) => e.data)
|
||||||
|
.whereType<SnCloudFile>()
|
||||||
|
.toList(),
|
||||||
|
publisher: SnPublisher(
|
||||||
|
id: '',
|
||||||
|
type: 0,
|
||||||
|
name: '',
|
||||||
|
nick: '',
|
||||||
|
picture: null,
|
||||||
|
background: null,
|
||||||
|
account: null,
|
||||||
|
accountId: null,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
realmId: null,
|
||||||
|
verification: null,
|
||||||
|
),
|
||||||
|
reactions: [],
|
||||||
|
tags: [],
|
||||||
|
categories: [],
|
||||||
|
collections: [],
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft);
|
await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('[ComposeLogic] Failed to save draft, error: $e');
|
log('[ComposeLogic] Failed to save draft, error: $e');
|
||||||
// Silently fail for auto-save to avoid disrupting user experience
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> saveDraftWithoutUpload(WidgetRef ref, ComposeState state, {int postType = 0}) async {
|
||||||
|
final hasContent =
|
||||||
|
state.titleController.text.trim().isNotEmpty ||
|
||||||
|
state.descriptionController.text.trim().isNotEmpty ||
|
||||||
|
state.contentController.text.trim().isNotEmpty;
|
||||||
|
final hasAttachments = state.attachments.value.isNotEmpty;
|
||||||
|
|
||||||
|
if (!hasContent && !hasAttachments) {
|
||||||
|
return; // Don't save empty posts
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (state._autoSaveTimer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final draft = SnPost(
|
||||||
|
id: state.draftId,
|
||||||
|
title: state.titleController.text,
|
||||||
|
description: state.descriptionController.text,
|
||||||
|
language: null,
|
||||||
|
editedAt: null,
|
||||||
|
publishedAt: DateTime.now(),
|
||||||
|
visibility: state.visibility.value,
|
||||||
|
content: state.contentController.text,
|
||||||
|
type: postType,
|
||||||
|
meta: null,
|
||||||
|
viewsUnique: 0,
|
||||||
|
viewsTotal: 0,
|
||||||
|
upvotes: 0,
|
||||||
|
downvotes: 0,
|
||||||
|
repliesCount: 0,
|
||||||
|
threadedPostId: null,
|
||||||
|
threadedPost: null,
|
||||||
|
repliedPostId: null,
|
||||||
|
repliedPost: null,
|
||||||
|
forwardedPostId: null,
|
||||||
|
forwardedPost: null,
|
||||||
|
attachments:
|
||||||
|
state.attachments.value
|
||||||
|
.map((e) => e.data)
|
||||||
|
.whereType<SnCloudFile>()
|
||||||
|
.toList(),
|
||||||
|
publisher: SnPublisher(
|
||||||
|
id: '',
|
||||||
|
type: 0,
|
||||||
|
name: '',
|
||||||
|
nick: '',
|
||||||
|
picture: null,
|
||||||
|
background: null,
|
||||||
|
account: null,
|
||||||
|
accountId: null,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
realmId: null,
|
||||||
|
verification: null,
|
||||||
|
),
|
||||||
|
reactions: [],
|
||||||
|
tags: [],
|
||||||
|
categories: [],
|
||||||
|
collections: [],
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft);
|
||||||
|
} catch (e) {
|
||||||
|
log('[ComposeLogic] Failed to save draft without upload, error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> saveDraftManually(
|
||||||
|
WidgetRef ref,
|
||||||
|
ComposeState state,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final draft = SnPost(
|
||||||
|
id: state.draftId,
|
||||||
|
title: state.titleController.text,
|
||||||
|
description: state.descriptionController.text,
|
||||||
|
language: null,
|
||||||
|
editedAt: null,
|
||||||
|
publishedAt: DateTime.now(),
|
||||||
|
visibility: state.visibility.value,
|
||||||
|
content: state.contentController.text,
|
||||||
|
type: 0,
|
||||||
|
meta: null,
|
||||||
|
viewsUnique: 0,
|
||||||
|
viewsTotal: 0,
|
||||||
|
upvotes: 0,
|
||||||
|
downvotes: 0,
|
||||||
|
repliesCount: 0,
|
||||||
|
threadedPostId: null,
|
||||||
|
threadedPost: null,
|
||||||
|
repliedPostId: null,
|
||||||
|
repliedPost: null,
|
||||||
|
forwardedPostId: null,
|
||||||
|
forwardedPost: null,
|
||||||
|
attachments: [], // TODO: Handle attachments
|
||||||
|
publisher: SnPublisher(
|
||||||
|
id: '',
|
||||||
|
type: 0,
|
||||||
|
name: '',
|
||||||
|
nick: '',
|
||||||
|
picture: null,
|
||||||
|
background: null,
|
||||||
|
account: null,
|
||||||
|
accountId: null,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
realmId: null,
|
||||||
|
verification: null,
|
||||||
|
),
|
||||||
|
reactions: [],
|
||||||
|
tags: [],
|
||||||
|
categories: [],
|
||||||
|
collections: [],
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(context, 'draftSaved'.tr());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('[ComposeLogic] Failed to save draft manually, error: $e');
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(context, 'draftSaveFailed'.tr());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +366,7 @@ class ComposeLogic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<ComposeDraftModel?> loadDraft(WidgetRef ref, String draftId) async {
|
static Future<SnPost?> loadDraft(WidgetRef ref, String draftId) async {
|
||||||
try {
|
try {
|
||||||
return ref
|
return ref
|
||||||
.read(composeStorageNotifierProvider.notifier)
|
.read(composeStorageNotifierProvider.notifier)
|
||||||
@ -282,6 +502,20 @@ class ComposeLogic {
|
|||||||
}) async {
|
}) async {
|
||||||
if (state.submitting.value) return;
|
if (state.submitting.value) return;
|
||||||
|
|
||||||
|
// Don't submit empty posts (no content and no attachments)
|
||||||
|
final hasContent =
|
||||||
|
state.titleController.text.trim().isNotEmpty ||
|
||||||
|
state.descriptionController.text.trim().isNotEmpty ||
|
||||||
|
state.contentController.text.trim().isNotEmpty;
|
||||||
|
final hasAttachments = state.attachments.value.isNotEmpty;
|
||||||
|
|
||||||
|
if (!hasContent && !hasAttachments) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(context, 'postContentEmpty'.tr());
|
||||||
|
}
|
||||||
|
return; // Don't submit empty posts
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.submitting.value = true;
|
state.submitting.value = true;
|
||||||
|
|
||||||
@ -329,7 +563,7 @@ class ComposeLogic {
|
|||||||
if (postType == 1) {
|
if (postType == 1) {
|
||||||
// Delete article draft
|
// Delete article draft
|
||||||
await ref
|
await ref
|
||||||
.read(articleStorageNotifierProvider.notifier)
|
.read(composeStorageNotifierProvider.notifier)
|
||||||
.deleteDraft(state.draftId);
|
.deleteDraft(state.draftId);
|
||||||
} else {
|
} else {
|
||||||
// Delete regular post draft
|
// Delete regular post draft
|
||||||
@ -381,7 +615,7 @@ class ComposeLogic {
|
|||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
handlePaste(state);
|
handlePaste(state);
|
||||||
} else if (isSave && isModifierPressed) {
|
} else if (isSave && isModifierPressed) {
|
||||||
saveDraft(ref, state);
|
saveDraftManually(ref, state, context);
|
||||||
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
|
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
|
||||||
performAction(
|
performAction(
|
||||||
ref,
|
ref,
|
||||||
|
@ -7,42 +7,48 @@ import 'package:island/services/compose_storage_db.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
class DraftManagerSheet extends HookConsumerWidget {
|
class DraftManagerSheet extends HookConsumerWidget {
|
||||||
final bool isArticle;
|
|
||||||
final Function(String draftId)? onDraftSelected;
|
final Function(String draftId)? onDraftSelected;
|
||||||
|
|
||||||
const DraftManagerSheet({
|
const DraftManagerSheet({super.key, this.onDraftSelected});
|
||||||
super.key,
|
|
||||||
this.isArticle = false,
|
|
||||||
this.onDraftSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
|
final isLoading = useState(true);
|
||||||
|
|
||||||
final drafts =
|
final drafts = ref.watch(composeStorageNotifierProvider);
|
||||||
isArticle
|
|
||||||
? ref.watch(articleStorageNotifierProvider)
|
|
||||||
: ref.watch(composeStorageNotifierProvider);
|
|
||||||
|
|
||||||
final sortedDrafts = useMemoized(() {
|
// Track loading state based on drafts being loaded
|
||||||
if (isArticle) {
|
useEffect(() {
|
||||||
final draftList = drafts.values.cast<ArticleDraftModel>().toList();
|
// Set loading to false after drafts are loaded
|
||||||
draftList.sort((a, b) => b.lastModified.compareTo(a.lastModified));
|
// We consider drafts loaded when the provider has been initialized
|
||||||
return draftList;
|
Future.microtask(() {
|
||||||
} else {
|
if (isLoading.value) {
|
||||||
final draftList = drafts.values.cast<ComposeDraftModel>().toList();
|
isLoading.value = false;
|
||||||
draftList.sort((a, b) => b.lastModified.compareTo(a.lastModified));
|
|
||||||
return draftList;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
}, [drafts]);
|
}, [drafts]);
|
||||||
|
|
||||||
|
final sortedDrafts = useMemoized(
|
||||||
|
() {
|
||||||
|
final draftList = drafts.values.toList();
|
||||||
|
draftList.sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
|
||||||
|
return draftList;
|
||||||
|
},
|
||||||
|
[
|
||||||
|
drafts.length,
|
||||||
|
drafts.values.map((e) => e.updatedAt!.millisecondsSinceEpoch).join(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text('drafts'.tr())),
|
||||||
title: Text(isArticle ? 'articleDrafts'.tr() : 'postDrafts'.tr()),
|
body:
|
||||||
),
|
isLoading.value
|
||||||
body: Column(
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
if (sortedDrafts.isEmpty)
|
if (sortedDrafts.isEmpty)
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -74,29 +80,14 @@ class DraftManagerSheet extends HookConsumerWidget {
|
|||||||
final draft = sortedDrafts[index];
|
final draft = sortedDrafts[index];
|
||||||
return _DraftItem(
|
return _DraftItem(
|
||||||
draft: draft,
|
draft: draft,
|
||||||
isArticle: isArticle,
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
final draftId =
|
onDraftSelected?.call(draft.id);
|
||||||
isArticle
|
|
||||||
? (draft as ArticleDraftModel).id
|
|
||||||
: (draft as ComposeDraftModel).id;
|
|
||||||
onDraftSelected?.call(draftId);
|
|
||||||
},
|
},
|
||||||
onDelete: () async {
|
onDelete: () async {
|
||||||
final draftId =
|
|
||||||
isArticle
|
|
||||||
? (draft as ArticleDraftModel).id
|
|
||||||
: (draft as ComposeDraftModel).id;
|
|
||||||
if (isArticle) {
|
|
||||||
await ref
|
|
||||||
.read(articleStorageNotifierProvider.notifier)
|
|
||||||
.deleteDraft(draftId);
|
|
||||||
} else {
|
|
||||||
await ref
|
await ref
|
||||||
.read(composeStorageNotifierProvider.notifier)
|
.read(composeStorageNotifierProvider.notifier)
|
||||||
.deleteDraft(draftId);
|
.deleteDraft(draft.id);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -116,16 +107,22 @@ class DraftManagerSheet extends HookConsumerWidget {
|
|||||||
builder:
|
builder:
|
||||||
(context) => AlertDialog(
|
(context) => AlertDialog(
|
||||||
title: Text('clearAllDrafts'.tr()),
|
title: Text('clearAllDrafts'.tr()),
|
||||||
content: Text('clearAllDraftsConfirm'.tr()),
|
content: Text(
|
||||||
|
'clearAllDraftsConfirm'.tr(),
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
() => Navigator.of(context).pop(false),
|
() => Navigator.of(
|
||||||
|
context,
|
||||||
|
).pop(false),
|
||||||
child: Text('cancel'.tr()),
|
child: Text('cancel'.tr()),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
() => Navigator.of(context).pop(true),
|
() => Navigator.of(
|
||||||
|
context,
|
||||||
|
).pop(true),
|
||||||
child: Text('confirm'.tr()),
|
child: Text('confirm'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -133,15 +130,11 @@ class DraftManagerSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed == true) {
|
if (confirmed == true) {
|
||||||
if (isArticle) {
|
|
||||||
await ref
|
await ref
|
||||||
.read(articleStorageNotifierProvider.notifier)
|
.read(
|
||||||
|
composeStorageNotifierProvider.notifier,
|
||||||
|
)
|
||||||
.clearAllDrafts();
|
.clearAllDrafts();
|
||||||
} else {
|
|
||||||
await ref
|
|
||||||
.read(composeStorageNotifierProvider.notifier)
|
|
||||||
.clearAllDrafts();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Symbols.delete_sweep),
|
icon: const Icon(Symbols.delete_sweep),
|
||||||
@ -159,56 +152,23 @@ class DraftManagerSheet extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DraftItem extends StatelessWidget {
|
class _DraftItem extends StatelessWidget {
|
||||||
final dynamic draft; // ComposeDraft or ArticleDraft
|
final dynamic draft;
|
||||||
final bool isArticle;
|
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final VoidCallback? onDelete;
|
final VoidCallback? onDelete;
|
||||||
|
|
||||||
const _DraftItem({
|
const _DraftItem({required this.draft, this.onTap, this.onDelete});
|
||||||
required this.draft,
|
|
||||||
required this.isArticle,
|
|
||||||
this.onTap,
|
|
||||||
this.onDelete,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
|
|
||||||
final String title;
|
final title = draft.title ?? 'untitled'.tr();
|
||||||
final String content;
|
final content = draft.content ?? (draft.description ?? 'noContent'.tr());
|
||||||
final DateTime lastModified;
|
|
||||||
final String visibility;
|
|
||||||
|
|
||||||
if (isArticle) {
|
|
||||||
final articleDraft = draft as ArticleDraftModel;
|
|
||||||
title =
|
|
||||||
articleDraft.title.isNotEmpty ? articleDraft.title : 'untitled'.tr();
|
|
||||||
content =
|
|
||||||
articleDraft.content.isNotEmpty
|
|
||||||
? articleDraft.content
|
|
||||||
: (articleDraft.description.isNotEmpty
|
|
||||||
? articleDraft.description
|
|
||||||
: 'noContent'.tr());
|
|
||||||
lastModified = articleDraft.lastModified;
|
|
||||||
visibility = _parseArticleVisibility(articleDraft.visibility);
|
|
||||||
} else {
|
|
||||||
final postDraft = draft as ComposeDraftModel;
|
|
||||||
title = postDraft.title.isNotEmpty ? postDraft.title : 'untitled'.tr();
|
|
||||||
content =
|
|
||||||
postDraft.content.isNotEmpty
|
|
||||||
? postDraft.content
|
|
||||||
: (postDraft.description.isNotEmpty
|
|
||||||
? postDraft.description
|
|
||||||
: 'noContent'.tr());
|
|
||||||
lastModified = postDraft.lastModified;
|
|
||||||
visibility = _parseArticleVisibility(postDraft.visibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
final preview =
|
final preview =
|
||||||
content.length > 100 ? '${content.substring(0, 100)}...' : content;
|
content.length > 100 ? '${content.substring(0, 100)}...' : content;
|
||||||
final timeAgo = _formatTimeAgo(lastModified);
|
final timeAgo = _formatTimeAgo(draft.updatedAt!);
|
||||||
|
final visibility = _parseVisibility(draft.visibility);
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
@ -223,7 +183,7 @@ class _DraftItem extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isArticle ? Symbols.article : Symbols.post_add,
|
draft.type == 1 ? Symbols.article : Symbols.post_add,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: colorScheme.primary,
|
color: colorScheme.primary,
|
||||||
),
|
),
|
||||||
@ -316,7 +276,7 @@ class _DraftItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _parseArticleVisibility(int visibility) {
|
String _parseVisibility(int visibility) {
|
||||||
switch (visibility) {
|
switch (visibility) {
|
||||||
case 0:
|
case 0:
|
||||||
return 'public'.tr();
|
return 'public'.tr();
|
||||||
|
@ -162,8 +162,8 @@ class PostItem extends HookConsumerWidget {
|
|||||||
Spacer(),
|
Spacer(),
|
||||||
Text(
|
Text(
|
||||||
isFullPost
|
isFullPost
|
||||||
? item.publishedAt.formatSystem()
|
? item.publishedAt?.formatSystem() ?? ''
|
||||||
: item.publishedAt.formatRelative(context),
|
: item.publishedAt?.formatRelative(context) ?? '',
|
||||||
).fontSize(11).alignment(Alignment.bottomRight),
|
).fontSize(11).alignment(Alignment.bottomRight),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
],
|
],
|
||||||
@ -213,12 +213,14 @@ class PostItem extends HookConsumerWidget {
|
|||||||
content: item.content!,
|
content: item.content!,
|
||||||
linesMargin:
|
linesMargin:
|
||||||
item.type == 0
|
item.type == 0
|
||||||
? EdgeInsets.only(bottom: 4)
|
? EdgeInsets.only(bottom: 8)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
// Show truncation hint if post is truncated
|
// Show truncation hint if post is truncated
|
||||||
if (item.isTruncated && !isFullPost)
|
if (item.isTruncated && !isFullPost)
|
||||||
_PostTruncateHint(),
|
_PostTruncateHint().padding(
|
||||||
|
bottom: item.attachments.isNotEmpty ? 8 : null,
|
||||||
|
),
|
||||||
if ((item.repliedPost != null ||
|
if ((item.repliedPost != null ||
|
||||||
item.forwardedPost != null) &&
|
item.forwardedPost != null) &&
|
||||||
showReferencePost)
|
showReferencePost)
|
||||||
@ -234,7 +236,7 @@ class PostItem extends HookConsumerWidget {
|
|||||||
MediaQuery.of(context).size.width * 0.9,
|
MediaQuery.of(context).size.width * 0.9,
|
||||||
kWideScreenWidth - 160,
|
kWideScreenWidth - 160,
|
||||||
),
|
),
|
||||||
).padding(top: 4),
|
),
|
||||||
// Render embed links
|
// Render embed links
|
||||||
if (item.meta?['embeds'] != null)
|
if (item.meta?['embeds'] != null)
|
||||||
...((item.meta!['embeds'] as List<dynamic>)
|
...((item.meta!['embeds'] as List<dynamic>)
|
||||||
@ -248,7 +250,8 @@ class PostItem extends HookConsumerWidget {
|
|||||||
MediaQuery.of(context).size.width * 0.85,
|
MediaQuery.of(context).size.width * 0.85,
|
||||||
kWideScreenWidth - 160,
|
kWideScreenWidth - 160,
|
||||||
),
|
),
|
||||||
).padding(top: 4),
|
margin: EdgeInsets.only(top: 8),
|
||||||
|
),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -323,7 +326,6 @@ Widget _buildReferencePost(BuildContext context, SnPost item) {
|
|||||||
final isReply = item.repliedPost != null;
|
final isReply = item.repliedPost != null;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(top: 8, bottom: 8),
|
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
||||||
|
@ -153,7 +153,7 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
item.publishedAt.formatSystem(),
|
item.publishedAt?.formatSystem() ?? '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
@ -291,7 +291,7 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Created: ${item.createdAt.formatSystem()}',
|
'Created: ${item.createdAt?.formatSystem() ?? ''}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user