✨ Reworked post draft
This commit is contained in:
		@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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};
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
      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.string,
 | 
			
		||||
            data['${effectivePrefix}post'],
 | 
			
		||||
            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(
 | 
			
		||||
        id: id ?? this.id,
 | 
			
		||||
        post: post ?? this.post,
 | 
			
		||||
        lastModified: lastModified ?? this.lastModified,
 | 
			
		||||
      );
 | 
			
		||||
  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,
 | 
			
		||||
    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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ part of 'compose_storage_db.dart';
 | 
			
		||||
// **************************************************************************
 | 
			
		||||
 | 
			
		||||
String _$composeStorageNotifierHash() =>
 | 
			
		||||
    r'4ab4dce85d0a961f096dc3b11505f8f0964dee9d';
 | 
			
		||||
    r'8baf17aa06b6f69641c20645ba8a3dfe01c97f8c';
 | 
			
		||||
 | 
			
		||||
/// See also [ComposeStorageNotifier].
 | 
			
		||||
@ProviderFor(ComposeStorageNotifier)
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ class ComposeToolbar extends HookConsumerWidget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void saveDraft() {
 | 
			
		||||
      ComposeLogic.saveDraft(ref, state);
 | 
			
		||||
      ComposeLogic.saveDraftManually(ref, state, context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void pickPoll() {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,138 +16,145 @@ 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;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      return null;
 | 
			
		||||
    }, [drafts]);
 | 
			
		||||
    // Search functionality
 | 
			
		||||
    final filteredDrafts = useMemoized(() {
 | 
			
		||||
      if (searchQuery.value.isEmpty) {
 | 
			
		||||
        return drafts.values.toList()
 | 
			
		||||
          ..sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
                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);
 | 
			
		||||
                            },
 | 
			
		||||
                          );
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  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()),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        ],
 | 
			
		||||
                                      ),
 | 
			
		||||
                                );
 | 
			
		||||
      child: Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          // 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,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
                                if (confirmed == true) {
 | 
			
		||||
                                  await ref
 | 
			
		||||
                                      .read(
 | 
			
		||||
                                        composeStorageNotifierProvider.notifier,
 | 
			
		||||
                                      )
 | 
			
		||||
                                      .clearAllDrafts();
 | 
			
		||||
                                }
 | 
			
		||||
                              },
 | 
			
		||||
                              icon: const Icon(Symbols.delete_sweep),
 | 
			
		||||
                              label: Text('clearAll'.tr()),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ],
 | 
			
		||||
          // Drafts list
 | 
			
		||||
          if (filteredDrafts.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(
 | 
			
		||||
                      searchQuery.value.isEmpty
 | 
			
		||||
                          ? 'noDrafts'.tr()
 | 
			
		||||
                          : 'noSearchResults'.tr(),
 | 
			
		||||
                      style: theme.textTheme.bodyLarge?.copyWith(
 | 
			
		||||
                        color: colorScheme.onSurface.withOpacity(0.6),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            )
 | 
			
		||||
          else
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: ListView.builder(
 | 
			
		||||
                itemCount: filteredDrafts.length,
 | 
			
		||||
                itemBuilder: (context, index) {
 | 
			
		||||
                  final draft = filteredDrafts[index];
 | 
			
		||||
                  return _DraftItem(
 | 
			
		||||
                    draft: draft,
 | 
			
		||||
                    onTap: () {
 | 
			
		||||
                      Navigator.of(context).pop();
 | 
			
		||||
                      onDraftSelected?.call(draft.id);
 | 
			
		||||
                    },
 | 
			
		||||
                    onDelete: () async {
 | 
			
		||||
                      await ref
 | 
			
		||||
                          .read(composeStorageNotifierProvider.notifier)
 | 
			
		||||
                          .deleteDraft(draft.id);
 | 
			
		||||
                    },
 | 
			
		||||
                  );
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
          // Clear all button
 | 
			
		||||
          if (filteredDrafts.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()),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user