Reworked post draft

This commit is contained in:
2025-09-08 22:25:54 +08:00
parent 541900673a
commit aa648fec62
10 changed files with 651 additions and 237 deletions

View File

@@ -471,6 +471,8 @@
"close": "Close",
"drafts": "Drafts",
"noDrafts": "No drafts yet",
"searchDrafts": "Search drafts...",
"noSearchResults": "No search results",
"articleDrafts": "Article drafts",
"postDrafts": "Post drafts",
"saveDraft": "Save draft",

View File

@@ -2,8 +2,15 @@ import 'package:drift/drift.dart';
class PostDrafts extends Table {
TextColumn get id => text()();
TextColumn get post => text()(); // Store SnPost model as JSON string
// Searchable fields stored separately for performance
TextColumn get title => text().nullable()();
TextColumn get description => text().nullable()();
TextColumn get content => text().nullable()();
IntColumn get visibility => integer().withDefault(const Constant(0))();
IntColumn get type => integer().withDefault(const Constant(0))();
DateTimeColumn get lastModified => dateTime()();
// Full post data stored as JSON for complete restoration
TextColumn get postData => text()();
@override
Set<Column> get primaryKey => {id};

View File

@@ -12,7 +12,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase(super.e);
@override
int get schemaVersion => 4;
int get schemaVersion => 6;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -28,9 +28,67 @@ class AppDatabase extends _$AppDatabase {
// Drop old draft tables if they exist
await m.createTable(postDrafts);
}
if (from < 6) {
// Migrate from old schema to new schema with separate searchable fields
await _migrateToVersion6(m);
}
},
);
Future<void> _migrateToVersion6(Migrator m) async {
// Rename existing table to old if it exists
try {
await customStatement(
'ALTER TABLE post_drafts RENAME TO post_drafts_old',
);
} catch (e) {
// Table might not exist
}
// Drop the table
await customStatement('DROP TABLE IF EXISTS post_drafts');
// Create new table
await m.createTable(postDrafts);
// Migrate existing data if any
try {
final oldDrafts =
await customSelect(
'SELECT id, post, lastModified FROM post_drafts_old',
readsFrom: {postDrafts},
).get();
for (final row in oldDrafts) {
final postJson = row.read<String>('post');
final id = row.read<String>('id');
final lastModified = row.read<DateTime>('lastModified');
if (postJson.isNotEmpty) {
final post = SnPost.fromJson(jsonDecode(postJson));
await into(postDrafts).insert(
PostDraftsCompanion(
id: Value(id),
title: Value(post.title),
description: Value(post.description),
content: Value(post.content),
visibility: Value(post.visibility),
type: Value(post.type),
lastModified: Value(lastModified),
postData: Value(postJson),
),
);
}
}
// Drop old table
await customStatement('DROP TABLE IF EXISTS post_drafts_old');
} catch (e) {
// If migration fails, just recreate the table
await m.createTable(postDrafts);
}
}
// Methods for chat messages
Future<List<ChatMessage>> getMessagesForRoom(
String roomId, {
@@ -69,7 +127,9 @@ class AppDatabase extends _$AppDatabase {
}
Future<int> getTotalMessagesForRoom(String roomId) {
return (select(chatMessages)..where((m) => m.roomId.equals(roomId))).get().then((list) => list.length);
return (select(
chatMessages,
)..where((m) => m.roomId.equals(roomId))).get().then((list) => list.length);
}
Future<List<LocalChatMessage>> searchMessages(
@@ -85,10 +145,6 @@ class AppDatabase extends _$AppDatabase {
..where((m) => m.content.like('%${query.toLowerCase()}%'));
}
final messages =
await (selectStatement
..orderBy([(m) => OrderingTerm.desc(m.createdAt)]))
@@ -129,10 +185,31 @@ class AppDatabase extends _$AppDatabase {
Future<List<SnPost>> getAllPostDrafts() async {
final drafts = await select(postDrafts).get();
return drafts
.map((draft) => SnPost.fromJson(jsonDecode(draft.post)))
.map((draft) => SnPost.fromJson(jsonDecode(draft.postData)))
.toList();
}
Future<List<PostDraft>> getAllPostDraftRecords() async {
return await select(postDrafts).get();
}
Future<List<PostDraft>> searchPostDrafts(String query) async {
if (query.isEmpty) {
return await select(postDrafts).get();
}
final searchTerm = '%${query.toLowerCase()}%';
return await (select(postDrafts)
..where(
(draft) =>
draft.title.like(searchTerm) |
draft.description.like(searchTerm) |
draft.content.like(searchTerm),
)
..orderBy([(draft) => OrderingTerm.desc(draft.lastModified)]))
.get();
}
Future<void> addPostDraft(PostDraftsCompanion entry) async {
await into(postDrafts).insert(entry, mode: InsertMode.replace);
}
@@ -144,4 +221,9 @@ class AppDatabase extends _$AppDatabase {
Future<void> clearAllPostDrafts() async {
await delete(postDrafts).go();
}
Future<PostDraft?> getPostDraftById(String id) async {
return await (select(postDrafts)
..where((tbl) => tbl.id.equals(id))).getSingleOrNull();
}
}

View File

@@ -584,14 +584,58 @@ class $PostDraftsTable extends PostDrafts
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _postMeta = const VerificationMeta('post');
static const VerificationMeta _titleMeta = const VerificationMeta('title');
@override
late final GeneratedColumn<String> post = GeneratedColumn<String>(
'post',
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _descriptionMeta = const VerificationMeta(
'description',
);
@override
late final GeneratedColumn<String> description = GeneratedColumn<String>(
'description',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _contentMeta = const VerificationMeta(
'content',
);
@override
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'content',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _visibilityMeta = const VerificationMeta(
'visibility',
);
@override
late final GeneratedColumn<int> visibility = GeneratedColumn<int>(
'visibility',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0),
);
static const VerificationMeta _typeMeta = const VerificationMeta('type');
@override
late final GeneratedColumn<int> type = GeneratedColumn<int>(
'type',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0),
);
static const VerificationMeta _lastModifiedMeta = const VerificationMeta(
'lastModified',
@@ -604,8 +648,28 @@ class $PostDraftsTable extends PostDrafts
type: DriftSqlType.dateTime,
requiredDuringInsert: true,
);
static const VerificationMeta _postDataMeta = const VerificationMeta(
'postData',
);
@override
List<GeneratedColumn> get $columns => [id, post, lastModified];
late final GeneratedColumn<String> postData = GeneratedColumn<String>(
'post_data',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
@override
List<GeneratedColumn> get $columns => [
id,
title,
description,
content,
visibility,
type,
lastModified,
postData,
];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -623,13 +687,38 @@ class $PostDraftsTable extends PostDrafts
} else if (isInserting) {
context.missing(_idMeta);
}
if (data.containsKey('post')) {
if (data.containsKey('title')) {
context.handle(
_postMeta,
post.isAcceptableOrUnknown(data['post']!, _postMeta),
_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('type')) {
context.handle(
_typeMeta,
type.isAcceptableOrUnknown(data['type']!, _typeMeta),
);
} else if (isInserting) {
context.missing(_postMeta);
}
if (data.containsKey('last_modified')) {
context.handle(
@@ -642,6 +731,14 @@ class $PostDraftsTable extends PostDrafts
} else if (isInserting) {
context.missing(_lastModifiedMeta);
}
if (data.containsKey('post_data')) {
context.handle(
_postDataMeta,
postData.isAcceptableOrUnknown(data['post_data']!, _postDataMeta),
);
} else if (isInserting) {
context.missing(_postDataMeta);
}
return context;
}
@@ -656,16 +753,38 @@ class $PostDraftsTable extends PostDrafts
DriftSqlType.string,
data['${effectivePrefix}id'],
)!,
post:
attachedDatabase.typeMapping.read(
title: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}post'],
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'],
)!,
type:
attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}type'],
)!,
lastModified:
attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}last_modified'],
)!,
postData:
attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}post_data'],
)!,
);
}
@@ -677,27 +796,60 @@ class $PostDraftsTable extends PostDrafts
class PostDraft extends DataClass implements Insertable<PostDraft> {
final String id;
final String post;
final String? title;
final String? description;
final String? content;
final int visibility;
final int type;
final DateTime lastModified;
final String postData;
const PostDraft({
required this.id,
required this.post,
this.title,
this.description,
this.content,
required this.visibility,
required this.type,
required this.lastModified,
required this.postData,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<String>(id);
map['post'] = Variable<String>(post);
if (!nullToAbsent || title != null) {
map['title'] = Variable<String>(title);
}
if (!nullToAbsent || description != null) {
map['description'] = Variable<String>(description);
}
if (!nullToAbsent || content != null) {
map['content'] = Variable<String>(content);
}
map['visibility'] = Variable<int>(visibility);
map['type'] = Variable<int>(type);
map['last_modified'] = Variable<DateTime>(lastModified);
map['post_data'] = Variable<String>(postData);
return map;
}
PostDraftsCompanion toCompanion(bool nullToAbsent) {
return PostDraftsCompanion(
id: Value(id),
post: Value(post),
title:
title == null && nullToAbsent ? const Value.absent() : Value(title),
description:
description == null && nullToAbsent
? const Value.absent()
: Value(description),
content:
content == null && nullToAbsent
? const Value.absent()
: Value(content),
visibility: Value(visibility),
type: Value(type),
lastModified: Value(lastModified),
postData: Value(postData),
);
}
@@ -708,8 +860,13 @@ class PostDraft extends DataClass implements Insertable<PostDraft> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return PostDraft(
id: serializer.fromJson<String>(json['id']),
post: serializer.fromJson<String>(json['post']),
title: serializer.fromJson<String?>(json['title']),
description: serializer.fromJson<String?>(json['description']),
content: serializer.fromJson<String?>(json['content']),
visibility: serializer.fromJson<int>(json['visibility']),
type: serializer.fromJson<int>(json['type']),
lastModified: serializer.fromJson<DateTime>(json['lastModified']),
postData: serializer.fromJson<String>(json['postData']),
);
}
@override
@@ -717,25 +874,50 @@ class PostDraft extends DataClass implements Insertable<PostDraft> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'post': serializer.toJson<String>(post),
'title': serializer.toJson<String?>(title),
'description': serializer.toJson<String?>(description),
'content': serializer.toJson<String?>(content),
'visibility': serializer.toJson<int>(visibility),
'type': serializer.toJson<int>(type),
'lastModified': serializer.toJson<DateTime>(lastModified),
'postData': serializer.toJson<String>(postData),
};
}
PostDraft copyWith({String? id, String? post, DateTime? lastModified}) =>
PostDraft(
PostDraft copyWith({
String? id,
Value<String?> title = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<String?> content = const Value.absent(),
int? visibility,
int? type,
DateTime? lastModified,
String? postData,
}) => PostDraft(
id: id ?? this.id,
post: post ?? this.post,
title: title.present ? title.value : this.title,
description: description.present ? description.value : this.description,
content: content.present ? content.value : this.content,
visibility: visibility ?? this.visibility,
type: type ?? this.type,
lastModified: lastModified ?? this.lastModified,
postData: postData ?? this.postData,
);
PostDraft copyWithCompanion(PostDraftsCompanion data) {
return PostDraft(
id: data.id.present ? data.id.value : this.id,
post: data.post.present ? data.post.value : this.post,
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,
type: data.type.present ? data.type.value : this.type,
lastModified:
data.lastModified.present
? data.lastModified.value
: this.lastModified,
postData: data.postData.present ? data.postData.value : this.postData,
);
}
@@ -743,66 +925,120 @@ class PostDraft extends DataClass implements Insertable<PostDraft> {
String toString() {
return (StringBuffer('PostDraft(')
..write('id: $id, ')
..write('post: $post, ')
..write('lastModified: $lastModified')
..write('title: $title, ')
..write('description: $description, ')
..write('content: $content, ')
..write('visibility: $visibility, ')
..write('type: $type, ')
..write('lastModified: $lastModified, ')
..write('postData: $postData')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, post, lastModified);
int get hashCode => Object.hash(
id,
title,
description,
content,
visibility,
type,
lastModified,
postData,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is PostDraft &&
other.id == this.id &&
other.post == this.post &&
other.lastModified == this.lastModified);
other.title == this.title &&
other.description == this.description &&
other.content == this.content &&
other.visibility == this.visibility &&
other.type == this.type &&
other.lastModified == this.lastModified &&
other.postData == this.postData);
}
class PostDraftsCompanion extends UpdateCompanion<PostDraft> {
final Value<String> id;
final Value<String> post;
final Value<String?> title;
final Value<String?> description;
final Value<String?> content;
final Value<int> visibility;
final Value<int> type;
final Value<DateTime> lastModified;
final Value<String> postData;
final Value<int> rowid;
const PostDraftsCompanion({
this.id = const Value.absent(),
this.post = const Value.absent(),
this.title = const Value.absent(),
this.description = const Value.absent(),
this.content = const Value.absent(),
this.visibility = const Value.absent(),
this.type = const Value.absent(),
this.lastModified = const Value.absent(),
this.postData = const Value.absent(),
this.rowid = const Value.absent(),
});
PostDraftsCompanion.insert({
required String id,
required String post,
this.title = const Value.absent(),
this.description = const Value.absent(),
this.content = const Value.absent(),
this.visibility = const Value.absent(),
this.type = const Value.absent(),
required DateTime lastModified,
required String postData,
this.rowid = const Value.absent(),
}) : id = Value(id),
post = Value(post),
lastModified = Value(lastModified);
lastModified = Value(lastModified),
postData = Value(postData);
static Insertable<PostDraft> custom({
Expression<String>? id,
Expression<String>? post,
Expression<String>? title,
Expression<String>? description,
Expression<String>? content,
Expression<int>? visibility,
Expression<int>? type,
Expression<DateTime>? lastModified,
Expression<String>? postData,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (post != null) 'post': post,
if (title != null) 'title': title,
if (description != null) 'description': description,
if (content != null) 'content': content,
if (visibility != null) 'visibility': visibility,
if (type != null) 'type': type,
if (lastModified != null) 'last_modified': lastModified,
if (postData != null) 'post_data': postData,
if (rowid != null) 'rowid': rowid,
});
}
PostDraftsCompanion copyWith({
Value<String>? id,
Value<String>? post,
Value<String?>? title,
Value<String?>? description,
Value<String?>? content,
Value<int>? visibility,
Value<int>? type,
Value<DateTime>? lastModified,
Value<String>? postData,
Value<int>? rowid,
}) {
return PostDraftsCompanion(
id: id ?? this.id,
post: post ?? this.post,
title: title ?? this.title,
description: description ?? this.description,
content: content ?? this.content,
visibility: visibility ?? this.visibility,
type: type ?? this.type,
lastModified: lastModified ?? this.lastModified,
postData: postData ?? this.postData,
rowid: rowid ?? this.rowid,
);
}
@@ -813,12 +1049,27 @@ class PostDraftsCompanion extends UpdateCompanion<PostDraft> {
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (post.present) {
map['post'] = Variable<String>(post.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 (type.present) {
map['type'] = Variable<int>(type.value);
}
if (lastModified.present) {
map['last_modified'] = Variable<DateTime>(lastModified.value);
}
if (postData.present) {
map['post_data'] = Variable<String>(postData.value);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
@@ -829,8 +1080,13 @@ class PostDraftsCompanion extends UpdateCompanion<PostDraft> {
String toString() {
return (StringBuffer('PostDraftsCompanion(')
..write('id: $id, ')
..write('post: $post, ')
..write('title: $title, ')
..write('description: $description, ')
..write('content: $content, ')
..write('visibility: $visibility, ')
..write('type: $type, ')
..write('lastModified: $lastModified, ')
..write('postData: $postData, ')
..write('rowid: $rowid')
..write(')'))
.toString();
@@ -1140,15 +1396,25 @@ typedef $$ChatMessagesTableProcessedTableManager =
typedef $$PostDraftsTableCreateCompanionBuilder =
PostDraftsCompanion Function({
required String id,
required String post,
Value<String?> title,
Value<String?> description,
Value<String?> content,
Value<int> visibility,
Value<int> type,
required DateTime lastModified,
required String postData,
Value<int> rowid,
});
typedef $$PostDraftsTableUpdateCompanionBuilder =
PostDraftsCompanion Function({
Value<String> id,
Value<String> post,
Value<String?> title,
Value<String?> description,
Value<String?> content,
Value<int> visibility,
Value<int> type,
Value<DateTime> lastModified,
Value<String> postData,
Value<int> rowid,
});
@@ -1166,8 +1432,28 @@ class $$PostDraftsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get post => $composableBuilder(
column: $table.post,
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<int> get type => $composableBuilder(
column: $table.type,
builder: (column) => ColumnFilters(column),
);
@@ -1175,6 +1461,11 @@ class $$PostDraftsTableFilterComposer
column: $table.lastModified,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get postData => $composableBuilder(
column: $table.postData,
builder: (column) => ColumnFilters(column),
);
}
class $$PostDraftsTableOrderingComposer
@@ -1191,8 +1482,28 @@ class $$PostDraftsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get post => $composableBuilder(
column: $table.post,
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<int> get type => $composableBuilder(
column: $table.type,
builder: (column) => ColumnOrderings(column),
);
@@ -1200,6 +1511,11 @@ class $$PostDraftsTableOrderingComposer
column: $table.lastModified,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get postData => $composableBuilder(
column: $table.postData,
builder: (column) => ColumnOrderings(column),
);
}
class $$PostDraftsTableAnnotationComposer
@@ -1214,13 +1530,32 @@ class $$PostDraftsTableAnnotationComposer
GeneratedColumn<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get post =>
$composableBuilder(column: $table.post, 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<int> get type =>
$composableBuilder(column: $table.type, builder: (column) => column);
GeneratedColumn<DateTime> get lastModified => $composableBuilder(
column: $table.lastModified,
builder: (column) => column,
);
GeneratedColumn<String> get postData =>
$composableBuilder(column: $table.postData, builder: (column) => column);
}
class $$PostDraftsTableTableManager
@@ -1255,25 +1590,45 @@ class $$PostDraftsTableTableManager
updateCompanionCallback:
({
Value<String> id = const Value.absent(),
Value<String> post = 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<int> type = const Value.absent(),
Value<DateTime> lastModified = const Value.absent(),
Value<String> postData = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => PostDraftsCompanion(
id: id,
post: post,
title: title,
description: description,
content: content,
visibility: visibility,
type: type,
lastModified: lastModified,
postData: postData,
rowid: rowid,
),
createCompanionCallback:
({
required String id,
required String post,
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<int> type = const Value.absent(),
required DateTime lastModified,
required String postData,
Value<int> rowid = const Value.absent(),
}) => PostDraftsCompanion.insert(
id: id,
post: post,
title: title,
description: description,
content: content,
visibility: visibility,
type: type,
lastModified: lastModified,
postData: postData,
rowid: rowid,
),
withReferenceMapper:

View File

@@ -128,14 +128,6 @@ class ArticleComposeScreen extends HookConsumerWidget {
return null;
}, []);
// Auto-save cleanup
useEffect(() {
return () {
state.stopAutoSave();
ComposeLogic.dispose(state);
};
}, [state]);
// Helper methods
void showSettingsSheet() {
showModalBottomSheet(
@@ -182,6 +174,12 @@ class ArticleComposeScreen extends HookConsumerWidget {
MarkdownTextContent(
content: contentValue.text,
textStyle: theme.textTheme.bodyMedium,
attachments:
state.attachments.value
.where((e) => e.isOnCloud)
.map((e) => e.data)
.cast<SnCloudFile>()
.toList(),
),
],
);
@@ -268,7 +266,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
child: KeyboardListener(
focusNode: FocusNode(),
onKeyEvent:
(event) => _handleKeyPress(
(event) => ComposeLogic.handleKeyPress(
event,
state,
ref,
@@ -511,38 +509,4 @@ class ArticleComposeScreen extends HookConsumerWidget {
),
);
}
// Helper method to handle keyboard shortcuts
void _handleKeyPress(
KeyEvent event,
ComposeState state,
WidgetRef ref,
BuildContext context, {
SnPost? originalPost,
}) {
if (event is! RawKeyDownEvent) return;
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
final isModifierPressed =
HardwareKeyboard.instance.isMetaPressed ||
HardwareKeyboard.instance.isControlPressed;
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
if (isPaste && isModifierPressed) {
ComposeLogic.handlePaste(state);
} else if (isSave && isModifierPressed) {
ComposeLogic.saveDraft(ref, state);
ComposeLogic.saveDraft(ref, state);
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
ComposeLogic.performAction(
ref,
state,
context,
originalPost: originalPost,
);
}
}
// Helper method to save article draft
}

View File

@@ -39,8 +39,13 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier {
await database.addPostDraft(
PostDraftsCompanion(
id: Value(updatedDraft.id),
post: Value(jsonEncode(updatedDraft.toJson())),
title: Value(updatedDraft.title),
description: Value(updatedDraft.description),
content: Value(updatedDraft.content),
visibility: Value(updatedDraft.visibility),
type: Value(updatedDraft.type),
lastModified: Value(updatedDraft.updatedAt ?? DateTime.now()),
postData: Value(jsonEncode(updatedDraft.toJson())),
),
);
} catch (e) {

View File

@@ -7,7 +7,7 @@ part of 'compose_storage_db.dart';
// **************************************************************************
String _$composeStorageNotifierHash() =>
r'4ab4dce85d0a961f096dc3b11505f8f0964dee9d';
r'8baf17aa06b6f69641c20645ba8a3dfe01c97f8c';
/// See also [ComposeStorageNotifier].
@ProviderFor(ComposeStorageNotifier)

View File

@@ -173,10 +173,6 @@ class ComposeLogic {
}
try {
if (state._autoSaveTimer == null) {
return;
}
// Upload any local attachments first
final baseUrl = ref.watch(serverUrlProvider);
final token = await getToken(ref.watch(tokenProvider));
@@ -284,10 +280,6 @@ class ComposeLogic {
}
try {
if (state._autoSaveTimer == null) {
return;
}
final draft = SnPost(
id: state.draftId,
title: state.titleController.text,

View File

@@ -34,7 +34,7 @@ class ComposeToolbar extends HookConsumerWidget {
}
void saveDraft() {
ComposeLogic.saveDraft(ref, state);
ComposeLogic.saveDraftManually(ref, state, context);
}
void pickPoll() {

View File

@@ -16,42 +16,53 @@ class DraftManagerSheet extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final isLoading = useState(true);
final searchController = useTextEditingController();
final searchQuery = useState('');
final drafts = ref.watch(composeStorageNotifierProvider);
// 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;
// Search functionality
final filteredDrafts = useMemoized(() {
if (searchQuery.value.isEmpty) {
return drafts.values.toList()
..sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
}
});
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(),
],
);
final query = searchQuery.value.toLowerCase();
return drafts.values.where((draft) {
return (draft.title?.toLowerCase().contains(query) ?? false) ||
(draft.description?.toLowerCase().contains(query) ?? false) ||
(draft.content?.toLowerCase().contains(query) ?? false);
}).toList()
..sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
}, [drafts, searchQuery.value]);
return SheetScaffold(
titleText: 'drafts'.tr(),
child:
isLoading.value
? const Center(child: CircularProgressIndicator())
: Column(
child: Column(
children: [
if (sortedDrafts.isEmpty)
// Search bar
Padding(
padding: const EdgeInsets.all(16),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'searchDrafts'.tr(),
prefixIcon: const Icon(Symbols.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onChanged: (value) => searchQuery.value = value,
),
),
// Drafts list
if (filteredDrafts.isEmpty)
Expanded(
child: Center(
child: Column(
@@ -64,7 +75,9 @@ class DraftManagerSheet extends HookConsumerWidget {
),
const Gap(16),
Text(
'noDrafts'.tr(),
searchQuery.value.isEmpty
? 'noDrafts'.tr()
: 'noSearchResults'.tr(),
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurface.withOpacity(0.6),
),
@@ -76,9 +89,9 @@ class DraftManagerSheet extends HookConsumerWidget {
else
Expanded(
child: ListView.builder(
itemCount: sortedDrafts.length,
itemCount: filteredDrafts.length,
itemBuilder: (context, index) {
final draft = sortedDrafts[index];
final draft = filteredDrafts[index];
return _DraftItem(
draft: draft,
onTap: () {
@@ -94,7 +107,9 @@ class DraftManagerSheet extends HookConsumerWidget {
},
),
),
if (sortedDrafts.isNotEmpty) ...[
// Clear all button
if (filteredDrafts.isNotEmpty) ...[
const Divider(),
Padding(
padding: const EdgeInsets.all(16),
@@ -108,22 +123,16 @@ class DraftManagerSheet extends HookConsumerWidget {
builder:
(context) => AlertDialog(
title: Text('clearAllDrafts'.tr()),
content: Text(
'clearAllDraftsConfirm'.tr(),
),
content: Text('clearAllDraftsConfirm'.tr()),
actions: [
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(false),
() => Navigator.of(context).pop(false),
child: Text('cancel'.tr()),
),
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(true),
() => Navigator.of(context).pop(true),
child: Text('confirm'.tr()),
),
],
@@ -132,9 +141,7 @@ class DraftManagerSheet extends HookConsumerWidget {
if (confirmed == true) {
await ref
.read(
composeStorageNotifierProvider.notifier,
)
.read(composeStorageNotifierProvider.notifier)
.clearAllDrafts();
}
},