Skip to content

Commit

Permalink
Add the ability to specify alt text for image posts (#1640)
Browse files Browse the repository at this point in the history
* Add the ability to specify alt text for image posts

* Handle db migration downgrade for alt_text
  • Loading branch information
micahmo authored Jan 17, 2025
1 parent b6af7e7 commit 6ee7d5e
Show file tree
Hide file tree
Showing 13 changed files with 991 additions and 15 deletions.
1 change: 1 addition & 0 deletions drift_schemas/drift_schema_v6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"accounts","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"username","getter_name":"username","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"jwt","getter_name":"jwt","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"instance","getter_name":"instance","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"anonymous","getter_name":"anonymous","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"anonymous\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"anonymous\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"user_id","getter_name":"userId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"list_index","getter_name":"listIndex","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"favorites","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"account_id","getter_name":"accountId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"community_id","getter_name":"communityId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"local_subscriptions","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"title","getter_name":"title","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"actor_id","getter_name":"actorId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"icon","getter_name":"icon","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"user_labels","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"label","getter_name":"label","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"drafts","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"draft_type","getter_name":"draftType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const DraftTypeConverter()","dart_type_name":"DraftType"}},{"name":"existing_id","getter_name":"existingId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"reply_id","getter_name":"replyId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"title","getter_name":"title","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"custom_thumbnail","getter_name":"customThumbnail","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"alt_text","getter_name":"altText","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"body","getter_name":"body","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}
4 changes: 2 additions & 2 deletions lib/account/models/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Account {
instance: Value(account.instance),
anonymous: Value(account.anonymous),
userId: Value(account.userId),
listIndex: newIndex,
listIndex: Value(newIndex),
),
);

Expand Down Expand Up @@ -85,7 +85,7 @@ class Account {
instance: Value(anonymousInstance.instance),
anonymous: Value(anonymousInstance.anonymous),
userId: Value(anonymousInstance.userId),
listIndex: newIndex,
listIndex: Value(newIndex),
),
);

Expand Down
11 changes: 10 additions & 1 deletion lib/account/models/draft.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class Draft {
/// The custom thumbnail of the post
final String? customThumbnail;

/// Alternative text for the image
final String? altText;

/// The body of the post/comment
final String? body;

Expand All @@ -38,6 +41,7 @@ class Draft {
this.title,
this.url,
this.customThumbnail,
this.altText,
this.body,
});

Expand All @@ -49,6 +53,7 @@ class Draft {
String? title,
String? url,
String? customThumbnail,
String? altText,
String? body,
}) =>
Draft(
Expand All @@ -59,11 +64,12 @@ class Draft {
title: title ?? this.title,
url: url ?? this.url,
customThumbnail: customThumbnail ?? this.customThumbnail,
altText: altText ?? this.altText,
body: body ?? this.body,
);

/// See whether this draft contains enough info to save for a post
bool get isPostNotEmpty => title?.isNotEmpty == true || url?.isNotEmpty == true || customThumbnail?.isNotEmpty == true || body?.isNotEmpty == true;
bool get isPostNotEmpty => title?.isNotEmpty == true || url?.isNotEmpty == true || customThumbnail?.isNotEmpty == true || altText?.isNotEmpty == true || body?.isNotEmpty == true;

/// See whether this draft contains enough info to save for a comment
bool get isCommentNotEmpty => body?.isNotEmpty == true;
Expand All @@ -86,6 +92,7 @@ class Draft {
title: Value(draft.title),
url: Value(draft.url),
customThumbnail: Value(draft.customThumbnail),
altText: Value(draft.altText),
body: Value(draft.body),
),
);
Expand All @@ -100,6 +107,7 @@ class Draft {
title: Value(draft.title),
url: Value(draft.url),
customThumbnail: Value(draft.customThumbnail),
altText: Value(draft.altText),
body: Value(draft.body),
),
);
Expand Down Expand Up @@ -130,6 +138,7 @@ class Draft {
title: draft.title,
url: draft.url,
customThumbnail: draft.customThumbnail,
altText: draft.altText,
body: draft.body,
);
} catch (e) {
Expand Down
36 changes: 35 additions & 1 deletion lib/community/pages/create_post_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CreatePostPage extends StatefulWidget {
final int? communityId;
final CommunityView? communityView;

/// Whether or not to pre-populate the post with the [title], [text], [image], [url], and/or [customThumbnail]
/// Whether or not to pre-populate the post with the [title], [text], [image], [url], [customThumbnail], and/or [altText]
final bool? prePopulated;

/// Used to pre-populate the post title
Expand All @@ -63,6 +63,9 @@ class CreatePostPage extends StatefulWidget {
/// Used to pre-populate the custom thumbnail for the post
final String? customThumbnail;

/// Alternative text for the image
final String? altText;

/// [postView] is passed in when editing an existing post
final PostView? postView;

Expand All @@ -78,6 +81,7 @@ class CreatePostPage extends StatefulWidget {
this.text,
this.url,
this.customThumbnail,
this.altText,
this.prePopulated = false,
this.postView,
this.onPostSuccess,
Expand Down Expand Up @@ -124,6 +128,9 @@ class _CreatePostPageState extends State<CreatePostPage> {
/// The custom thumbnail for this post.
String? customThumbnail;

/// Alternative text for the image
String? altText;

/// The error message for the shared link if available
String? urlError;

Expand All @@ -146,6 +153,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
final TextEditingController _titleTextController = TextEditingController();
final TextEditingController _urlTextController = TextEditingController();
final TextEditingController _customThumbnailTextController = TextEditingController();
final TextEditingController _altTextTextController = TextEditingController();

/// The focus node for the body. This is used to keep track of the position of the cursor when toggling preview
final FocusNode _bodyFocusNode = FocusNode();
Expand Down Expand Up @@ -182,12 +190,19 @@ class _CreatePostPageState extends State<CreatePostPage> {
debounce(const Duration(milliseconds: 1000), _updatePreview, [customThumbnail]);
});

_altTextTextController.addListener(() {
altText = _altTextTextController.text;
_validateSubmission();
debounce(const Duration(milliseconds: 1000), _updatePreview, [altText]);
});

// Logic for pre-populating the post with the given fields
if (widget.prePopulated == true) {
_titleTextController.text = widget.title ?? '';
_bodyTextController.text = widget.text ?? '';
_urlTextController.text = widget.url ?? '';
_customThumbnailTextController.text = widget.customThumbnail ?? '';
_altTextTextController.text = widget.altText ?? '';
_getDataFromLink(updateTitleField: _titleTextController.text.isEmpty);

if (widget.image != null) {
Expand All @@ -204,6 +219,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
_titleTextController.text = widget.postView!.post.name;
_urlTextController.text = widget.postView!.post.url ?? '';
_customThumbnailTextController.text = widget.postView!.post.thumbnailUrl ?? '';
_altTextTextController.text = widget.postView!.post.altText ?? '';
_bodyTextController.text = widget.postView!.post.body ?? '';
isNSFW = widget.postView!.post.nsfw;
languageId = widget.postView!.post.languageId;
Expand All @@ -221,6 +237,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
_titleTextController.dispose();
_urlTextController.dispose();
_customThumbnailTextController.dispose();
_altTextTextController.dispose();
_bodyFocusNode.dispose();

FocusManager.instance.primaryFocus?.unfocus();
Expand Down Expand Up @@ -260,6 +277,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
_titleTextController.text = draft.title ?? '';
_urlTextController.text = draft.url ?? '';
_customThumbnailTextController.text = draft.customThumbnail ?? '';
_altTextTextController.text = draft.altText ?? '';
_bodyTextController.text = draft.body ?? '';
}

Expand All @@ -282,6 +300,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
_titleTextController.text = widget.postView?.post.name ?? '';
_urlTextController.text = widget.postView?.post.url ?? '';
_customThumbnailTextController.text = widget.postView?.post.thumbnailUrl ?? '';
_altTextTextController.text = widget.postView?.post.altText ?? '';
_bodyTextController.text = widget.postView?.post.body ?? '';
},
);
Expand All @@ -297,6 +316,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
title: _titleTextController.text,
url: _urlTextController.text,
customThumbnail: _customThumbnailTextController.text,
altText: _altTextTextController.text,
body: _bodyTextController.text,
);
}
Expand All @@ -311,6 +331,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
return draft.title != widget.postView!.post.name ||
draft.url != (widget.postView!.post.url ?? '') ||
draft.customThumbnail != (widget.postView!.post.thumbnailUrl ?? '') ||
draft.altText != (widget.postView!.post.altText ?? '') ||
draft.body != (widget.postView!.post.body ?? '');
}

