Skip to content

Commit

Permalink
Add support for custom thumbnails in posts (thunder-app#1495)
Browse files Browse the repository at this point in the history
  • Loading branch information
micahmo authored Jul 16, 2024
1 parent ee7384e commit 762f130
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 23 deletions.
11 changes: 10 additions & 1 deletion lib/account/models/draft.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class Draft {
/// The URL of the post
final String? url;

/// The custom thumbnail of the post
final String? customThumbnail;

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

Expand All @@ -34,6 +37,7 @@ class Draft {
this.replyId,
this.title,
this.url,
this.customThumbnail,
this.body,
});

Expand All @@ -44,6 +48,7 @@ class Draft {
int? replyId,
String? title,
String? url,
String? customThumbnail,
String? body,
}) =>
Draft(
Expand All @@ -53,11 +58,12 @@ class Draft {
replyId: replyId ?? this.replyId,
title: title ?? this.title,
url: url ?? this.url,
customThumbnail: customThumbnail ?? this.customThumbnail,
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 || body?.isNotEmpty == true;
bool get isPostNotEmpty => title?.isNotEmpty == true || url?.isNotEmpty == true || customThumbnail?.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 @@ -79,6 +85,7 @@ class Draft {
replyId: Value(draft.replyId),
title: Value(draft.title),
url: Value(draft.url),
customThumbnail: Value(draft.customThumbnail),
body: Value(draft.body),
),
);
Expand All @@ -92,6 +99,7 @@ class Draft {
replyId: Value(draft.replyId),
title: Value(draft.title),
url: Value(draft.url),
customThumbnail: Value(draft.customThumbnail),
body: Value(draft.body),
),
);
Expand Down Expand Up @@ -121,6 +129,7 @@ class Draft {
replyId: draft.replyId,
title: draft.title,
url: draft.url,
customThumbnail: draft.customThumbnail,
body: draft.body,
);
} catch (e) {
Expand Down
66 changes: 56 additions & 10 deletions 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] and/or [url]
/// Whether or not to pre-populate the post with the [title], [text], [image], [url], and/or [customThumbnail]
final bool? prePopulated;

/// Used to pre-populate the post title
Expand All @@ -60,6 +60,9 @@ class CreatePostPage extends StatefulWidget {
/// Used to pre-populate the shared link for the post
final String? url;

/// Used to pre-populate the custom thumbnail for the post
final String? customThumbnail;

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

Expand All @@ -74,6 +77,7 @@ class CreatePostPage extends StatefulWidget {
this.title,
this.text,
this.url,
this.customThumbnail,
this.prePopulated = false,
this.postView,
this.onPostSuccess,
Expand Down Expand Up @@ -117,9 +121,15 @@ class _CreatePostPageState extends State<CreatePostPage> {
/// The shared link for the post. This is used to determine any cross posts
String url = "";

/// The custom thumbnail for this post.
String? customThumbnail;

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

/// The error message for the custom thumbnail if available
String? customThumbnailError;

/// The id of the community that the post will be created in
int? communityId;

Expand All @@ -135,6 +145,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
final TextEditingController _bodyTextController = TextEditingController();
final TextEditingController _titleTextController = TextEditingController();
final TextEditingController _urlTextController = TextEditingController();
final TextEditingController _customThumbnailTextController = 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 @@ -165,11 +176,18 @@ class _CreatePostPageState extends State<CreatePostPage> {
debounce(const Duration(milliseconds: 1000), _updatePreview, [url]);
});

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

// 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 ?? '';
_getDataFromLink(updateTitleField: _titleTextController.text.isEmpty);

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

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

Expand All @@ -260,6 +281,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
Draft.deleteDraft(draftType, draftExistingId, draftReplyId);
_titleTextController.text = widget.postView?.post.name ?? '';
_urlTextController.text = widget.postView?.post.url ?? '';
_customThumbnailTextController.text = widget.postView?.post.thumbnailUrl ?? '';
_bodyTextController.text = widget.postView?.post.body ?? '';
},
);
Expand All @@ -274,6 +296,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
replyId: draftReplyId,
title: _titleTextController.text,
url: _urlTextController.text,
customThumbnail: _customThumbnailTextController.text,
body: _bodyTextController.text,
);
}
Expand All @@ -285,7 +308,10 @@ class _CreatePostPageState extends State<CreatePostPage> {
return true;
}

return draft.title != widget.postView!.post.name || draft.url != (widget.postView!.post.url ?? '') || draft.body != (widget.postView!.post.body ?? '');
return draft.title != widget.postView!.post.name ||
draft.url != (widget.postView!.post.url ?? '') ||
draft.customThumbnail != (widget.postView!.post.thumbnailUrl ?? '') ||
draft.body != (widget.postView!.post.body ?? '');
}

