From 47c31ddec2752320189112361086aacad647c145 Mon Sep 17 00:00:00 2001 From: LittleSheep <littlesheep.code@hotmail.com> Date: Wed, 25 Jun 2025 02:15:45 +0800 Subject: [PATCH] :recycle: Refactored post draft system --- assets/i18n/en-US.json | 3 + assets/i18n/zh-CN.json | 18 +- assets/i18n/zh-TW.json | 18 +- lib/database/draft.dart | 20 +- lib/database/drift_db.dart | 64 +- lib/database/drift_db.g.dart | 1196 +++-------------------- lib/models/post.dart | 84 +- lib/models/post.freezed.dart | 100 +- lib/models/post.g.dart | 77 +- lib/pods/theme.dart | 1 + lib/screens/account/event_calendar.dart | 3 +- lib/screens/posts/compose.dart | 26 +- lib/screens/posts/compose_article.dart | 59 +- lib/services/compose_storage_db.dart | 335 +------ lib/services/compose_storage_db.g.dart | 28 +- lib/widgets/post/compose_shared.dart | 268 ++++- lib/widgets/post/draft_manager.dart | 300 +++--- lib/widgets/post/post_item.dart | 16 +- lib/widgets/post/post_item_creator.dart | 4 +- 19 files changed, 776 insertions(+), 1844 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index ec4051e..1528157 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -410,6 +410,8 @@ "articleDrafts": "Article drafts", "postDrafts": "Post drafts", "saveDraft": "Save draft", + "draftSaved": "Draft saved", + "draftSaveFailed": "Failed to save draft", "clearAllDrafts": "Clear All Drafts", "clearAllDraftsConfirm": "Are you sure you want to delete all drafts? This action cannot be undone.", "clearAll": "Clear All", @@ -441,6 +443,7 @@ "contactMethodDelete": "Delete Contact", "contactMethodNew": "New Contact Method", "contactMethodContentEmpty": "Contact content cannot be empty", + "postContentEmpty": "Post content cannot be empty", "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.", "accountContactMethod": "Contact Methods", diff --git a/assets/i18n/zh-CN.json b/assets/i18n/zh-CN.json index 81a718e..a5d5266 100644 --- a/assets/i18n/zh-CN.json +++ b/assets/i18n/zh-CN.json @@ -319,5 +319,21 @@ "processingPayment": "处理付款中...", "pleaseWait": "请稍候", "paymentFailed": "付款失败,请重试。", - "paymentSuccess": "付款成功完成!" + "paymentSuccess": "付款成功完成!", + "drafts": "草稿", + "noDrafts": "暂无草稿", + "articleDrafts": "文章草稿", + "postDrafts": "帖子草稿", + "saveDraft": "保存草稿", + "draftSaved": "草稿已保存", + "draftSaveFailed": "保存草稿失败", + "clearAllDrafts": "清空所有草稿", + "clearAllDraftsConfirm": "确定要删除所有草稿吗?此操作无法撤销。", + "clearAll": "清空全部", + "untitled": "无标题", + "noContent": "无内容", + "justNow": "刚刚", + "minutesAgo": "{} 分钟前", + "hoursAgo": "{} 小时前", + "postContentEmpty": "帖子内容不能为空" } \ No newline at end of file diff --git a/assets/i18n/zh-TW.json b/assets/i18n/zh-TW.json index d74b8e4..3aea04e 100644 --- a/assets/i18n/zh-TW.json +++ b/assets/i18n/zh-TW.json @@ -334,5 +334,21 @@ "membershipFeatureAllNova": "所有新星功能", "membershipFeatureExclusiveContent": "獨家內容", "membershipFeatureVipSupport": "VIP 支援", - "membershipCurrentBadge": "目前" + "membershipCurrentBadge": "目前", + "drafts": "草稿", + "noDrafts": "暫無草稿", + "articleDrafts": "文章草稿", + "postDrafts": "貼文草稿", + "saveDraft": "儲存草稿", + "draftSaved": "草稿已儲存", + "draftSaveFailed": "儲存草稿失敗", + "clearAllDrafts": "清空所有草稿", + "clearAllDraftsConfirm": "確定要刪除所有草稿嗎?此操作無法復原。", + "clearAll": "清空全部", + "untitled": "無標題", + "noContent": "無內容", + "justNow": "剛剛", + "minutesAgo": "{} 分鐘前", + "hoursAgo": "{} 小時前", + "postContentEmpty": "貼文內容不能為空" } \ No newline at end of file diff --git a/lib/database/draft.dart b/lib/database/draft.dart index 95cfa52..e0fe29d 100644 --- a/lib/database/draft.dart +++ b/lib/database/draft.dart @@ -1,26 +1,10 @@ import 'package:drift/drift.dart'; -class ComposeDrafts extends Table { +class PostDrafts 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(''))(); - 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 + TextColumn get post => text()(); // Store SnPost model as JSON string 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()(); - - @override - Set<Column> get primaryKey => {id}; -} \ No newline at end of file diff --git a/lib/database/drift_db.dart b/lib/database/drift_db.dart index 3748f6c..5963525 100644 --- a/lib/database/drift_db.dart +++ b/lib/database/drift_db.dart @@ -2,16 +2,17 @@ import 'dart:convert'; import 'package:drift/drift.dart'; import 'package:island/database/message.dart'; import 'package:island/database/draft.dart'; +import 'package:island/models/post.dart'; part 'drift_db.g.dart'; // Define the database -@DriftDatabase(tables: [ChatMessages, ComposeDrafts, ArticleDrafts]) +@DriftDatabase(tables: [ChatMessages, PostDrafts]) class AppDatabase extends _$AppDatabase { AppDatabase(super.e); @override - int get schemaVersion => 3; + int get schemaVersion => 4; @override MigrationStrategy get migration => MigrationStrategy( @@ -23,10 +24,9 @@ class AppDatabase extends _$AppDatabase { // Add isRead column with default value false await m.addColumn(chatMessages, chatMessages.isRead); } - if (from < 3) { - // Add draft tables - await m.createTable(composeDrafts); - await m.createTable(articleDrafts); + if (from < 4) { + // Drop old draft tables if they exist + await m.createTable(postDrafts); } }, ); @@ -98,51 +98,23 @@ class AppDatabase extends _$AppDatabase { ); } - // Methods for compose drafts - Future<List<ComposeDraft>> getAllComposeDrafts() { - return (select(composeDrafts) - ..orderBy([(d) => OrderingTerm.desc(d.lastModified)])) - .get(); + // Methods for post drafts + Future<List<SnPost>> getAllPostDrafts() async { + final drafts = await select(postDrafts).get(); + return drafts + .map((draft) => SnPost.fromJson(jsonDecode(draft.post))) + .toList(); } - Future<ComposeDraft?> getComposeDraft(String id) { - return (select(composeDrafts)..where((d) => d.id.equals(id))) - .getSingleOrNull(); + Future<void> addPostDraft(PostDraftsCompanion entry) async { + await into(postDrafts).insert(entry, mode: InsertMode.replace); } - Future<int> saveComposeDraft(ComposeDraftsCompanion draft) { - return into(composeDrafts).insert(draft, mode: InsertMode.insertOrReplace); + Future<void> deletePostDraft(String id) async { + await (delete(postDrafts)..where((tbl) => tbl.id.equals(id))).go(); } - Future<int> deleteComposeDraft(String id) { - return (delete(composeDrafts)..where((d) => d.id.equals(id))).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(); + Future<void> clearAllPostDrafts() async { + await delete(postDrafts).go(); } } diff --git a/lib/database/drift_db.g.dart b/lib/database/drift_db.g.dart index 4403baa..d26a862 100644 --- a/lib/database/drift_db.g.dart +++ b/lib/database/drift_db.g.dart @@ -569,12 +569,12 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> { } } -class $ComposeDraftsTable extends ComposeDrafts - with TableInfo<$ComposeDraftsTable, ComposeDraft> { +class $PostDraftsTable extends PostDrafts + with TableInfo<$PostDraftsTable, PostDraft> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $ComposeDraftsTable(this.attachedDatabase, [this._alias]); + $PostDraftsTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn<String> id = GeneratedColumn<String>( @@ -584,63 +584,14 @@ class $ComposeDraftsTable extends ComposeDrafts type: DriftSqlType.string, requiredDuringInsert: true, ); - static const VerificationMeta _titleMeta = const VerificationMeta('title'); + static const VerificationMeta _postMeta = const VerificationMeta('post'); @override - late final GeneratedColumn<String> title = GeneratedColumn<String>( - 'title', + late final GeneratedColumn<String> post = GeneratedColumn<String>( + 'post', aliasedName, false, type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant(''), - ); - static const VerificationMeta _descriptionMeta = const VerificationMeta( - 'description', - ); - @override - late final GeneratedColumn<String> description = GeneratedColumn<String>( - 'description', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant(''), - ); - static const VerificationMeta _contentMeta = const VerificationMeta( - 'content', - ); - @override - late final GeneratedColumn<String> content = GeneratedColumn<String>( - 'content', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant(''), - ); - static const VerificationMeta _attachmentIdsMeta = const VerificationMeta( - 'attachmentIds', - ); - @override - late final GeneratedColumn<String> attachmentIds = GeneratedColumn<String>( - 'attachment_ids', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('[]'), - ); - static const VerificationMeta _visibilityMeta = const VerificationMeta( - 'visibility', - ); - @override - late final GeneratedColumn<int> visibility = GeneratedColumn<int>( - 'visibility', - aliasedName, - false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0), + requiredDuringInsert: true, ); static const VerificationMeta _lastModifiedMeta = const VerificationMeta( 'lastModified', @@ -654,23 +605,15 @@ class $ComposeDraftsTable extends ComposeDrafts requiredDuringInsert: true, ); @override - List<GeneratedColumn> get $columns => [ - id, - title, - description, - content, - attachmentIds, - visibility, - lastModified, - ]; + List<GeneratedColumn> get $columns => [id, post, lastModified]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'compose_drafts'; + static const String $name = 'post_drafts'; @override VerificationContext validateIntegrity( - Insertable<ComposeDraft> instance, { + Insertable<PostDraft> instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -680,41 +623,13 @@ class $ComposeDraftsTable extends ComposeDrafts } else if (isInserting) { context.missing(_idMeta); } - if (data.containsKey('title')) { + if (data.containsKey('post')) { context.handle( - _titleMeta, - title.isAcceptableOrUnknown(data['title']!, _titleMeta), - ); - } - if (data.containsKey('description')) { - context.handle( - _descriptionMeta, - description.isAcceptableOrUnknown( - data['description']!, - _descriptionMeta, - ), - ); - } - if (data.containsKey('content')) { - context.handle( - _contentMeta, - content.isAcceptableOrUnknown(data['content']!, _contentMeta), - ); - } - if (data.containsKey('attachment_ids')) { - context.handle( - _attachmentIdsMeta, - attachmentIds.isAcceptableOrUnknown( - data['attachment_ids']!, - _attachmentIdsMeta, - ), - ); - } - if (data.containsKey('visibility')) { - context.handle( - _visibilityMeta, - visibility.isAcceptableOrUnknown(data['visibility']!, _visibilityMeta), + _postMeta, + post.isAcceptableOrUnknown(data['post']!, _postMeta), ); + } else if (isInserting) { + context.missing(_postMeta); } if (data.containsKey('last_modified')) { context.handle( @@ -733,38 +648,18 @@ class $ComposeDraftsTable extends ComposeDrafts @override Set<GeneratedColumn> get $primaryKey => {id}; @override - ComposeDraft map(Map<String, dynamic> data, {String? tablePrefix}) { + PostDraft map(Map<String, dynamic> data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return ComposeDraft( + return PostDraft( id: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}id'], )!, - title: + post: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}title'], - )!, - description: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}description'], - )!, - content: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}content'], - )!, - attachmentIds: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}attachment_ids'], - )!, - visibility: - attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}visibility'], + data['${effectivePrefix}post'], )!, lastModified: attachedDatabase.typeMapping.read( @@ -775,65 +670,45 @@ class $ComposeDraftsTable extends ComposeDrafts } @override - $ComposeDraftsTable createAlias(String alias) { - return $ComposeDraftsTable(attachedDatabase, alias); + $PostDraftsTable createAlias(String alias) { + return $PostDraftsTable(attachedDatabase, alias); } } -class ComposeDraft extends DataClass implements Insertable<ComposeDraft> { +class PostDraft extends DataClass implements Insertable<PostDraft> { final String id; - final String title; - final String description; - final String content; - final String attachmentIds; - final int visibility; + final String post; final DateTime lastModified; - const ComposeDraft({ + const PostDraft({ required this.id, - required this.title, - required this.description, - required this.content, - required this.attachmentIds, - required this.visibility, + required this.post, required this.lastModified, }); @override Map<String, Expression> toColumns(bool nullToAbsent) { final map = <String, Expression>{}; map['id'] = Variable<String>(id); - map['title'] = Variable<String>(title); - map['description'] = Variable<String>(description); - map['content'] = Variable<String>(content); - map['attachment_ids'] = Variable<String>(attachmentIds); - map['visibility'] = Variable<int>(visibility); + map['post'] = Variable<String>(post); map['last_modified'] = Variable<DateTime>(lastModified); return map; } - ComposeDraftsCompanion toCompanion(bool nullToAbsent) { - return ComposeDraftsCompanion( + PostDraftsCompanion toCompanion(bool nullToAbsent) { + return PostDraftsCompanion( id: Value(id), - title: Value(title), - description: Value(description), - content: Value(content), - attachmentIds: Value(attachmentIds), - visibility: Value(visibility), + post: Value(post), lastModified: Value(lastModified), ); } - factory ComposeDraft.fromJson( + factory PostDraft.fromJson( Map<String, dynamic> json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return ComposeDraft( + return PostDraft( id: serializer.fromJson<String>(json['id']), - title: serializer.fromJson<String>(json['title']), - description: serializer.fromJson<String>(json['description']), - content: serializer.fromJson<String>(json['content']), - attachmentIds: serializer.fromJson<String>(json['attachmentIds']), - visibility: serializer.fromJson<int>(json['visibility']), + post: serializer.fromJson<String>(json['post']), lastModified: serializer.fromJson<DateTime>(json['lastModified']), ); } @@ -842,45 +717,21 @@ class ComposeDraft extends DataClass implements Insertable<ComposeDraft> { serializer ??= driftRuntimeOptions.defaultSerializer; return <String, dynamic>{ 'id': serializer.toJson<String>(id), - 'title': serializer.toJson<String>(title), - 'description': serializer.toJson<String>(description), - 'content': serializer.toJson<String>(content), - 'attachmentIds': serializer.toJson<String>(attachmentIds), - 'visibility': serializer.toJson<int>(visibility), + 'post': serializer.toJson<String>(post), 'lastModified': serializer.toJson<DateTime>(lastModified), }; } - ComposeDraft copyWith({ - String? id, - String? title, - String? description, - String? content, - String? attachmentIds, - int? visibility, - DateTime? lastModified, - }) => ComposeDraft( - id: id ?? this.id, - title: title ?? this.title, - description: description ?? this.description, - content: content ?? this.content, - attachmentIds: attachmentIds ?? this.attachmentIds, - visibility: visibility ?? this.visibility, - lastModified: lastModified ?? this.lastModified, - ); - ComposeDraft copyWithCompanion(ComposeDraftsCompanion data) { - return ComposeDraft( + PostDraft copyWith({String? id, String? post, DateTime? lastModified}) => + PostDraft( + id: id ?? this.id, + post: post ?? this.post, + lastModified: lastModified ?? this.lastModified, + ); + PostDraft copyWithCompanion(PostDraftsCompanion data) { + return PostDraft( id: data.id.present ? data.id.value : this.id, - title: data.title.present ? data.title.value : this.title, - description: - data.description.present ? data.description.value : this.description, - content: data.content.present ? data.content.value : this.content, - attachmentIds: - data.attachmentIds.present - ? data.attachmentIds.value - : this.attachmentIds, - visibility: - data.visibility.present ? data.visibility.value : this.visibility, + post: data.post.present ? data.post.value : this.post, lastModified: data.lastModified.present ? data.lastModified.value @@ -890,110 +741,67 @@ class ComposeDraft extends DataClass implements Insertable<ComposeDraft> { @override String toString() { - return (StringBuffer('ComposeDraft(') + return (StringBuffer('PostDraft(') ..write('id: $id, ') - ..write('title: $title, ') - ..write('description: $description, ') - ..write('content: $content, ') - ..write('attachmentIds: $attachmentIds, ') - ..write('visibility: $visibility, ') + ..write('post: $post, ') ..write('lastModified: $lastModified') ..write(')')) .toString(); } @override - int get hashCode => Object.hash( - id, - title, - description, - content, - attachmentIds, - visibility, - lastModified, - ); + int get hashCode => Object.hash(id, post, lastModified); @override bool operator ==(Object other) => identical(this, other) || - (other is ComposeDraft && + (other is PostDraft && other.id == this.id && - other.title == this.title && - other.description == this.description && - other.content == this.content && - other.attachmentIds == this.attachmentIds && - other.visibility == this.visibility && + other.post == this.post && other.lastModified == this.lastModified); } -class ComposeDraftsCompanion extends UpdateCompanion<ComposeDraft> { +class PostDraftsCompanion extends UpdateCompanion<PostDraft> { final Value<String> id; - final Value<String> title; - final Value<String> description; - final Value<String> content; - final Value<String> attachmentIds; - final Value<int> visibility; + final Value<String> post; final Value<DateTime> lastModified; final Value<int> rowid; - const ComposeDraftsCompanion({ + const PostDraftsCompanion({ this.id = const Value.absent(), - this.title = const Value.absent(), - this.description = const Value.absent(), - this.content = const Value.absent(), - this.attachmentIds = const Value.absent(), - this.visibility = const Value.absent(), + this.post = const Value.absent(), this.lastModified = const Value.absent(), this.rowid = const Value.absent(), }); - ComposeDraftsCompanion.insert({ + PostDraftsCompanion.insert({ required String id, - this.title = const Value.absent(), - this.description = const Value.absent(), - this.content = const Value.absent(), - this.attachmentIds = const Value.absent(), - this.visibility = const Value.absent(), + required String post, required DateTime lastModified, this.rowid = const Value.absent(), }) : id = Value(id), + post = Value(post), lastModified = Value(lastModified); - static Insertable<ComposeDraft> custom({ + static Insertable<PostDraft> custom({ Expression<String>? id, - Expression<String>? title, - Expression<String>? description, - Expression<String>? content, - Expression<String>? attachmentIds, - Expression<int>? visibility, + Expression<String>? post, Expression<DateTime>? lastModified, Expression<int>? rowid, }) { return RawValuesInsertable({ if (id != null) 'id': id, - if (title != null) 'title': title, - if (description != null) 'description': description, - if (content != null) 'content': content, - if (attachmentIds != null) 'attachment_ids': attachmentIds, - if (visibility != null) 'visibility': visibility, + if (post != null) 'post': post, if (lastModified != null) 'last_modified': lastModified, if (rowid != null) 'rowid': rowid, }); } - ComposeDraftsCompanion copyWith({ + PostDraftsCompanion copyWith({ Value<String>? id, - Value<String>? title, - Value<String>? description, - Value<String>? content, - Value<String>? attachmentIds, - Value<int>? visibility, + Value<String>? post, Value<DateTime>? lastModified, Value<int>? rowid, }) { - return ComposeDraftsCompanion( + return PostDraftsCompanion( id: id ?? this.id, - title: title ?? this.title, - description: description ?? this.description, - content: content ?? this.content, - attachmentIds: attachmentIds ?? this.attachmentIds, - visibility: visibility ?? this.visibility, + post: post ?? this.post, lastModified: lastModified ?? this.lastModified, rowid: rowid ?? this.rowid, ); @@ -1005,20 +813,8 @@ class ComposeDraftsCompanion extends UpdateCompanion<ComposeDraft> { if (id.present) { map['id'] = Variable<String>(id.value); } - if (title.present) { - map['title'] = Variable<String>(title.value); - } - if (description.present) { - map['description'] = Variable<String>(description.value); - } - if (content.present) { - map['content'] = Variable<String>(content.value); - } - if (attachmentIds.present) { - map['attachment_ids'] = Variable<String>(attachmentIds.value); - } - if (visibility.present) { - map['visibility'] = Variable<int>(visibility.value); + if (post.present) { + map['post'] = Variable<String>(post.value); } if (lastModified.present) { map['last_modified'] = Variable<DateTime>(lastModified.value); @@ -1031,430 +827,9 @@ class ComposeDraftsCompanion extends UpdateCompanion<ComposeDraft> { @override String toString() { - return (StringBuffer('ComposeDraftsCompanion(') + return (StringBuffer('PostDraftsCompanion(') ..write('id: $id, ') - ..write('title: $title, ') - ..write('description: $description, ') - ..write('content: $content, ') - ..write('attachmentIds: $attachmentIds, ') - ..write('visibility: $visibility, ') - ..write('lastModified: $lastModified, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - -class $ArticleDraftsTable extends ArticleDrafts - with TableInfo<$ArticleDraftsTable, ArticleDraft> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $ArticleDraftsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn<String> id = GeneratedColumn<String>( - 'id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - ); - static const VerificationMeta _titleMeta = const VerificationMeta('title'); - @override - late final GeneratedColumn<String> title = GeneratedColumn<String>( - 'title', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant(''), - ); - static const VerificationMeta _descriptionMeta = const VerificationMeta( - 'description', - ); - @override - late final GeneratedColumn<String> description = GeneratedColumn<String>( - 'description', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant(''), - ); - static const VerificationMeta _contentMeta = const VerificationMeta( - 'content', - ); - @override - late final GeneratedColumn<String> content = GeneratedColumn<String>( - 'content', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant(''), - ); - static const VerificationMeta _visibilityMeta = const VerificationMeta( - 'visibility', - ); - @override - late final GeneratedColumn<int> visibility = GeneratedColumn<int>( - 'visibility', - aliasedName, - false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0), - ); - static const VerificationMeta _lastModifiedMeta = const VerificationMeta( - 'lastModified', - ); - @override - late final GeneratedColumn<DateTime> lastModified = GeneratedColumn<DateTime>( - 'last_modified', - aliasedName, - false, - type: DriftSqlType.dateTime, - requiredDuringInsert: true, - ); - @override - List<GeneratedColumn> get $columns => [ - id, - title, - description, - content, - visibility, - lastModified, - ]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'article_drafts'; - @override - VerificationContext validateIntegrity( - Insertable<ArticleDraft> instance, { - bool isInserting = false, - }) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); - } else if (isInserting) { - context.missing(_idMeta); - } - if (data.containsKey('title')) { - context.handle( - _titleMeta, - title.isAcceptableOrUnknown(data['title']!, _titleMeta), - ); - } - if (data.containsKey('description')) { - context.handle( - _descriptionMeta, - description.isAcceptableOrUnknown( - data['description']!, - _descriptionMeta, - ), - ); - } - if (data.containsKey('content')) { - context.handle( - _contentMeta, - content.isAcceptableOrUnknown(data['content']!, _contentMeta), - ); - } - if (data.containsKey('visibility')) { - context.handle( - _visibilityMeta, - visibility.isAcceptableOrUnknown(data['visibility']!, _visibilityMeta), - ); - } - if (data.containsKey('last_modified')) { - context.handle( - _lastModifiedMeta, - lastModified.isAcceptableOrUnknown( - data['last_modified']!, - _lastModifiedMeta, - ), - ); - } else if (isInserting) { - context.missing(_lastModifiedMeta); - } - return context; - } - - @override - Set<GeneratedColumn> get $primaryKey => {id}; - @override - ArticleDraft map(Map<String, dynamic> data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return ArticleDraft( - id: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}id'], - )!, - title: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}title'], - )!, - description: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}description'], - )!, - content: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}content'], - )!, - visibility: - attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}visibility'], - )!, - lastModified: - attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, - data['${effectivePrefix}last_modified'], - )!, - ); - } - - @override - $ArticleDraftsTable createAlias(String alias) { - return $ArticleDraftsTable(attachedDatabase, alias); - } -} - -class ArticleDraft extends DataClass implements Insertable<ArticleDraft> { - final String id; - final String title; - final String description; - final String content; - final int visibility; - final DateTime lastModified; - const ArticleDraft({ - required this.id, - required this.title, - required this.description, - required this.content, - required this.visibility, - required this.lastModified, - }); - @override - Map<String, Expression> toColumns(bool nullToAbsent) { - final map = <String, Expression>{}; - map['id'] = Variable<String>(id); - map['title'] = Variable<String>(title); - map['description'] = Variable<String>(description); - map['content'] = Variable<String>(content); - map['visibility'] = Variable<int>(visibility); - map['last_modified'] = Variable<DateTime>(lastModified); - return map; - } - - ArticleDraftsCompanion toCompanion(bool nullToAbsent) { - return ArticleDraftsCompanion( - id: Value(id), - title: Value(title), - description: Value(description), - content: Value(content), - visibility: Value(visibility), - lastModified: Value(lastModified), - ); - } - - factory ArticleDraft.fromJson( - Map<String, dynamic> json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return ArticleDraft( - id: serializer.fromJson<String>(json['id']), - title: serializer.fromJson<String>(json['title']), - description: serializer.fromJson<String>(json['description']), - content: serializer.fromJson<String>(json['content']), - visibility: serializer.fromJson<int>(json['visibility']), - lastModified: serializer.fromJson<DateTime>(json['lastModified']), - ); - } - @override - Map<String, dynamic> toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return <String, dynamic>{ - 'id': serializer.toJson<String>(id), - 'title': serializer.toJson<String>(title), - 'description': serializer.toJson<String>(description), - 'content': serializer.toJson<String>(content), - 'visibility': serializer.toJson<int>(visibility), - 'lastModified': serializer.toJson<DateTime>(lastModified), - }; - } - - ArticleDraft copyWith({ - String? id, - String? title, - String? description, - String? content, - int? visibility, - DateTime? lastModified, - }) => ArticleDraft( - id: id ?? this.id, - title: title ?? this.title, - description: description ?? this.description, - content: content ?? this.content, - visibility: visibility ?? this.visibility, - lastModified: lastModified ?? this.lastModified, - ); - ArticleDraft copyWithCompanion(ArticleDraftsCompanion data) { - return ArticleDraft( - id: data.id.present ? data.id.value : this.id, - title: data.title.present ? data.title.value : this.title, - description: - data.description.present ? data.description.value : this.description, - content: data.content.present ? data.content.value : this.content, - visibility: - data.visibility.present ? data.visibility.value : this.visibility, - lastModified: - data.lastModified.present - ? data.lastModified.value - : this.lastModified, - ); - } - - @override - String toString() { - return (StringBuffer('ArticleDraft(') - ..write('id: $id, ') - ..write('title: $title, ') - ..write('description: $description, ') - ..write('content: $content, ') - ..write('visibility: $visibility, ') - ..write('lastModified: $lastModified') - ..write(')')) - .toString(); - } - - @override - int get hashCode => - Object.hash(id, title, description, content, visibility, lastModified); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is ArticleDraft && - other.id == this.id && - other.title == this.title && - other.description == this.description && - other.content == this.content && - other.visibility == this.visibility && - other.lastModified == this.lastModified); -} - -class ArticleDraftsCompanion extends UpdateCompanion<ArticleDraft> { - final Value<String> id; - final Value<String> title; - final Value<String> description; - final Value<String> content; - final Value<int> visibility; - final Value<DateTime> lastModified; - final Value<int> rowid; - const ArticleDraftsCompanion({ - this.id = const Value.absent(), - this.title = const Value.absent(), - this.description = const Value.absent(), - this.content = const Value.absent(), - this.visibility = const Value.absent(), - this.lastModified = const Value.absent(), - this.rowid = const Value.absent(), - }); - ArticleDraftsCompanion.insert({ - required String id, - this.title = const Value.absent(), - this.description = const Value.absent(), - this.content = const Value.absent(), - this.visibility = const Value.absent(), - required DateTime lastModified, - this.rowid = const Value.absent(), - }) : id = Value(id), - lastModified = Value(lastModified); - static Insertable<ArticleDraft> custom({ - Expression<String>? id, - Expression<String>? title, - Expression<String>? description, - Expression<String>? content, - Expression<int>? visibility, - Expression<DateTime>? lastModified, - Expression<int>? rowid, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (title != null) 'title': title, - if (description != null) 'description': description, - if (content != null) 'content': content, - if (visibility != null) 'visibility': visibility, - if (lastModified != null) 'last_modified': lastModified, - if (rowid != null) 'rowid': rowid, - }); - } - - ArticleDraftsCompanion copyWith({ - Value<String>? id, - Value<String>? title, - Value<String>? description, - Value<String>? content, - Value<int>? visibility, - Value<DateTime>? lastModified, - Value<int>? rowid, - }) { - return ArticleDraftsCompanion( - id: id ?? this.id, - title: title ?? this.title, - description: description ?? this.description, - content: content ?? this.content, - visibility: visibility ?? this.visibility, - lastModified: lastModified ?? this.lastModified, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map<String, Expression> toColumns(bool nullToAbsent) { - final map = <String, Expression>{}; - if (id.present) { - map['id'] = Variable<String>(id.value); - } - if (title.present) { - map['title'] = Variable<String>(title.value); - } - if (description.present) { - map['description'] = Variable<String>(description.value); - } - if (content.present) { - map['content'] = Variable<String>(content.value); - } - if (visibility.present) { - map['visibility'] = Variable<int>(visibility.value); - } - if (lastModified.present) { - map['last_modified'] = Variable<DateTime>(lastModified.value); - } - if (rowid.present) { - map['rowid'] = Variable<int>(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('ArticleDraftsCompanion(') - ..write('id: $id, ') - ..write('title: $title, ') - ..write('description: $description, ') - ..write('content: $content, ') - ..write('visibility: $visibility, ') + ..write('post: $post, ') ..write('lastModified: $lastModified, ') ..write('rowid: $rowid') ..write(')')) @@ -1466,16 +841,14 @@ abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $ChatMessagesTable chatMessages = $ChatMessagesTable(this); - late final $ComposeDraftsTable composeDrafts = $ComposeDraftsTable(this); - late final $ArticleDraftsTable articleDrafts = $ArticleDraftsTable(this); + late final $PostDraftsTable postDrafts = $PostDraftsTable(this); @override Iterable<TableInfo<Table, Object?>> get allTables => allSchemaEntities.whereType<TableInfo<Table, Object?>>(); @override List<DatabaseSchemaEntity> get allSchemaEntities => [ chatMessages, - composeDrafts, - articleDrafts, + postDrafts, ]; } @@ -1764,32 +1137,24 @@ typedef $$ChatMessagesTableProcessedTableManager = ChatMessage, PrefetchHooks Function() >; -typedef $$ComposeDraftsTableCreateCompanionBuilder = - ComposeDraftsCompanion Function({ +typedef $$PostDraftsTableCreateCompanionBuilder = + PostDraftsCompanion Function({ required String id, - Value<String> title, - Value<String> description, - Value<String> content, - Value<String> attachmentIds, - Value<int> visibility, + required String post, required DateTime lastModified, Value<int> rowid, }); -typedef $$ComposeDraftsTableUpdateCompanionBuilder = - ComposeDraftsCompanion Function({ +typedef $$PostDraftsTableUpdateCompanionBuilder = + PostDraftsCompanion Function({ Value<String> id, - Value<String> title, - Value<String> description, - Value<String> content, - Value<String> attachmentIds, - Value<int> visibility, + Value<String> post, Value<DateTime> lastModified, Value<int> rowid, }); -class $$ComposeDraftsTableFilterComposer - extends Composer<_$AppDatabase, $ComposeDraftsTable> { - $$ComposeDraftsTableFilterComposer({ +class $$PostDraftsTableFilterComposer + extends Composer<_$AppDatabase, $PostDraftsTable> { + $$PostDraftsTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -1801,28 +1166,8 @@ class $$ComposeDraftsTableFilterComposer builder: (column) => ColumnFilters(column), ); - ColumnFilters<String> get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<String> get description => $composableBuilder( - column: $table.description, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<String> get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<String> get attachmentIds => $composableBuilder( - column: $table.attachmentIds, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<int> get visibility => $composableBuilder( - column: $table.visibility, + ColumnFilters<String> get post => $composableBuilder( + column: $table.post, builder: (column) => ColumnFilters(column), ); @@ -1832,9 +1177,9 @@ class $$ComposeDraftsTableFilterComposer ); } -class $$ComposeDraftsTableOrderingComposer - extends Composer<_$AppDatabase, $ComposeDraftsTable> { - $$ComposeDraftsTableOrderingComposer({ +class $$PostDraftsTableOrderingComposer + extends Composer<_$AppDatabase, $PostDraftsTable> { + $$PostDraftsTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -1846,28 +1191,8 @@ class $$ComposeDraftsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - ColumnOrderings<String> get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<String> get description => $composableBuilder( - column: $table.description, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<String> get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<String> get attachmentIds => $composableBuilder( - column: $table.attachmentIds, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<int> get visibility => $composableBuilder( - column: $table.visibility, + ColumnOrderings<String> get post => $composableBuilder( + column: $table.post, builder: (column) => ColumnOrderings(column), ); @@ -1877,9 +1202,9 @@ class $$ComposeDraftsTableOrderingComposer ); } -class $$ComposeDraftsTableAnnotationComposer - extends Composer<_$AppDatabase, $ComposeDraftsTable> { - $$ComposeDraftsTableAnnotationComposer({ +class $$PostDraftsTableAnnotationComposer + extends Composer<_$AppDatabase, $PostDraftsTable> { + $$PostDraftsTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -1889,26 +1214,8 @@ class $$ComposeDraftsTableAnnotationComposer GeneratedColumn<String> get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn<String> get title => - $composableBuilder(column: $table.title, builder: (column) => column); - - GeneratedColumn<String> get description => $composableBuilder( - column: $table.description, - builder: (column) => column, - ); - - GeneratedColumn<String> get content => - $composableBuilder(column: $table.content, builder: (column) => column); - - GeneratedColumn<String> get attachmentIds => $composableBuilder( - column: $table.attachmentIds, - builder: (column) => column, - ); - - GeneratedColumn<int> get visibility => $composableBuilder( - column: $table.visibility, - builder: (column) => column, - ); + GeneratedColumn<String> get post => + $composableBuilder(column: $table.post, builder: (column) => column); GeneratedColumn<DateTime> get lastModified => $composableBuilder( column: $table.lastModified, @@ -1916,76 +1223,56 @@ class $$ComposeDraftsTableAnnotationComposer ); } -class $$ComposeDraftsTableTableManager +class $$PostDraftsTableTableManager extends RootTableManager< _$AppDatabase, - $ComposeDraftsTable, - ComposeDraft, - $$ComposeDraftsTableFilterComposer, - $$ComposeDraftsTableOrderingComposer, - $$ComposeDraftsTableAnnotationComposer, - $$ComposeDraftsTableCreateCompanionBuilder, - $$ComposeDraftsTableUpdateCompanionBuilder, + $PostDraftsTable, + PostDraft, + $$PostDraftsTableFilterComposer, + $$PostDraftsTableOrderingComposer, + $$PostDraftsTableAnnotationComposer, + $$PostDraftsTableCreateCompanionBuilder, + $$PostDraftsTableUpdateCompanionBuilder, ( - ComposeDraft, - BaseReferences<_$AppDatabase, $ComposeDraftsTable, ComposeDraft>, + PostDraft, + BaseReferences<_$AppDatabase, $PostDraftsTable, PostDraft>, ), - ComposeDraft, + PostDraft, PrefetchHooks Function() > { - $$ComposeDraftsTableTableManager(_$AppDatabase db, $ComposeDraftsTable table) + $$PostDraftsTableTableManager(_$AppDatabase db, $PostDraftsTable table) : super( TableManagerState( db: db, table: table, createFilteringComposer: - () => $$ComposeDraftsTableFilterComposer($db: db, $table: table), + () => $$PostDraftsTableFilterComposer($db: db, $table: table), createOrderingComposer: - () => - $$ComposeDraftsTableOrderingComposer($db: db, $table: table), + () => $$PostDraftsTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: - () => $$ComposeDraftsTableAnnotationComposer( - $db: db, - $table: table, - ), + () => $$PostDraftsTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value<String> id = const Value.absent(), - Value<String> title = const Value.absent(), - Value<String> description = const Value.absent(), - Value<String> content = const Value.absent(), - Value<String> attachmentIds = const Value.absent(), - Value<int> visibility = const Value.absent(), + Value<String> post = const Value.absent(), Value<DateTime> lastModified = const Value.absent(), Value<int> rowid = const Value.absent(), - }) => ComposeDraftsCompanion( + }) => PostDraftsCompanion( id: id, - title: title, - description: description, - content: content, - attachmentIds: attachmentIds, - visibility: visibility, + post: post, lastModified: lastModified, rowid: rowid, ), createCompanionCallback: ({ required String id, - Value<String> title = const Value.absent(), - Value<String> description = const Value.absent(), - Value<String> content = const Value.absent(), - Value<String> attachmentIds = const Value.absent(), - Value<int> visibility = const Value.absent(), + required String post, required DateTime lastModified, Value<int> rowid = const Value.absent(), - }) => ComposeDraftsCompanion.insert( + }) => PostDraftsCompanion.insert( id: id, - title: title, - description: description, - content: content, - attachmentIds: attachmentIds, - visibility: visibility, + post: post, lastModified: lastModified, rowid: rowid, ), @@ -2004,257 +1291,18 @@ class $$ComposeDraftsTableTableManager ); } -typedef $$ComposeDraftsTableProcessedTableManager = +typedef $$PostDraftsTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $ComposeDraftsTable, - ComposeDraft, - $$ComposeDraftsTableFilterComposer, - $$ComposeDraftsTableOrderingComposer, - $$ComposeDraftsTableAnnotationComposer, - $$ComposeDraftsTableCreateCompanionBuilder, - $$ComposeDraftsTableUpdateCompanionBuilder, - ( - ComposeDraft, - BaseReferences<_$AppDatabase, $ComposeDraftsTable, ComposeDraft>, - ), - ComposeDraft, - PrefetchHooks Function() - >; -typedef $$ArticleDraftsTableCreateCompanionBuilder = - ArticleDraftsCompanion Function({ - required String id, - Value<String> title, - Value<String> description, - Value<String> content, - Value<int> visibility, - required DateTime lastModified, - Value<int> rowid, - }); -typedef $$ArticleDraftsTableUpdateCompanionBuilder = - ArticleDraftsCompanion Function({ - Value<String> id, - Value<String> title, - Value<String> description, - Value<String> content, - Value<int> visibility, - Value<DateTime> lastModified, - Value<int> rowid, - }); - -class $$ArticleDraftsTableFilterComposer - extends Composer<_$AppDatabase, $ArticleDraftsTable> { - $$ArticleDraftsTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters<String> get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<String> get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<String> get description => $composableBuilder( - column: $table.description, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<String> get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<int> get visibility => $composableBuilder( - column: $table.visibility, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters<DateTime> get lastModified => $composableBuilder( - column: $table.lastModified, - builder: (column) => ColumnFilters(column), - ); -} - -class $$ArticleDraftsTableOrderingComposer - extends Composer<_$AppDatabase, $ArticleDraftsTable> { - $$ArticleDraftsTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings<String> get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<String> get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<String> get description => $composableBuilder( - column: $table.description, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<String> get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<int> get visibility => $composableBuilder( - column: $table.visibility, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings<DateTime> get lastModified => $composableBuilder( - column: $table.lastModified, - builder: (column) => ColumnOrderings(column), - ); -} - -class $$ArticleDraftsTableAnnotationComposer - extends Composer<_$AppDatabase, $ArticleDraftsTable> { - $$ArticleDraftsTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn<String> get id => - $composableBuilder(column: $table.id, builder: (column) => column); - - GeneratedColumn<String> get title => - $composableBuilder(column: $table.title, builder: (column) => column); - - GeneratedColumn<String> get description => $composableBuilder( - column: $table.description, - builder: (column) => column, - ); - - GeneratedColumn<String> get content => - $composableBuilder(column: $table.content, builder: (column) => column); - - GeneratedColumn<int> get visibility => $composableBuilder( - column: $table.visibility, - builder: (column) => column, - ); - - GeneratedColumn<DateTime> get lastModified => $composableBuilder( - column: $table.lastModified, - builder: (column) => column, - ); -} - -class $$ArticleDraftsTableTableManager - extends - RootTableManager< - _$AppDatabase, - $ArticleDraftsTable, - ArticleDraft, - $$ArticleDraftsTableFilterComposer, - $$ArticleDraftsTableOrderingComposer, - $$ArticleDraftsTableAnnotationComposer, - $$ArticleDraftsTableCreateCompanionBuilder, - $$ArticleDraftsTableUpdateCompanionBuilder, - ( - ArticleDraft, - BaseReferences<_$AppDatabase, $ArticleDraftsTable, ArticleDraft>, - ), - ArticleDraft, - PrefetchHooks Function() - > { - $$ArticleDraftsTableTableManager(_$AppDatabase db, $ArticleDraftsTable table) - : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: - () => $$ArticleDraftsTableFilterComposer($db: db, $table: table), - createOrderingComposer: - () => - $$ArticleDraftsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: - () => $$ArticleDraftsTableAnnotationComposer( - $db: db, - $table: table, - ), - updateCompanionCallback: - ({ - Value<String> id = const Value.absent(), - Value<String> title = const Value.absent(), - Value<String> description = const Value.absent(), - Value<String> content = const Value.absent(), - Value<int> visibility = const Value.absent(), - Value<DateTime> lastModified = const Value.absent(), - Value<int> rowid = const Value.absent(), - }) => ArticleDraftsCompanion( - id: id, - title: title, - description: description, - content: content, - visibility: visibility, - lastModified: lastModified, - rowid: rowid, - ), - createCompanionCallback: - ({ - required String id, - Value<String> title = const Value.absent(), - Value<String> description = const Value.absent(), - Value<String> content = const Value.absent(), - Value<int> visibility = const Value.absent(), - required DateTime lastModified, - Value<int> rowid = const Value.absent(), - }) => ArticleDraftsCompanion.insert( - id: id, - title: title, - description: description, - content: content, - visibility: visibility, - lastModified: lastModified, - rowid: rowid, - ), - withReferenceMapper: - (p0) => - p0 - .map( - (e) => ( - e.readTable(table), - BaseReferences(db, table, e), - ), - ) - .toList(), - prefetchHooksCallback: null, - ), - ); -} - -typedef $$ArticleDraftsTableProcessedTableManager = - ProcessedTableManager< - _$AppDatabase, - $ArticleDraftsTable, - ArticleDraft, - $$ArticleDraftsTableFilterComposer, - $$ArticleDraftsTableOrderingComposer, - $$ArticleDraftsTableAnnotationComposer, - $$ArticleDraftsTableCreateCompanionBuilder, - $$ArticleDraftsTableUpdateCompanionBuilder, - ( - ArticleDraft, - BaseReferences<_$AppDatabase, $ArticleDraftsTable, ArticleDraft>, - ), - ArticleDraft, + $PostDraftsTable, + PostDraft, + $$PostDraftsTableFilterComposer, + $$PostDraftsTableOrderingComposer, + $$PostDraftsTableAnnotationComposer, + $$PostDraftsTableCreateCompanionBuilder, + $$PostDraftsTableUpdateCompanionBuilder, + (PostDraft, BaseReferences<_$AppDatabase, $PostDraftsTable, PostDraft>), + PostDraft, PrefetchHooks Function() >; @@ -2263,8 +1311,6 @@ class $AppDatabaseManager { $AppDatabaseManager(this._db); $$ChatMessagesTableTableManager get chatMessages => $$ChatMessagesTableTableManager(_db, _db.chatMessages); - $$ComposeDraftsTableTableManager get composeDrafts => - $$ComposeDraftsTableTableManager(_db, _db.composeDrafts); - $$ArticleDraftsTableTableManager get articleDrafts => - $$ArticleDraftsTableTableManager(_db, _db.articleDrafts); + $$PostDraftsTableTableManager get postDrafts => + $$PostDraftsTableTableManager(_db, _db.postDrafts); } diff --git a/lib/models/post.dart b/lib/models/post.dart index d1ae308..350c724 100644 --- a/lib/models/post.dart +++ b/lib/models/post.dart @@ -9,36 +9,36 @@ part 'post.g.dart'; sealed class SnPost with _$SnPost { const factory SnPost({ required String id, - required String? title, - required String? description, - required String? language, - required DateTime? editedAt, - required DateTime publishedAt, - required int visibility, - required String? content, - required int type, - required Map<String, dynamic>? meta, - required int viewsUnique, - required int viewsTotal, - required int upvotes, - required int downvotes, - required int repliesCount, - required String? threadedPostId, - required SnPost? threadedPost, - required String? repliedPostId, - required SnPost? repliedPost, - required String? forwardedPostId, - required SnPost? forwardedPost, - required List<SnCloudFile> attachments, - required SnPublisher publisher, + String? title, + String? description, + String? language, + DateTime? editedAt, + @Default(null) DateTime? publishedAt, + @Default(0) int visibility, + String? content, + @Default(0) int type, + Map<String, dynamic>? meta, + @Default(0) int viewsUnique, + @Default(0) int viewsTotal, + @Default(0) int upvotes, + @Default(0) int downvotes, + @Default(0) int repliesCount, + String? threadedPostId, + SnPost? threadedPost, + String? repliedPostId, + SnPost? repliedPost, + String? forwardedPostId, + SnPost? forwardedPost, + @Default([]) List<SnCloudFile> attachments, + @Default(SnPublisher()) SnPublisher publisher, @Default({}) Map<String, int> reactionsCount, - required List<dynamic> reactions, - required List<dynamic> tags, - required List<dynamic> categories, - required List<dynamic> collections, - required DateTime createdAt, - required DateTime updatedAt, - required DateTime? deletedAt, + @Default([]) List<dynamic> reactions, + @Default([]) List<dynamic> tags, + @Default([]) List<dynamic> categories, + @Default([]) List<dynamic> collections, + @Default(null) DateTime? createdAt, + @Default(null) DateTime? updatedAt, + DateTime? deletedAt, @Default(false) bool isTruncated, }) = _SnPost; @@ -48,20 +48,20 @@ sealed class SnPost with _$SnPost { @freezed sealed class SnPublisher with _$SnPublisher { const factory SnPublisher({ - required String id, - required int type, - required String name, - required String nick, + @Default('') String id, + @Default(0) int type, + @Default('') String name, + @Default('') String nick, @Default('') String bio, - required SnCloudFile? picture, - required SnCloudFile? background, - required SnAccount? account, - required String? accountId, - required DateTime createdAt, - required DateTime updatedAt, - required DateTime? deletedAt, - required String? realmId, - required SnVerificationMark? verification, + SnCloudFile? picture, + SnCloudFile? background, + SnAccount? account, + String? accountId, + @Default(null) DateTime? createdAt, + @Default(null) DateTime? updatedAt, + DateTime? deletedAt, + String? realmId, + SnVerificationMark? verification, }) = _SnPublisher; factory SnPublisher.fromJson(Map<String, dynamic> json) => diff --git a/lib/models/post.freezed.dart b/lib/models/post.freezed.dart index ed78355..193127b 100644 --- a/lib/models/post.freezed.dart +++ b/lib/models/post.freezed.dart @@ -16,7 +16,7 @@ T _$identity<T>(T value) => value; /// @nodoc 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 /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -49,7 +49,7 @@ abstract mixin class $SnPostCopyWith<$Res> { factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl; @useResult $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 /// 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( 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?,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?,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,visibility: null == visibility ? _self.visibility : visibility // 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 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 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>,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>,createdAt: null == 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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // 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?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable as bool, )); @@ -156,7 +156,7 @@ $SnPublisherCopyWith<$Res> get publisher { @JsonSerializable() 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); @override final String id; @@ -164,10 +164,10 @@ class _SnPost implements SnPost { @override final String? description; @override final String? language; @override final DateTime? editedAt; -@override final DateTime publishedAt; -@override final int visibility; +@override@JsonKey() final DateTime? publishedAt; +@override@JsonKey() final int visibility; @override final String? content; -@override final int type; +@override@JsonKey() final int type; final Map<String, dynamic>? _meta; @override Map<String, dynamic>? get meta { final value = _meta; @@ -177,11 +177,11 @@ class _SnPost implements SnPost { return EqualUnmodifiableMapView(value); } -@override final int viewsUnique; -@override final int viewsTotal; -@override final int upvotes; -@override final int downvotes; -@override final int repliesCount; +@override@JsonKey() final int viewsUnique; +@override@JsonKey() final int viewsTotal; +@override@JsonKey() final int upvotes; +@override@JsonKey() final int downvotes; +@override@JsonKey() final int repliesCount; @override final String? threadedPostId; @override final SnPost? threadedPost; @override final String? repliedPostId; @@ -189,13 +189,13 @@ class _SnPost implements SnPost { @override final String? forwardedPostId; @override final SnPost? forwardedPost; final List<SnCloudFile> _attachments; -@override List<SnCloudFile> get attachments { +@override@JsonKey() List<SnCloudFile> get attachments { if (_attachments is EqualUnmodifiableListView) return _attachments; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_attachments); } -@override final SnPublisher publisher; +@override@JsonKey() final SnPublisher publisher; final Map<String, int> _reactionsCount; @override@JsonKey() Map<String, int> get reactionsCount { if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount; @@ -204,35 +204,35 @@ class _SnPost implements SnPost { } final List<dynamic> _reactions; -@override List<dynamic> get reactions { +@override@JsonKey() List<dynamic> get reactions { if (_reactions is EqualUnmodifiableListView) return _reactions; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_reactions); } final List<dynamic> _tags; -@override List<dynamic> get tags { +@override@JsonKey() List<dynamic> get tags { if (_tags is EqualUnmodifiableListView) return _tags; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_tags); } final List<dynamic> _categories; -@override List<dynamic> get categories { +@override@JsonKey() List<dynamic> get categories { if (_categories is EqualUnmodifiableListView) return _categories; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_categories); } final List<dynamic> _collections; -@override List<dynamic> get collections { +@override@JsonKey() List<dynamic> get collections { if (_collections is EqualUnmodifiableListView) return _collections; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_collections); } -@override final DateTime createdAt; -@override final DateTime updatedAt; +@override@JsonKey() final DateTime? createdAt; +@override@JsonKey() final DateTime? updatedAt; @override final DateTime? deletedAt; @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; @override @useResult $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 /// 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( 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?,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?,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,visibility: null == visibility ? _self.visibility : visibility // 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 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 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>,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>,createdAt: null == 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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // 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?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable as bool, )); @@ -376,7 +376,7 @@ $SnPublisherCopyWith<$Res> get publisher { /// @nodoc 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 /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -409,7 +409,7 @@ abstract mixin class $SnPublisherCopyWith<$Res> { factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl; @useResult $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 /// 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( 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 @@ -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?,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 String?,createdAt: null == 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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // 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?,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 SnVerificationMark?, @@ -501,20 +501,20 @@ $SnVerificationMarkCopyWith<$Res>? get verification { @JsonSerializable() 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); -@override final String id; -@override final int type; -@override final String name; -@override final String nick; +@override@JsonKey() final String id; +@override@JsonKey() final int type; +@override@JsonKey() final String name; +@override@JsonKey() final String nick; @override@JsonKey() final String bio; @override final SnCloudFile? picture; @override final SnCloudFile? background; @override final SnAccount? account; @override final String? accountId; -@override final DateTime createdAt; -@override final DateTime updatedAt; +@override@JsonKey() final DateTime? createdAt; +@override@JsonKey() final DateTime? updatedAt; @override final DateTime? deletedAt; @override final String? realmId; @override final SnVerificationMark? verification; @@ -552,7 +552,7 @@ abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl; @override @useResult $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 /// 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( 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 @@ -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?,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 String?,createdAt: null == 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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // 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?,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 SnVerificationMark?, diff --git a/lib/models/post.g.dart b/lib/models/post.g.dart index 18a9226..22b2304 100644 --- a/lib/models/post.g.dart +++ b/lib/models/post.g.dart @@ -15,16 +15,19 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( json['edited_at'] == null ? null : DateTime.parse(json['edited_at'] as String), - publishedAt: DateTime.parse(json['published_at'] as String), - visibility: (json['visibility'] as num).toInt(), + publishedAt: + json['published_at'] == null + ? null + : DateTime.parse(json['published_at'] as String), + visibility: (json['visibility'] as num?)?.toInt() ?? 0, 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>?, - viewsUnique: (json['views_unique'] as num).toInt(), - viewsTotal: (json['views_total'] as num).toInt(), - upvotes: (json['upvotes'] as num).toInt(), - downvotes: (json['downvotes'] as num).toInt(), - repliesCount: (json['replies_count'] as num).toInt(), + viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0, + viewsTotal: (json['views_total'] as num?)?.toInt() ?? 0, + upvotes: (json['upvotes'] as num?)?.toInt() ?? 0, + downvotes: (json['downvotes'] as num?)?.toInt() ?? 0, + repliesCount: (json['replies_count'] as num?)?.toInt() ?? 0, threadedPostId: json['threaded_post_id'] as String?, threadedPost: json['threaded_post'] == null @@ -41,21 +44,31 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( ? null : SnPost.fromJson(json['forwarded_post'] as Map<String, dynamic>), attachments: - (json['attachments'] as List<dynamic>) - .map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) - .toList(), - publisher: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>), + (json['attachments'] as List<dynamic>?) + ?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) + .toList() ?? + const [], + publisher: + json['publisher'] == null + ? const SnPublisher() + : SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>), reactionsCount: (json['reactions_count'] as Map<String, dynamic>?)?.map( (k, e) => MapEntry(k, (e as num).toInt()), ) ?? const {}, - reactions: json['reactions'] as List<dynamic>, - tags: json['tags'] as List<dynamic>, - categories: json['categories'] as List<dynamic>, - collections: json['collections'] as List<dynamic>, - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), + reactions: json['reactions'] as List<dynamic>? ?? const [], + tags: json['tags'] as List<dynamic>? ?? const [], + categories: json['categories'] as List<dynamic>? ?? const [], + collections: json['collections'] as List<dynamic>? ?? const [], + createdAt: + 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: json['deleted_at'] == null ? null @@ -69,7 +82,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ 'description': instance.description, 'language': instance.language, 'edited_at': instance.editedAt?.toIso8601String(), - 'published_at': instance.publishedAt.toIso8601String(), + 'published_at': instance.publishedAt?.toIso8601String(), 'visibility': instance.visibility, 'content': instance.content, 'type': instance.type, @@ -92,17 +105,17 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ 'tags': instance.tags, 'categories': instance.categories, 'collections': instance.collections, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(), 'is_truncated': instance.isTruncated, }; _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher( - id: json['id'] as String, - type: (json['type'] as num).toInt(), - name: json['name'] as String, - nick: json['nick'] as String, + id: json['id'] as String? ?? '', + type: (json['type'] as num?)?.toInt() ?? 0, + name: json['name'] as String? ?? '', + nick: json['nick'] as String? ?? '', bio: json['bio'] as String? ?? '', picture: json['picture'] == null @@ -117,8 +130,14 @@ _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher( ? null : SnAccount.fromJson(json['account'] as Map<String, dynamic>), accountId: json['account_id'] as String?, - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), + createdAt: + 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: json['deleted_at'] == null ? null @@ -143,8 +162,8 @@ Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) => 'background': instance.background?.toJson(), 'account': instance.account?.toJson(), 'account_id': instance.accountId, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(), 'realm_id': instance.realmId, 'verification': instance.verification?.toJson(), diff --git a/lib/pods/theme.dart b/lib/pods/theme.dart index 0380245..421ecc1 100644 --- a/lib/pods/theme.dart +++ b/lib/pods/theme.dart @@ -102,6 +102,7 @@ Future<ThemeData> createAppTheme( ), snackBarTheme: SnackBarThemeData( behavior: useM3 ? SnackBarBehavior.floating : SnackBarBehavior.fixed, + width: 560, ), appBarTheme: AppBarTheme( centerTitle: true, diff --git a/lib/screens/account/event_calendar.dart b/lib/screens/account/event_calendar.dart index a4046c5..83093f4 100644 --- a/lib/screens/account/event_calendar.dart +++ b/lib/screens/account/event_calendar.dart @@ -61,7 +61,7 @@ class EventCalanderScreen extends HookConsumerWidget { child: Column( children: [ Card( - margin: EdgeInsets.all(16), + margin: EdgeInsets.only(left: 16, right: 16, top: 16), child: Column( children: [ // Use the reusable EventCalendarWidget @@ -77,7 +77,6 @@ class EventCalanderScreen extends HookConsumerWidget { ), // Add the fortune graph widget - const Divider(height: 1), FortuneGraphWidget( events: events, constrainWidth: true, diff --git a/lib/screens/posts/compose.dart b/lib/screens/posts/compose.dart index 60b057a..c9d738e 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/screens/posts/compose.dart @@ -62,8 +62,6 @@ class PostComposeScreen extends HookConsumerWidget { @QueryParam('type') this.type, }); - - @override Widget build(BuildContext context, WidgetRef ref) { // Determine the compose type: auto-detect from edited post or use query parameter @@ -96,7 +94,7 @@ class PostComposeScreen extends HookConsumerWidget { useEffect(() { if (originalPost == null) { // Only auto-save for new posts, not edits - state.startAutoSave(ref); + state.startAutoSave(ref, postType: 0); } return () => state.stopAutoSave(); }, [state]); @@ -118,14 +116,14 @@ class PostComposeScreen extends HookConsumerWidget { final drafts = ref.read(composeStorageNotifierProvider); if (drafts.isNotEmpty) { 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 - if (!mostRecentDraft.isEmpty) { - state.titleController.text = mostRecentDraft.title; - state.descriptionController.text = mostRecentDraft.description; - state.contentController.text = mostRecentDraft.content; + if (mostRecentDraft.content?.isNotEmpty == true || mostRecentDraft.title?.isNotEmpty == true) { + state.titleController.text = mostRecentDraft.title ?? ''; + state.descriptionController.text = mostRecentDraft.description ?? ''; + state.contentController.text = mostRecentDraft.content ?? ''; state.visibility.value = mostRecentDraft.visibility; } } @@ -162,9 +160,10 @@ class PostComposeScreen extends HookConsumerWidget { Widget buildWideAttachmentGrid() { return GridView.builder( shrinkWrap: true, + padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, + crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, ), @@ -245,17 +244,16 @@ class PostComposeScreen extends HookConsumerWidget { isScrollControlled: true, builder: (context) => DraftManagerSheet( - isArticle: false, onDraftSelected: (draftId) { final draft = ref.read( composeStorageNotifierProvider, )[draftId]; if (draft != null) { - state.titleController.text = draft.title; + state.titleController.text = draft.title ?? ''; state.descriptionController.text = - draft.description; - state.contentController.text = draft.content; + draft.description ?? ''; + state.contentController.text = draft.content ?? ''; state.visibility.value = draft.visibility; } }, @@ -320,7 +318,7 @@ class PostComposeScreen extends HookConsumerWidget { // Main content area Expanded( child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 480), + constraints: const BoxConstraints(maxWidth: 560), child: Row( spacing: 12, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/screens/posts/compose_article.dart b/lib/screens/posts/compose_article.dart index 560a257..421111d 100644 --- a/lib/screens/posts/compose_article.dart +++ b/lib/screens/posts/compose_article.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:developer'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -9,6 +9,7 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/file.dart'; import 'package:island/models/post.dart'; + import 'package:island/screens/creators/publishers.dart'; import 'package:island/services/responsive.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/widgets/post/publishers_modal.dart'; import 'package:island/widgets/post/draft_manager.dart'; + import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -71,7 +73,7 @@ class ArticleComposeScreen extends HookConsumerWidget { if (originalPost == null) { // Only auto-save for new articles, not edits autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) { - _saveArticleDraft(ref, state); + ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1); }); } return () { @@ -79,7 +81,7 @@ class ArticleComposeScreen extends HookConsumerWidget { state.stopAutoSave(); // Save final draft before disposing if (originalPost == null) { - _saveArticleDraft(ref, state); + ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1); } ComposeLogic.dispose(state); autoSaveTimer?.cancel(); @@ -100,17 +102,22 @@ class ArticleComposeScreen extends HookConsumerWidget { useEffect(() { if (originalPost == null) { // Try to load the most recent article draft - final drafts = ref.read(articleStorageNotifierProvider); + final drafts = ref.read(composeStorageNotifierProvider); if (drafts.isNotEmpty) { 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 - if (!mostRecentDraft.isEmpty) { - state.titleController.text = mostRecentDraft.title; - state.descriptionController.text = mostRecentDraft.description; - state.contentController.text = mostRecentDraft.content; + if (mostRecentDraft.content?.isNotEmpty == true || + mostRecentDraft.title?.isNotEmpty == true) { + state.titleController.text = mostRecentDraft.title ?? ''; + state.descriptionController.text = + mostRecentDraft.description ?? ''; + state.contentController.text = mostRecentDraft.content ?? ''; state.visibility.value = mostRecentDraft.visibility; } } @@ -356,7 +363,7 @@ class ArticleComposeScreen extends HookConsumerWidget { return PopScope( onPopInvoked: (_) { if (originalPost == null) { - _saveArticleDraft(ref, state); + ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1); } }, child: AppScaffold( @@ -383,17 +390,17 @@ class ArticleComposeScreen extends HookConsumerWidget { isScrollControlled: true, builder: (context) => DraftManagerSheet( - isArticle: true, onDraftSelected: (draftId) { final draft = ref.read( - articleStorageNotifierProvider, + composeStorageNotifierProvider, )[draftId]; if (draft != null) { - state.titleController.text = draft.title; + state.titleController.text = draft.title ?? ''; state.descriptionController.text = - draft.description; - state.contentController.text = draft.content; + draft.description ?? ''; + state.contentController.text = + draft.content ?? ''; state.visibility.value = draft.visibility; } }, @@ -404,7 +411,7 @@ class ArticleComposeScreen extends HookConsumerWidget { ), IconButton( icon: const Icon(Symbols.save), - onPressed: () => _saveArticleDraft(ref, state), + onPressed: () => ComposeLogic.saveDraft(ref, state, postType: 1), tooltip: 'saveDraft'.tr(), ), IconButton( @@ -524,7 +531,7 @@ class ArticleComposeScreen extends HookConsumerWidget { if (isPaste && isModifierPressed) { ComposeLogic.handlePaste(state); } else if (isSave && isModifierPressed) { - _saveArticleDraft(ref, state); + ComposeLogic.saveDraft(ref, state, postType: 1); } else if (isSubmit && isModifierPressed && !state.submitting.value) { ComposeLogic.performAction( ref, @@ -537,23 +544,5 @@ class ArticleComposeScreen extends HookConsumerWidget { } // 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 - } - } - } diff --git a/lib/services/compose_storage_db.dart b/lib/services/compose_storage_db.dart index fdf8110..121994d 100644 --- a/lib/services/compose_storage_db.dart +++ b/lib/services/compose_storage_db.dart @@ -1,183 +1,16 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:island/database/drift_db.dart'; +import 'package:island/models/post.dart'; import 'package:island/pods/database.dart'; -import 'package:island/services/file.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; +import 'package:riverpod_annotation/riverpod_annotation.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 class ComposeStorageNotifier extends _$ComposeStorageNotifier { @override - Map<String, ComposeDraftModel> build() { + Map<String, SnPost> build() { _loadDrafts(); return {}; } @@ -185,10 +18,9 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier { void _loadDrafts() async { try { final database = ref.read(databaseProvider); - final dbDrafts = await database.getAllComposeDrafts(); - final drafts = <String, ComposeDraftModel>{}; - for (final dbDraft in dbDrafts) { - final draft = ComposeDraftModel.fromDbRow(dbDraft); + final dbDrafts = await database.getAllPostDrafts(); + final drafts = <String, SnPost>{}; + for (final draft in dbDrafts) { drafts[draft.id] = draft; } state = drafts; @@ -198,52 +30,22 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier { } } - Future<void> saveDraft(ComposeDraftModel draft) async { - if (draft.isEmpty) { - 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(), - ); + Future<void> saveDraft(SnPost draft) async { + final updatedDraft = draft.copyWith(updatedAt: DateTime.now()); state = {...state, updatedDraft.id: updatedDraft}; - + try { 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) { // Revert state on error - final newState = Map<String, ComposeDraftModel>.from(state); + final newState = Map<String, SnPost>.from(state); newState.remove(updatedDraft.id); state = newState; rethrow; @@ -252,13 +54,13 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier { Future<void> deleteDraft(String id) async { final oldDraft = state[id]; - final newState = Map<String, ComposeDraftModel>.from(state); + final newState = Map<String, SnPost>.from(state); newState.remove(id); state = newState; - + try { final database = ref.read(databaseProvider); - await database.deleteComposeDraft(id); + await database.deletePostDraft(id); } catch (e) { // Revert state on error if (oldDraft != null) { @@ -268,22 +70,22 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier { } } - ComposeDraftModel? getDraft(String id) { + SnPost? getDraft(String id) { return state[id]; } - List<ComposeDraftModel> getAllDrafts() { + List<SnPost> getAllDrafts() { 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; } Future<void> clearAllDrafts() async { state = {}; - + try { final database = ref.read(databaseProvider); - await database.clearAllComposeDrafts(); + await database.clearAllPostDrafts(); } catch (e) { // If clearing fails, we might want to reload from database _loadDrafts(); @@ -291,90 +93,3 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier { } } } - -@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) { - // If clearing fails, we might want to reload from database - _loadDrafts(); - rethrow; - } - } -} \ No newline at end of file diff --git a/lib/services/compose_storage_db.g.dart b/lib/services/compose_storage_db.g.dart index 654fafe..051438a 100644 --- a/lib/services/compose_storage_db.g.dart +++ b/lib/services/compose_storage_db.g.dart @@ -7,13 +7,13 @@ part of 'compose_storage_db.dart'; // ************************************************************************** String _$composeStorageNotifierHash() => - r'fcdb006dca44d30916a20804922e93d0caad49ca'; + r'3de7a01a93d999d45a32fb68617b77f194589686'; /// See also [ComposeStorageNotifier]. @ProviderFor(ComposeStorageNotifier) final composeStorageNotifierProvider = AutoDisposeNotifierProvider< ComposeStorageNotifier, - Map<String, ComposeDraftModel> + Map<String, SnPost> >.internal( ComposeStorageNotifier.new, name: r'composeStorageNotifierProvider', @@ -25,28 +25,6 @@ final composeStorageNotifierProvider = AutoDisposeNotifierProvider< allTransitiveDependencies: null, ); -typedef _$ComposeStorageNotifier = - 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>>; +typedef _$ComposeStorageNotifier = AutoDisposeNotifier<Map<String, SnPost>>; // 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 diff --git a/lib/widgets/post/compose_shared.dart b/lib/widgets/post/compose_shared.dart index 238e90c..8d25b3c 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/widgets/post/compose_shared.dart @@ -1,7 +1,6 @@ -import 'dart:developer'; - import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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:pasteboard/pasteboard.dart'; import 'dart:async'; +import 'dart:developer'; class ComposeState { final ValueNotifier<List<UniversalFile>> attachments; @@ -40,10 +40,10 @@ class ComposeState { required this.draftId, }); - void startAutoSave(WidgetRef ref) { + void startAutoSave(WidgetRef ref, {int postType = 0}) { _autoSaveTimer?.cancel(); _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( - attachments: ValueNotifier<List<UniversalFile>>([]), + attachments: ValueNotifier<List<UniversalFile>>( + draft.attachments.map((e) => UniversalFile.fromAttachment(e)).toList(), + ), titleController: TextEditingController(text: draft.title), descriptionController: TextEditingController(text: draft.description), 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) { + return; // Don't save empty posts + } - static Future<void> saveDraft(WidgetRef ref, ComposeState state) async { 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 + return; } - final draft = ComposeDraftModel( + // 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, title: state.titleController.text, description: state.descriptionController.text, - content: state.contentController.text, - attachments: state.attachments.value, + language: null, + editedAt: null, + publishedAt: DateTime.now(), 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); } catch (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 { return ref .read(composeStorageNotifierProvider.notifier) @@ -282,6 +502,20 @@ class ComposeLogic { }) async { 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 { state.submitting.value = true; @@ -329,7 +563,7 @@ class ComposeLogic { if (postType == 1) { // Delete article draft await ref - .read(articleStorageNotifierProvider.notifier) + .read(composeStorageNotifierProvider.notifier) .deleteDraft(state.draftId); } else { // Delete regular post draft @@ -381,7 +615,7 @@ class ComposeLogic { if (isPaste && isModifierPressed) { handlePaste(state); } else if (isSave && isModifierPressed) { - saveDraft(ref, state); + saveDraftManually(ref, state, context); } else if (isSubmit && isModifierPressed && !state.submitting.value) { performAction( ref, diff --git a/lib/widgets/post/draft_manager.dart b/lib/widgets/post/draft_manager.dart index 1a915ec..0784d03 100644 --- a/lib/widgets/post/draft_manager.dart +++ b/lib/widgets/post/draft_manager.dart @@ -7,208 +7,168 @@ import 'package:island/services/compose_storage_db.dart'; import 'package:material_symbols_icons/symbols.dart'; class DraftManagerSheet extends HookConsumerWidget { - final bool isArticle; final Function(String draftId)? onDraftSelected; - const DraftManagerSheet({ - super.key, - this.isArticle = false, - this.onDraftSelected, - }); + const DraftManagerSheet({super.key, this.onDraftSelected}); @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; + final isLoading = useState(true); - final drafts = - isArticle - ? ref.watch(articleStorageNotifierProvider) - : ref.watch(composeStorageNotifierProvider); + final drafts = ref.watch(composeStorageNotifierProvider); - final sortedDrafts = useMemoized(() { - if (isArticle) { - final draftList = drafts.values.cast<ArticleDraftModel>().toList(); - draftList.sort((a, b) => b.lastModified.compareTo(a.lastModified)); - return draftList; - } else { - final draftList = drafts.values.cast<ComposeDraftModel>().toList(); - draftList.sort((a, b) => b.lastModified.compareTo(a.lastModified)); - return draftList; - } + // Track loading state based on drafts being loaded + useEffect(() { + // Set loading to false after drafts are loaded + // We consider drafts loaded when the provider has been initialized + Future.microtask(() { + if (isLoading.value) { + isLoading.value = false; + } + }); + return null; }, [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( - appBar: AppBar( - title: Text(isArticle ? 'articleDrafts'.tr() : 'postDrafts'.tr()), - ), - body: Column( - children: [ - if (sortedDrafts.isEmpty) - Expanded( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Symbols.draft, - size: 64, - color: colorScheme.onSurface.withOpacity(0.3), + appBar: AppBar(title: Text('drafts'.tr())), + body: + isLoading.value + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + if (sortedDrafts.isEmpty) + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Symbols.draft, + size: 64, + color: colorScheme.onSurface.withOpacity(0.3), + ), + const Gap(16), + Text( + 'noDrafts'.tr(), + style: theme.textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface.withOpacity(0.6), + ), + ), + ], + ), + ), + ) + else + Expanded( + child: ListView.builder( + itemCount: sortedDrafts.length, + itemBuilder: (context, index) { + final draft = sortedDrafts[index]; + return _DraftItem( + draft: draft, + onTap: () { + Navigator.of(context).pop(); + onDraftSelected?.call(draft.id); + }, + onDelete: () async { + await ref + .read(composeStorageNotifierProvider.notifier) + .deleteDraft(draft.id); + }, + ); + }, + ), ), - const Gap(16), - Text( - 'noDrafts'.tr(), - style: theme.textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurface.withOpacity(0.6), + if (sortedDrafts.isNotEmpty) ...[ + const Divider(), + Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () async { + final confirmed = await showDialog<bool>( + context: context, + builder: + (context) => AlertDialog( + title: Text('clearAllDrafts'.tr()), + content: Text( + 'clearAllDraftsConfirm'.tr(), + ), + actions: [ + TextButton( + onPressed: + () => Navigator.of( + context, + ).pop(false), + child: Text('cancel'.tr()), + ), + TextButton( + onPressed: + () => Navigator.of( + context, + ).pop(true), + child: Text('confirm'.tr()), + ), + ], + ), + ); + + if (confirmed == true) { + await ref + .read( + composeStorageNotifierProvider.notifier, + ) + .clearAllDrafts(); + } + }, + icon: const Icon(Symbols.delete_sweep), + label: Text('clearAll'.tr()), + ), + ), + ], ), ), ], - ), - ), - ) - else - Expanded( - child: ListView.builder( - itemCount: sortedDrafts.length, - itemBuilder: (context, index) { - final draft = sortedDrafts[index]; - return _DraftItem( - draft: draft, - isArticle: isArticle, - onTap: () { - Navigator.of(context).pop(); - final draftId = - isArticle - ? (draft as ArticleDraftModel).id - : (draft as ComposeDraftModel).id; - onDraftSelected?.call(draftId); - }, - 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 - .read(composeStorageNotifierProvider.notifier) - .deleteDraft(draftId); - } - }, - ); - }, - ), - ), - if (sortedDrafts.isNotEmpty) ...[ - const Divider(), - Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Expanded( - child: OutlinedButton.icon( - onPressed: () async { - final confirmed = await showDialog<bool>( - context: context, - builder: - (context) => AlertDialog( - title: Text('clearAllDrafts'.tr()), - content: Text('clearAllDraftsConfirm'.tr()), - actions: [ - TextButton( - onPressed: - () => Navigator.of(context).pop(false), - child: Text('cancel'.tr()), - ), - TextButton( - onPressed: - () => Navigator.of(context).pop(true), - child: Text('confirm'.tr()), - ), - ], - ), - ); - - if (confirmed == true) { - if (isArticle) { - await ref - .read(articleStorageNotifierProvider.notifier) - .clearAllDrafts(); - } else { - await ref - .read(composeStorageNotifierProvider.notifier) - .clearAllDrafts(); - } - } - }, - icon: const Icon(Symbols.delete_sweep), - label: Text('clearAll'.tr()), - ), - ), ], ), - ), - ], - ], - ), ); } } class _DraftItem extends StatelessWidget { - final dynamic draft; // ComposeDraft or ArticleDraft - final bool isArticle; + final dynamic draft; final VoidCallback? onTap; final VoidCallback? onDelete; - const _DraftItem({ - required this.draft, - required this.isArticle, - this.onTap, - this.onDelete, - }); + const _DraftItem({required this.draft, this.onTap, this.onDelete}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; - final String title; - final String content; - 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 title = draft.title ?? 'untitled'.tr(); + final content = draft.content ?? (draft.description ?? 'noContent'.tr()); final preview = content.length > 100 ? '${content.substring(0, 100)}...' : content; - final timeAgo = _formatTimeAgo(lastModified); + final timeAgo = _formatTimeAgo(draft.updatedAt!); + final visibility = _parseVisibility(draft.visibility); return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), @@ -223,7 +183,7 @@ class _DraftItem extends StatelessWidget { Row( children: [ Icon( - isArticle ? Symbols.article : Symbols.post_add, + draft.type == 1 ? Symbols.article : Symbols.post_add, size: 20, color: colorScheme.primary, ), @@ -316,7 +276,7 @@ class _DraftItem extends StatelessWidget { } } - String _parseArticleVisibility(int visibility) { + String _parseVisibility(int visibility) { switch (visibility) { case 0: return 'public'.tr(); diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index d52c6f4..9732c05 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -162,8 +162,8 @@ class PostItem extends HookConsumerWidget { Spacer(), Text( isFullPost - ? item.publishedAt.formatSystem() - : item.publishedAt.formatRelative(context), + ? item.publishedAt?.formatSystem() ?? '' + : item.publishedAt?.formatRelative(context) ?? '', ).fontSize(11).alignment(Alignment.bottomRight), const Gap(4), ], @@ -213,12 +213,14 @@ class PostItem extends HookConsumerWidget { content: item.content!, linesMargin: item.type == 0 - ? EdgeInsets.only(bottom: 4) + ? EdgeInsets.only(bottom: 8) : null, ), // Show truncation hint if post is truncated if (item.isTruncated && !isFullPost) - _PostTruncateHint(), + _PostTruncateHint().padding( + bottom: item.attachments.isNotEmpty ? 8 : null, + ), if ((item.repliedPost != null || item.forwardedPost != null) && showReferencePost) @@ -234,7 +236,7 @@ class PostItem extends HookConsumerWidget { MediaQuery.of(context).size.width * 0.9, kWideScreenWidth - 160, ), - ).padding(top: 4), + ), // Render embed links if (item.meta?['embeds'] != null) ...((item.meta!['embeds'] as List<dynamic>) @@ -248,7 +250,8 @@ class PostItem extends HookConsumerWidget { MediaQuery.of(context).size.width * 0.85, 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; return Container( - margin: const EdgeInsets.only(top: 8, bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5), diff --git a/lib/widgets/post/post_item_creator.dart b/lib/widgets/post/post_item_creator.dart index d27cf48..380cfd2 100644 --- a/lib/widgets/post/post_item_creator.dart +++ b/lib/widgets/post/post_item_creator.dart @@ -153,7 +153,7 @@ class PostItemCreator extends HookConsumerWidget { ), const Gap(8), Text( - item.publishedAt.formatSystem(), + item.publishedAt?.formatSystem() ?? '', style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.secondary, @@ -291,7 +291,7 @@ class PostItemCreator extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Created: ${item.createdAt.formatSystem()}', + 'Created: ${item.createdAt?.formatSystem() ?? ''}', style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.secondary,