diff --git a/CHANGELOG.md b/CHANGELOG.md index 23e9b18f2..e5f12f1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Introduced support for reporting comments. - contribution from @ggichure - Added ability to create post from home feed - contribution from @micahmo - Added option to enter reader mode when tapping on a link in iOS +- Support new scaled and controversial sort types - contribution from @micahmo - Added support to open Lemmy links in app. Android only. - contribution from @ggichure ### Changed diff --git a/lib/core/singletons/lemmy_client.dart b/lib/core/singletons/lemmy_client.dart index 51926b41b..3251028c5 100644 --- a/lib/core/singletons/lemmy_client.dart +++ b/lib/core/singletons/lemmy_client.dart @@ -1,4 +1,7 @@ import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/account/models/account.dart'; +import 'package:thunder/core/auth/helpers/fetch_account.dart'; +import 'package:version/version.dart'; class LemmyClient { LemmyApiV3 lemmyApiV3 = const LemmyApiV3(''); @@ -7,9 +10,68 @@ class LemmyClient { void changeBaseUrl(String baseUrl) { lemmyApiV3 = LemmyApiV3(baseUrl); + _populateSiteInfo(); // Do NOT await this. Let it populate in the background. } static final LemmyClient _instance = LemmyClient._initialize(); static LemmyClient get instance => _instance; + + Future _populateSiteInfo() async { + if (_lemmySites.containsKey(instance.lemmyApiV3.host)) return; + + // Retrieve the site so we can look up metadata about it later + Account? account = await fetchActiveProfileAccount(); + + _lemmySites[instance.lemmyApiV3.host] = await instance.lemmyApiV3.run( + GetSite( + auth: account?.jwt, + ), + ); + } + + bool supportsFeature(LemmyFeature feature) { + if (!_lemmySites.containsKey(instance.lemmyApiV3.host)) return false; + + // Parse the version + FullSiteView site = _lemmySites[instance.lemmyApiV3.host]!; + Version instanceVersion; + try { + instanceVersion = Version.parse(site.version); + } catch (e) { + return false; + } + + // Check the feature and return whether it's supported in this version + return instanceVersion > feature.minSupportedVersion; + } + + static final Map _lemmySites = {}; +} + +enum LemmyFeature { + sortTypeControversial(0, 19, 0, preRelease: ["rc", "1"]), + sortTypeScaled(0, 19, 0, preRelease: ["rc", "1"]), + commentSortTypeControversial(0, 19, 0, preRelease: ["rc", "1"]); + + final int major; + final int minor; + final int patch; + final List preRelease; + + const LemmyFeature(this.major, this.minor, this.patch, {this.preRelease = const []}); + + Version get minSupportedVersion => Version( + major, + minor, + patch, + // The Version package attempts to modify this list, so give them a non-final copy. + preRelease: List.from(preRelease), + ); +} + +enum IncludeVersionSpecificFeature { + never, + ifSupported, + always, } diff --git a/lib/feed/widgets/feed_fab.dart b/lib/feed/widgets/feed_fab.dart index 29100197f..5c574fcf9 100644 --- a/lib/feed/widgets/feed_fab.dart +++ b/lib/feed/widgets/feed_fab.dart @@ -247,6 +247,7 @@ class FeedFAB extends StatelessWidget { showModalBottomSheet( showDragHandle: true, context: context, + isScrollControlled: true, builder: (builderContext) => SortPicker( title: l10n.sortOptions, onSelect: (selected) => context.read().add(FeedChangeSortTypeEvent(selected.payload)), diff --git a/lib/feed/widgets/feed_page_app_bar.dart b/lib/feed/widgets/feed_page_app_bar.dart index 5b2a713dd..0ea5cfa31 100644 --- a/lib/feed/widgets/feed_page_app_bar.dart +++ b/lib/feed/widgets/feed_page_app_bar.dart @@ -97,6 +97,7 @@ class FeedPageAppBar extends StatelessWidget { showModalBottomSheet( showDragHandle: true, context: context, + isScrollControlled: true, builder: (builderContext) => SortPicker( title: l10n.sortOptions, onSelect: (selected) => feedBloc.add(FeedChangeSortTypeEvent(selected.payload)), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b1862e79f..946c53d03 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -354,6 +354,8 @@ "@upvote": {}, "downvote": "Downvote", "@downvote": {}, + "scaled": "Scaled", + "controversial": "Controversial", "edit": "Edit", "@edit": {}, "restore": "Restore", diff --git a/lib/post/pages/post_page.dart b/lib/post/pages/post_page.dart index d5728dea9..ae9b19930 100644 --- a/lib/post/pages/post_page.dart +++ b/lib/post/pages/post_page.dart @@ -15,6 +15,7 @@ import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/core/enums/fab_action.dart'; import 'package:thunder/core/enums/local_settings.dart'; import 'package:thunder/core/models/post_view_media.dart'; +import 'package:thunder/core/singletons/lemmy_client.dart'; import 'package:thunder/core/singletons/preferences.dart'; import 'package:thunder/post/bloc/post_bloc.dart'; import 'package:thunder/post/pages/post_page_success.dart'; @@ -121,7 +122,8 @@ class _PostPageState extends State { if (previousState.sortType != currentState.sortType) { setState(() { sortType = currentState.sortType; - final sortTypeItem = commentSortTypeItems.firstWhere((sortTypeItem) => sortTypeItem.payload == currentState.sortType); + final sortTypeItem = CommentSortPicker.getCommentSortTypeItems(includeVersionSpecificFeature: IncludeVersionSpecificFeature.always) + .firstWhere((sortTypeItem) => sortTypeItem.payload == currentState.sortType); sortTypeIcon = sortTypeItem.icon; sortTypeLabel = sortTypeItem.label; }); diff --git a/lib/settings/pages/general_settings_page.dart b/lib/settings/pages/general_settings_page.dart index bf46a7893..443091ac8 100644 --- a/lib/settings/pages/general_settings_page.dart +++ b/lib/settings/pages/general_settings_page.dart @@ -10,6 +10,7 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:thunder/core/enums/local_settings.dart'; import 'package:thunder/core/enums/nested_comment_indicator.dart'; +import 'package:thunder/core/singletons/lemmy_client.dart'; import 'package:thunder/core/singletons/preferences.dart'; import 'package:thunder/settings/widgets/list_option.dart'; import 'package:thunder/settings/widgets/settings_list_tile.dart'; @@ -416,11 +417,12 @@ class _GeneralSettingsPageState extends State with SingleTi ListOption( description: LocalSettings.defaultFeedSortType.label, value: ListPickerItem(label: defaultSortType.value, icon: Icons.local_fire_department_rounded, payload: defaultSortType), - options: allSortTypeItems, + options: [...SortPicker.getDefaultSortTypeItems(includeVersionSpecificFeature: IncludeVersionSpecificFeature.never), ...topSortTypeItems], icon: Icons.sort_rounded, onChanged: (_) {}, isBottomModalScrollControlled: true, customListPicker: SortPicker( + includeVersionSpecificFeature: IncludeVersionSpecificFeature.never, title: LocalSettings.defaultFeedSortType.label, onSelect: (value) { setPreferences(LocalSettings.defaultFeedSortType, value.payload.name); @@ -627,10 +629,11 @@ class _GeneralSettingsPageState extends State with SingleTi ListOption( description: LocalSettings.defaultCommentSortType.label, value: ListPickerItem(label: defaultCommentSortType.value, icon: Icons.local_fire_department_rounded, payload: defaultCommentSortType), - options: commentSortTypeItems, + options: CommentSortPicker.getCommentSortTypeItems(includeVersionSpecificFeature: IncludeVersionSpecificFeature.never), icon: Icons.comment_bank_rounded, onChanged: (_) {}, customListPicker: CommentSortPicker( + includeVersionSpecificFeature: IncludeVersionSpecificFeature.never, title: l10n.commentSortType, onSelect: (value) { setPreferences(LocalSettings.defaultCommentSortType, value.payload.name); @@ -639,10 +642,16 @@ class _GeneralSettingsPageState extends State with SingleTi ), valueDisplay: Row( children: [ - Icon(commentSortTypeItems.firstWhere((sortTypeItem) => sortTypeItem.payload == defaultCommentSortType).icon, size: 13), + Icon( + CommentSortPicker.getCommentSortTypeItems(includeVersionSpecificFeature: IncludeVersionSpecificFeature.always) + .firstWhere((sortTypeItem) => sortTypeItem.payload == defaultCommentSortType) + .icon, + size: 13), const SizedBox(width: 4), Text( - commentSortTypeItems.firstWhere((sortTypeItem) => sortTypeItem.payload == defaultCommentSortType).label, + CommentSortPicker.getCommentSortTypeItems(includeVersionSpecificFeature: IncludeVersionSpecificFeature.always) + .firstWhere((sortTypeItem) => sortTypeItem.payload == defaultCommentSortType) + .label, style: theme.textTheme.titleSmall, ), ], diff --git a/lib/shared/comment_sort_picker.dart b/lib/shared/comment_sort_picker.dart index 32faf8d1e..9ac245b98 100644 --- a/lib/shared/comment_sort_picker.dart +++ b/lib/shared/comment_sort_picker.dart @@ -1,41 +1,58 @@ import 'package:flutter/material.dart'; import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/core/singletons/lemmy_client.dart'; import 'package:thunder/shared/picker_item.dart'; import 'package:thunder/utils/bottom_sheet_list_picker.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:thunder/utils/global_context.dart'; -List> commentSortTypeItems = [ - ListPickerItem( - payload: CommentSortType.top, - icon: Icons.military_tech, - label: AppLocalizations.of(GlobalContext.context)!.top, - ), - ListPickerItem( - payload: CommentSortType.old, - icon: Icons.access_time_outlined, - label: AppLocalizations.of(GlobalContext.context)!.old, - ), - ListPickerItem( - payload: CommentSortType.new_, - icon: Icons.auto_awesome_rounded, - label: AppLocalizations.of(GlobalContext.context)!.new_, - ), - ListPickerItem( - payload: CommentSortType.hot, - icon: Icons.local_fire_department, - label: AppLocalizations.of(GlobalContext.context)!.hot, - ), - // - // ListPickerItem( - // payload: CommentSortType.chat, - // icon: Icons.chat, - // label: 'Chat', - // ), -]; - class CommentSortPicker extends BottomSheetListPicker { - CommentSortPicker({super.key, required super.onSelect, required super.title, List>? items, super.previouslySelected}) : super(items: items ?? commentSortTypeItems); + final IncludeVersionSpecificFeature includeVersionSpecificFeature; + + static List> getCommentSortTypeItems({IncludeVersionSpecificFeature includeVersionSpecificFeature = IncludeVersionSpecificFeature.ifSupported}) => [ + ListPickerItem( + payload: CommentSortType.top, + icon: Icons.military_tech, + label: AppLocalizations.of(GlobalContext.context)!.top, + ), + ListPickerItem( + payload: CommentSortType.old, + icon: Icons.access_time_outlined, + label: AppLocalizations.of(GlobalContext.context)!.old, + ), + if (includeVersionSpecificFeature == IncludeVersionSpecificFeature.always || + (includeVersionSpecificFeature == IncludeVersionSpecificFeature.ifSupported && LemmyClient.instance.supportsFeature(LemmyFeature.commentSortTypeControversial))) + ListPickerItem( + payload: CommentSortType.controversial, + icon: Icons.warning_rounded, + label: AppLocalizations.of(GlobalContext.context)!.controversial, + ), + ListPickerItem( + payload: CommentSortType.new_, + icon: Icons.auto_awesome_rounded, + label: AppLocalizations.of(GlobalContext.context)!.new_, + ), + ListPickerItem( + payload: CommentSortType.hot, + icon: Icons.local_fire_department, + label: AppLocalizations.of(GlobalContext.context)!.hot, + ), + // + // ListPickerItem( + // payload: CommentSortType.chat, + // icon: Icons.chat, + // label: 'Chat', + // ), + ]; + + CommentSortPicker( + {super.key, + required super.onSelect, + required super.title, + List>? items, + super.previouslySelected, + this.includeVersionSpecificFeature = IncludeVersionSpecificFeature.ifSupported}) + : super(items: items ?? CommentSortPicker.getCommentSortTypeItems(includeVersionSpecificFeature: includeVersionSpecificFeature)); @override State createState() => _SortPickerState(); @@ -79,7 +96,7 @@ class _SortPickerState extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), children: [ - ..._generateList(commentSortTypeItems, theme), + ..._generateList(CommentSortPicker.getCommentSortTypeItems(includeVersionSpecificFeature: widget.includeVersionSpecificFeature), theme), ], ), const SizedBox(height: 16.0), diff --git a/lib/shared/sort_picker.dart b/lib/shared/sort_picker.dart index 419bc423e..a5051437c 100644 --- a/lib/shared/sort_picker.dart +++ b/lib/shared/sort_picker.dart @@ -1,38 +1,11 @@ import 'package:flutter/material.dart'; import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/core/singletons/lemmy_client.dart'; import 'package:thunder/shared/picker_item.dart'; import 'package:thunder/utils/bottom_sheet_list_picker.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:thunder/utils/global_context.dart'; -List> defaultSortTypeItems = [ - ListPickerItem( - payload: SortType.hot, - icon: Icons.local_fire_department_rounded, - label: AppLocalizations.of(GlobalContext.context)!.hot, - ), - ListPickerItem( - payload: SortType.active, - icon: Icons.rocket_launch_rounded, - label: AppLocalizations.of(GlobalContext.context)!.active, - ), - ListPickerItem( - payload: SortType.new_, - icon: Icons.auto_awesome_rounded, - label: AppLocalizations.of(GlobalContext.context)!.new_, - ), - ListPickerItem( - payload: SortType.mostComments, - icon: Icons.comment_bank_rounded, - label: AppLocalizations.of(GlobalContext.context)!.mostComments, - ), - ListPickerItem( - payload: SortType.newComments, - icon: Icons.add_comment_rounded, - label: AppLocalizations.of(GlobalContext.context)!.newComments, - ), -]; - List> topSortTypeItems = [ ListPickerItem( payload: SortType.topHour, @@ -76,10 +49,56 @@ List> topSortTypeItems = [ ), ]; -List> allSortTypeItems = [...defaultSortTypeItems, ...topSortTypeItems]; +List> allSortTypeItems = [...SortPicker.getDefaultSortTypeItems(includeVersionSpecificFeature: IncludeVersionSpecificFeature.always), ...topSortTypeItems]; class SortPicker extends BottomSheetListPicker { - SortPicker({super.key, required super.onSelect, required super.title, List>? items, super.previouslySelected}) : super(items: items ?? defaultSortTypeItems); + final IncludeVersionSpecificFeature includeVersionSpecificFeature; + + static List> getDefaultSortTypeItems({IncludeVersionSpecificFeature includeVersionSpecificFeature = IncludeVersionSpecificFeature.ifSupported}) => [ + ListPickerItem( + payload: SortType.hot, + icon: Icons.local_fire_department_rounded, + label: AppLocalizations.of(GlobalContext.context)!.hot, + ), + ListPickerItem( + payload: SortType.active, + icon: Icons.rocket_launch_rounded, + label: AppLocalizations.of(GlobalContext.context)!.active, + ), + if (includeVersionSpecificFeature == IncludeVersionSpecificFeature.always || + (includeVersionSpecificFeature == IncludeVersionSpecificFeature.ifSupported && LemmyClient.instance.supportsFeature(LemmyFeature.sortTypeScaled))) + ListPickerItem( + payload: SortType.scaled, + icon: Icons.line_weight_rounded, + label: AppLocalizations.of(GlobalContext.context)!.scaled, + ), + if (includeVersionSpecificFeature == IncludeVersionSpecificFeature.always || + (includeVersionSpecificFeature == IncludeVersionSpecificFeature.ifSupported && LemmyClient.instance.supportsFeature(LemmyFeature.sortTypeControversial))) + ListPickerItem( + payload: SortType.controversial, + icon: Icons.warning_rounded, + label: AppLocalizations.of(GlobalContext.context)!.controversial, + ), + ListPickerItem( + payload: SortType.mostComments, + icon: Icons.comment_bank_rounded, + label: AppLocalizations.of(GlobalContext.context)!.mostComments, + ), + ListPickerItem( + payload: SortType.newComments, + icon: Icons.add_comment_rounded, + label: AppLocalizations.of(GlobalContext.context)!.newComments, + ), + ]; + + SortPicker( + {super.key, + required super.onSelect, + required super.title, + List>? items, + super.previouslySelected, + this.includeVersionSpecificFeature = IncludeVersionSpecificFeature.ifSupported}) + : super(items: items ?? getDefaultSortTypeItems(includeVersionSpecificFeature: includeVersionSpecificFeature)); @override State createState() => _SortPickerState(); @@ -96,12 +115,12 @@ class _SortPickerState extends State { transitionBuilder: (Widget child, Animation animation) { return FadeTransition(opacity: animation, child: child); }, - child: topSelected ? topSortPicker() : defaultSortPicker(), + child: topSelected ? topSortPicker() : defaultSortPicker(widget.includeVersionSpecificFeature), ), ); } - Widget defaultSortPicker() { + Widget defaultSortPicker(IncludeVersionSpecificFeature includeVersionSpecificFeature) { final theme = Theme.of(context); return Column( @@ -123,7 +142,7 @@ class _SortPickerState extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), children: [ - ..._generateList(defaultSortTypeItems, theme), + ..._generateList(SortPicker.getDefaultSortTypeItems(includeVersionSpecificFeature: widget.includeVersionSpecificFeature), theme), PickerItem( label: AppLocalizations.of(GlobalContext.context)!.top, icon: Icons.military_tech, diff --git a/pubspec.lock b/pubspec.lock index 87a8f8b81..503a7f7b1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -850,8 +850,8 @@ packages: dependency: "direct main" description: path: "." - ref: "607f817e8687ebc60688fd4e0b51aeb11562cdbf" - resolved-ref: "607f817e8687ebc60688fd4e0b51aeb11562cdbf" + ref: "9c0f20c3ca3cdcfff447034c1b857dca3c0444d4" + resolved-ref: "9c0f20c3ca3cdcfff447034c1b857dca3c0444d4" url: "https://github.com/thunder-app/lemmy_api_client.git" source: git version: "0.21.0" diff --git a/pubspec.yaml b/pubspec.yaml index b7d5a5083..4b67cd4b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: lemmy_api_client: git: url: https://github.com/thunder-app/lemmy_api_client.git - ref: 607f817e8687ebc60688fd4e0b51aeb11562cdbf + ref: 9c0f20c3ca3cdcfff447034c1b857dca3c0444d4 link_preview_generator: git: url: https://github.com/thunder-app/link_preview_generator.git