Expand Down Expand Up @@ -502,6 +523,18 @@ class _CreatePostPageState extends State<CreatePostPage> {
),
),
],
if (LemmyClient.instance.supportsFeature(LemmyFeature.altText) && isImageUrl(_urlTextController.text)) ...[
const SizedBox(height: 10),
TextFormField(
controller: _altTextTextController,
decoration: InputDecoration(
labelText: l10n.altText,
isDense: true,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.all(13),
),
),
],
SizedBox(height: url.isNotEmpty ? 10 : 5),
Visibility(
visible: url.isNotEmpty,
Expand Down Expand Up @@ -757,6 +790,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
nsfw: isNSFW,
url: url,
customThumbnail: customThumbnail,
altText: altText,
postIdBeingEdited: widget.postView?.post.id,
languageId: languageId,
);
Expand Down
31 changes: 30 additions & 1 deletion lib/core/database/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class AppDatabase extends _$AppDatabase {
);

@override
int get schemaVersion => 5;
int get schemaVersion => 6;

@override
MigrationStrategy get migration => MigrationStrategy(
Expand Down Expand Up @@ -67,6 +67,10 @@ class AppDatabase extends _$AppDatabase {
await m.addColumn(schema.accounts, schema.accounts.listIndex);
await customStatement('UPDATE accounts SET list_index = id');
},
from5To6: (m, schema) async {
// Create the alt_text column on the drafts table
await m.addColumn(schema.drafts, schema.drafts.altText);
},
),
);

Expand All @@ -78,9 +82,34 @@ class AppDatabase extends _$AppDatabase {
await impl.validateDatabaseSchema(this);
await customStatement('PRAGMA foreign_keys = ON;');
},
beforeOpen: (details) async {
if (details.versionBefore != null && details.versionBefore! > details.versionNow) {
await _onDowngrade(this, details.versionBefore!, details.versionNow);
}
},
);
}

Future<void> _onDowngrade(AppDatabase database, int fromVersion, int toVersion) async {
await database.customStatement('PRAGMA foreign_keys = OFF');

int current = fromVersion;
while (current > toVersion) {
int target = current - 1;
await _onDownGradeOneStep(database, current, target);
current = target;
}

await database.customStatement('PRAGMA foreign_keys=ON;');
}

Future<void> _onDownGradeOneStep(AppDatabase database, int fromVersion, int toVersion) async {
if (fromVersion == 6 && toVersion == 5) {
// Drop the alt_text column on the drafts table
await database.customStatement('ALTER TABLE drafts DROP COLUMN alt_text');
}
}

Future<String?> exportDatabase() async {
final Directory dbFolder = await getApplicationDocumentsDirectory();
final File file = File(join(dbFolder.path, 'thunder.sqlite'));
Expand Down
Loading

0 comments on commit 6ee7d5e

Please sign in to comment.