/// Attempts to get the suggested title for a given link
Expand Down Expand Up @@ -371,6 +397,7 @@ class _CreatePostPageState extends State<CreatePostPage> {
body: _bodyTextController.text,
nsfw: isNSFW,
url: url,
customThumbnail: customThumbnail,
postIdBeingEdited: widget.postView?.post.id,
languageId: languageId,
);
Expand Down Expand Up @@ -480,14 +507,28 @@ class _CreatePostPageState extends State<CreatePostPage> {
),
),
),
if (LemmyClient.instance.supportsFeature(LemmyFeature.customThumbnail) && !isImageUrl(_urlTextController.text)) ...[
const SizedBox(height: 10),
TextFormField(
controller: _customThumbnailTextController,
decoration: InputDecoration(
hintText: l10n.thumbnailUrl,
errorText: customThumbnailError,
),
),
],
const SizedBox(height: 10),
Visibility(
visible: url.isNotEmpty,
child: LinkPreviewCard(
hideNsfw: false,
scrapeMissingPreviews: false,
originURL: url,
mediaURL: isImageUrl(url) ? url : null,
mediaURL: isImageUrl(url)
? url
: customThumbnail?.isNotEmpty == true && isImageUrl(customThumbnail!)
? customThumbnail
: null,
mediaHeight: null,
mediaWidth: null,
showFullHeightImages: false,
Expand Down Expand Up @@ -645,8 +686,8 @@ class _CreatePostPageState extends State<CreatePostPage> {
}

void _updatePreview(String text) async {
SearchResponse? searchResponse;
if (url == text) {
SearchResponse? searchResponse;
try {
// Fetch cross-posts
final Account? account = await fetchActiveProfileAccount();
Expand All @@ -658,31 +699,36 @@ class _CreatePostPageState extends State<CreatePostPage> {
limit: 20,
auth: account?.jwt,
));
} finally {
setState(() {
crossPosts = searchResponse?.posts ?? [];
});
} catch (e) {
// Ignore
}
}

setState(() {
crossPosts = searchResponse?.posts ?? [];
});
}

void _validateSubmission() {
final Uri? parsedUrl = Uri.tryParse(_urlTextController.text);
final Uri? parsedCustomThumbnail = Uri.tryParse(_customThumbnailTextController.text);

if (isSubmitButtonDisabled) {
// It's disabled, check if we can enable it.
if (_titleTextController.text.isNotEmpty && parsedUrl != null && communityId != null) {
if (_titleTextController.text.isNotEmpty && parsedUrl != null && parsedCustomThumbnail != null && communityId != null) {
setState(() {
isSubmitButtonDisabled = false;
urlError = null;
customThumbnailError = null;
});
}
} else {
// It's enabled, check if we need to disable it.
if (_titleTextController.text.isEmpty || parsedUrl == null || communityId == null) {
if (_titleTextController.text.isEmpty || parsedUrl == null || parsedCustomThumbnail == null || communityId == null) {
setState(() {
isSubmitButtonDisabled = true;
urlError = parsedUrl == null ? AppLocalizations.of(context)!.notValidUrl : null;
customThumbnailError = parsedCustomThumbnail == null ? AppLocalizations.of(context)!.notValidUrl : null;
});
}
}
Expand Down
22 changes: 17 additions & 5 deletions lib/core/database/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());

@override
int get schemaVersion => 3;
int get schemaVersion => 4;

@override
MigrationStrategy get migration => MigrationStrategy(
Expand All @@ -39,19 +39,31 @@ class AppDatabase extends _$AppDatabase {
await migrator.createTable(drafts);
}

// If we are migrating from 3 or lower to anything higher
if (from <= 3 && to > 3) {
// Create the custom_thumbnail on the drafts table
await customStatement('ALTER TABLE drafts ADD COLUMN custom_thumbnail TEXT');
}

// --- DOWNGRADES ---

// If we are downgrading from 2 or higher to 1
if (from >= 2 && to <= 1) {
// Delete the UserLabels table
await migrator.deleteTable('user_labels');
// If we are downgrading from 4 or higher to 3 or lower
if (from >= 4 && to <= 3) {
// Drop the custom_thumbnail column from Accounts
await customStatement('ALTER TABLE drafts DROP COLUMN custom_thumbnail');
}

// If we are downgrading from 3 or higher to 2 or lower
if (from >= 3 && to <= 2) {
// Delete the Drafts table
await migrator.deleteTable('drafts');
}

// If we are downgrading from 2 or higher to 1
if (from >= 2 && to <= 1) {
// Delete the UserLabels table
await migrator.deleteTable('user_labels');
}
},
);
}
Expand Down
Loading

0 comments on commit 762f130

Please sign in to comment.