diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f5ce1153..bb005bd23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Unreleased ### Added +- Added additional font scale option for medium - Added ability to subscribe/unsubscribe to community from long press action on posts - Added option to hide top app bar on scroll diff --git a/lib/core/enums/font_scale.dart b/lib/core/enums/font_scale.dart index a7e426773..e14425208 100644 --- a/lib/core/enums/font_scale.dart +++ b/lib/core/enums/font_scale.dart @@ -2,9 +2,14 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:thunder/utils/global_context.dart'; + enum FontScale { small, base, + medium, large, extraLarge, } @@ -18,6 +23,9 @@ extension FontScaleExtension on FontScale { case FontScale.base: if (!kIsWeb && Platform.isIOS) return 1; return 0.95; + case FontScale.medium: + if (!kIsWeb && Platform.isIOS) return 1.1; + return 1.05; case FontScale.large: if (!kIsWeb && Platform.isIOS) return 1.15; return 1.2; @@ -28,15 +36,19 @@ extension FontScaleExtension on FontScale { } String get label { + final l10n = AppLocalizations.of(GlobalContext.context)!; + switch (this) { case FontScale.small: - return 'Small'; + return l10n.small; case FontScale.base: - return 'Base'; + return l10n.base; + case FontScale.medium: + return l10n.medium; case FontScale.large: - return 'Large'; + return l10n.large; case FontScale.extraLarge: - return 'Extra Large'; + return l10n.extraLarge; } } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8cc295edb..b7cfbeadd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -73,6 +73,10 @@ "@backButton": {}, "backToTop": "Back To Top", "@backToTop": {}, + "base": "Base", + "@base": { + "description": "Description for base font scale" + }, "blockCommunity": "Block Community", "@blockCommunity": {}, "blockInstance": "Block Instance", @@ -299,6 +303,10 @@ "@dangerZone": { "description": "Describes a section where the actions could be dangerous (e.g., permanently deleting account)" }, + "dark": "Dark", + "@dark": { + "description": "Describes using the dark theme" + }, "debug": "Debug", "@debug": { "description": "Debug settings category." @@ -429,6 +437,10 @@ "@expanded": { "description": "Expanded post body view type" }, + "extraLarge": "Extra Large", + "@extraLarge": { + "description": "Description for extra large font scale" + }, "failedToBlock": "Failed to block: {errorMessage}", "@failedToBlock": {}, "failedToLoadBlocks": "Could not load blocks: {errorMessage}", @@ -475,6 +487,10 @@ "@floatingActionButtonSinglePressDescription": { "description": "Description of the FAB's single-press action" }, + "fonts": "Fonts", + "@fonts": { + "description": "Settings category for fonts" + }, "fullScreenNavigationSwipeDescription": "Swipe anywhere to go back when left-to-right gestures are disabled", "@fullScreenNavigationSwipeDescription": {}, "fullscreenSwipeGestures": "Fullscreen Swipe Gestures", @@ -561,6 +577,10 @@ "@languageNotAllowed": { "description": "The error message for the language_not_allowed Lemmy exception" }, + "large": "Large", + "@large": { + "description": "Description for large font scale" + }, "leftLongSwipe": "Left Long Swipe", "@leftLongSwipe": { "description": "Setting for left long swipe" @@ -569,6 +589,10 @@ "@leftShortSwipe": { "description": "Setting for left short swipe" }, + "light": "Light", + "@light": { + "description": "Describes using the light theme" + }, "link": "{count, plural, zero {Link} one {Link} other {Links} } ", "@link": {}, "linkActions": "Link Actions", @@ -611,6 +635,10 @@ "@markPostAsReadOnMediaView": { "description": "Toggle to mark posts as read after viewing media." }, + "medium": "Medium", + "@medium": { + "description": "Description for medium font scale" + }, "mention": "{count, plural, zero {Mention} one {Mention} other {Mentions} }", "@mention": {}, "message": "{count, plural, zero {Message} one {Message} other {Messages} }", @@ -779,7 +807,7 @@ "@postContentFontScale": { "description": "Setting for post content font scale" }, - "postCreatedSuccessfully" :"Post created successfully!", + "postCreatedSuccessfully": "Post created successfully!", "@postCreatedSuccessfully": { "description": "Notifying the user that their post was created successfully" }, @@ -823,6 +851,10 @@ "@profileAppliedSuccessfully": {}, "profiles": "Profiles", "@profiles": {}, + "pureBlack": "Pure Black", + "@pureBlack": { + "description": "Describes using the pure black theme" + }, "reachedTheBottom": "Hmmm. It seems like you've reached the bottom.", "@reachedTheBottom": {}, "readAll": "Read All", @@ -1073,6 +1105,10 @@ "@sidebarBottomNavDoubleTapDescription": {}, "sidebarBottomNavSwipeDescription": "Swipe bottom nav to open sidebar", "@sidebarBottomNavSwipeDescription": {}, + "small": "Small", + "@small": { + "description": "Description for small font scale" + }, "somethingWentWrong": "Oops, something went wrong!", "@somethingWentWrong": {}, "sortBy": "Sort By", @@ -1109,6 +1145,10 @@ "@suggestedTitle": { "description": "Suggested title for the post." }, + "system": "System", + "@system": { + "description": "Describes using the system settings for theme" + }, "tabletMode": "Tablet Mode (2-column view)", "@tabletMode": { "description": "Toggle to enable 2-column tablet mode." @@ -1257,6 +1297,10 @@ "@useMaterialYouTheme": { "description": "Toggle to use Material You theme." }, + "useMaterialYouThemeDescription": "Overrides the selected custom theme", + "@useMaterialYouThemeDescription": { + "description": "Subtitle of the setting for using Material You theme" + }, "useSuggestedTitle": "Use suggested title: {title}", "@useSuggestedTitle": {}, "userFormat": "User Format", diff --git a/lib/settings/pages/theme_settings_page.dart b/lib/settings/pages/theme_settings_page.dart index 75cc06a35..ffc8db32c 100644 --- a/lib/settings/pages/theme_settings_page.dart +++ b/lib/settings/pages/theme_settings_page.dart @@ -17,6 +17,7 @@ import 'package:thunder/settings/widgets/list_option.dart'; import 'package:thunder/settings/widgets/toggle_option.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; import 'package:thunder/utils/bottom_sheet_list_picker.dart'; +import 'package:thunder/utils/global_context.dart'; class ThemeSettingsPage extends StatefulWidget { const ThemeSettingsPage({super.key}); @@ -26,6 +27,8 @@ class ThemeSettingsPage extends StatefulWidget { } class _ThemeSettingsPageState extends State { + final l10n = AppLocalizations.of(GlobalContext.context)!; + /// -------------------------- Theme Related Settings -------------------------- // Theme Settings ThemeType themeType = ThemeType.system; @@ -42,7 +45,7 @@ class _ThemeSettingsPageState extends State { payload: CustomThemeType.deepBlue), ...CustomThemeType.values.where((element) => element != CustomThemeType.deepBlue).map((CustomThemeType scheme) { return ListPickerItem(colors: [scheme.primaryColor, scheme.secondaryColor, scheme.tertiaryColor], label: scheme.label, payload: scheme); - }).toList() + }) ]; // Font Settings @@ -51,21 +54,11 @@ class _ThemeSettingsPageState extends State { FontScale commentFontSizeScale = FontScale.base; FontScale metadataFontSizeScale = FontScale.base; - //Theme - List themeOptions = [ - const ListPickerItem(icon: Icons.phonelink_setup_rounded, label: 'System', payload: ThemeType.system), - const ListPickerItem(icon: Icons.light_mode_rounded, label: 'Light', payload: ThemeType.light), - const ListPickerItem(icon: Icons.dark_mode_outlined, label: 'Dark', payload: ThemeType.dark), - const ListPickerItem(icon: Icons.dark_mode, label: 'Pure Black', payload: ThemeType.pureBlack) - ]; + /// Theme - this is initialized in initState since we need to get l10n for localization strings + List themeOptions = []; - // Font size - List fontScaleOptions = [ - ListPickerItem(icon: Icons.text_fields_rounded, label: FontScale.small.label, payload: FontScale.small), - ListPickerItem(icon: Icons.text_fields_rounded, label: FontScale.base.label, payload: FontScale.base), - ListPickerItem(icon: Icons.text_fields_rounded, label: FontScale.large.label, payload: FontScale.large), - ListPickerItem(icon: Icons.text_fields_rounded, label: FontScale.extraLarge.label, payload: FontScale.extraLarge), - ]; + /// Font size scales + List fontScaleOptions = []; // Loading bool isLoading = true; @@ -80,6 +73,7 @@ class _ThemeSettingsPageState extends State { await prefs.setInt(LocalSettings.appTheme.name, value); setState(() => themeType = ThemeType.values[value]); if (context.mounted) context.read().add(ThemeChangeEvent()); + Future.delayed(const Duration(milliseconds: 300), () => _initFontScaleOptions()); // Refresh the font scale options since the textTheme has most likely changed (dark -> light and vice versa) break; case LocalSettings.appThemeAccentColor: await prefs.setString(LocalSettings.appThemeAccentColor.name, (value as CustomThemeType).name); @@ -140,9 +134,39 @@ class _ThemeSettingsPageState extends State { }); } + void _initFontScaleOptions() { + final theme = Theme.of(context); + + setState(() { + fontScaleOptions = FontScale.values + .map( + (FontScale fontScale) => ListPickerItem( + icon: Icons.text_fields_rounded, + label: fontScale.label, + payload: fontScale, + textTheme: theme.textTheme.copyWith( + bodyMedium: theme.textTheme.bodyMedium?.copyWith( + fontSize: MediaQuery.textScalerOf(context).scale(theme.textTheme.bodyMedium!.fontSize! * fontScale.textScaleFactor), + ), + ), + ), + ) + .toList(); + }); + } + @override void initState() { + themeOptions = [ + ListPickerItem(icon: Icons.phonelink_setup_rounded, label: l10n.system, payload: ThemeType.system), + ListPickerItem(icon: Icons.light_mode_rounded, label: l10n.light, payload: ThemeType.light), + ListPickerItem(icon: Icons.dark_mode_outlined, label: l10n.dark, payload: ThemeType.dark), + ListPickerItem(icon: Icons.dark_mode, label: l10n.pureBlack, payload: ThemeType.pureBlack) + ]; + WidgetsBinding.instance.addPostFrameCallback((_) => _initPreferences()); + WidgetsBinding.instance.addPostFrameCallback((_) => _initFontScaleOptions()); + super.initState(); } @@ -152,7 +176,7 @@ class _ThemeSettingsPageState extends State { final ThemeData theme = Theme.of(context); return Scaffold( - appBar: AppBar(title: const Text('Theming'), centerTitle: false), + appBar: AppBar(title: Text(l10n.theming), centerTitle: false), body: isLoading ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView( @@ -166,10 +190,7 @@ class _ThemeSettingsPageState extends State { children: [ Padding( padding: const EdgeInsets.only(left: 4.0, bottom: 8.0), - child: Text( - l10n.theme, - style: theme.textTheme.titleLarge, - ), + child: Text(l10n.theme, style: theme.textTheme.titleLarge), ), ListOption( description: l10n.theme, @@ -227,7 +248,7 @@ class _ThemeSettingsPageState extends State { if (!kIsWeb && Platform.isAndroid) ...[ ToggleOption( description: l10n.useMaterialYouTheme, - subtitle: 'Overrides the selected custom theme', + subtitle: l10n.useMaterialYouThemeDescription, value: useMaterialYouTheme, iconEnabled: Icons.color_lens_rounded, iconDisabled: Icons.color_lens_rounded, @@ -245,10 +266,7 @@ class _ThemeSettingsPageState extends State { children: [ Padding( padding: const EdgeInsets.only(left: 4.0, bottom: 8.0), - child: Text( - 'Fonts', - style: theme.textTheme.titleLarge, - ), + child: Text(l10n.fonts, style: theme.textTheme.titleLarge), ), ListOption( description: l10n.postTitleFontScale, diff --git a/lib/shared/comment_header.dart b/lib/shared/comment_header.dart index 65b3099b7..cf37636fa 100644 --- a/lib/shared/comment_header.dart +++ b/lib/shared/comment_header.dart @@ -157,7 +157,7 @@ class CommentHeader extends StatelessWidget { TextSpan( style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, - fontSize: MediaQuery.textScalerOf(context).scale(theme.textTheme.bodyMedium!.fontSize! * state.titleFontSizeScale.textScaleFactor), + fontSize: MediaQuery.textScalerOf(context).scale(theme.textTheme.bodyMedium!.fontSize! * state.metadataFontSizeScale.textScaleFactor), ), text: comment.creator.displayName != null && state.useDisplayNames ? comment.creator.displayName! : comment.creator.name, children: [ @@ -166,7 +166,7 @@ class CommentHeader extends StatelessWidget { text: generateUserFullNameSuffix(context, fetchInstanceNameFromUrl(comment.creator.actorId)), style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w300, - fontSize: MediaQuery.textScalerOf(context).scale(theme.textTheme.bodyMedium!.fontSize! * state.titleFontSizeScale.textScaleFactor), + fontSize: MediaQuery.textScalerOf(context).scale(theme.textTheme.bodyMedium!.fontSize! * state.metadataFontSizeScale.textScaleFactor), ), ) ]), diff --git a/lib/shared/picker_item.dart b/lib/shared/picker_item.dart index 8e1240823..74b2f1894 100644 --- a/lib/shared/picker_item.dart +++ b/lib/shared/picker_item.dart @@ -7,6 +7,7 @@ class PickerItem extends StatelessWidget { final IconData? trailingIcon; final void Function()? onSelected; final bool? isSelected; + final TextTheme? textTheme; const PickerItem({ super.key, @@ -16,6 +17,7 @@ class PickerItem extends StatelessWidget { this.isSelected, this.trailingIcon, this.leading, + this.textTheme, }); @override @@ -32,7 +34,10 @@ class PickerItem extends StatelessWidget { child: ListTile( title: Text( label, - style: theme.textTheme.bodyMedium?.copyWith(color: theme.textTheme.bodyMedium?.color?.withOpacity(onSelected == null ? 0.5 : 1)), + style: (textTheme?.bodyMedium ?? theme.textTheme.bodyMedium)?.copyWith( + color: (textTheme?.bodyMedium ?? theme.textTheme.bodyMedium)?.color?.withOpacity(onSelected == null ? 0.5 : 1), + ), + textScaler: TextScaler.noScaling, ), leading: icon != null ? Icon(icon) : this.leading, trailing: Icon(trailingIcon), diff --git a/lib/utils/bottom_sheet_list_picker.dart b/lib/utils/bottom_sheet_list_picker.dart index 2ebf0f70c..d96dbd768 100644 --- a/lib/utils/bottom_sheet_list_picker.dart +++ b/lib/utils/bottom_sheet_list_picker.dart @@ -64,6 +64,7 @@ class _BottomSheetListPickerState extends State> { (item) => PickerItem( label: item.capitalizeLabel ? item.label.capitalize : item.label, icon: item.icon, + textTheme: item.textTheme, onSelected: () { if (widget.closeOnSelect) { Navigator.of(context).pop(); @@ -154,6 +155,7 @@ class ListPickerItem { final String label; final T payload; final bool capitalizeLabel; + final TextTheme? textTheme; const ListPickerItem({ this.icon, @@ -161,5 +163,6 @@ class ListPickerItem { required this.label, required this.payload, this.capitalizeLabel = true, + this.textTheme, }); }