From 45c647a8a794d3a1a656d2970802850053ee1c1f Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Wed, 31 Dec 2025 11:05:23 -0800 Subject: [PATCH 1/2] feat: improve bloc architecture and split up ThunderBloc into smaller blocs --- lib/main.dart | 28 +- lib/src/app/bloc/thunder_bloc.dart | 381 +----- lib/src/app/bloc/thunder_event.dart | 17 - lib/src/app/bloc/thunder_state.dart | 828 +------------ .../comment_preferences_cubit.dart | 49 + .../comment_preferences_state.dart | 72 ++ lib/src/app/cubits/fab_cubit/fab_cubit.dart | 49 + lib/src/app/cubits/fab_cubit/fab_state.dart | 39 + .../fab_preferences_cubit.dart | 71 ++ .../fab_preferences_state.dart | 149 +++ .../feed_preferences_cubit.dart | 130 +++ .../feed_preferences_state.dart | 262 +++++ .../cubits/feed_ui_cubit/feed_ui_cubit.dart | 45 + .../cubits/feed_ui_cubit/feed_ui_state.dart | 51 + .../gesture_preferences_cubit.dart | 66 ++ .../gesture_preferences_state.dart | 116 ++ .../nav_bar_state_cubit.dart | 14 + .../nav_bar_state_state.dart | 21 + .../theme_preferences_cubit.dart | 103 ++ .../theme_preferences_state.dart | 207 ++++ .../video_preferences_cubit.dart | 43 + .../video_preferences_state.dart | 58 + lib/src/app/pages/thunder_page.dart | 217 ++-- lib/src/app/theme/bloc/theme_bloc.dart | 63 - lib/src/app/theme/bloc/theme_event.dart | 10 - lib/src/app/theme/bloc/theme_state.dart | 43 - lib/src/app/theme/theme.dart | 1 - lib/src/app/thunder.dart | 2 + lib/src/app/utils/navigation.dart | 114 +- lib/src/app/widgets/bottom_nav_bar.dart | 23 +- lib/src/core/database/database.g.dart | 1026 +++++------------ lib/src/core/database/database.steps.dart | 270 ++--- lib/src/core/enums/fab_action.dart | 6 +- lib/src/core/enums/full_name.dart | 14 +- lib/src/core/enums/swipe_action.dart | 16 +- .../presentation/pages/account_page.dart | 10 +- .../widgets/account_placeholder.dart | 2 +- .../widgets/profile_modal_body.dart | 12 +- .../general_comment_action_bottom_sheet.dart | 22 +- .../comment_card/additional_comment_card.dart | 20 +- .../widgets/comment_card/comment_card.dart | 18 +- .../comment_card/comment_card_background.dart | 6 +- .../comment_card_button_actions.dart | 6 +- .../comment_card_header.dart | 20 +- .../comment_card_header_date.dart | 16 +- .../comment_card_header_reply_count.dart | 13 +- .../comment_card_header_score.dart | 41 +- .../widgets/comment_card/comment_content.dart | 9 +- .../widgets/community_drawer.dart | 21 +- .../presentation/widgets/post_card.dart | 63 +- .../widgets/post_card_actions.dart | 20 +- .../widgets/post_card_metadata.dart | 50 +- .../widgets/post_card_view_comfortable.dart | 19 +- .../widgets/post_card_view_compact.dart | 13 +- .../feed/presentation/bloc/feed_bloc.dart | 44 - .../feed/presentation/bloc/feed_state.dart | 35 - .../feed/presentation/pages/feed_page.dart | 387 ++++--- .../widgets/feed_card_divider.dart | 6 +- .../feed/presentation/widgets/feed_fab.dart | 37 +- .../widgets/feed_post_card_list.dart | 45 +- .../feed/presentation/widgets/tagline.dart | 4 +- .../presentation/pages/report_page.dart | 9 +- .../bloc/modlog_cubit.freezed.dart | 72 +- .../widgets/modlog_item_card.dart | 8 +- .../widgets/modlog_item_context_card.dart | 35 +- .../post/domain/enums/post_status.dart | 9 +- lib/src/features/post/post.dart | 1 + .../post/presentation/bloc/post_bloc.dart | 72 +- .../post/presentation/bloc/post_event.dart | 32 +- .../post/presentation/bloc/post_state.dart | 31 - .../post_navigation_cubit.dart | 135 +++ .../post_navigation_state.dart | 52 + .../presentation/pages/create_post_page.dart | 4 +- .../post/presentation/pages/post_page.dart | 64 +- .../widgets/post_body/post_body.dart | 19 +- .../post_body/post_body_action_bar.dart | 31 +- .../widgets/post_body/post_body_preview.dart | 6 +- .../widgets/post_body/post_body_title.dart | 10 +- .../general_post_action_bottom_sheet.dart | 26 +- .../presentation/widgets/post_card_title.dart | 4 +- .../widgets/post_page_app_bar.dart | 5 +- .../presentation/widgets/post_page_fab.dart | 71 +- .../widgets/post_status_icon.dart | 4 +- .../presentation/pages/search_page.dart | 16 +- .../pages/accessibility_settings_page.dart | 4 +- .../comment_appearance_settings_page.dart | 3 + .../presentation/pages/fab_settings_page.dart | 2 + .../pages/filter_settings_page.dart | 2 + .../pages/general_settings_page.dart | 3 + .../pages/gesture_settings_page.dart | 2 + .../pages/post_appearance_settings_page.dart | 3 + .../pages/theme_settings_page.dart | 35 +- .../pages/video_player_settings.dart | 4 +- .../pages/media_management_page.dart | 21 +- .../user/presentation/utils/user_groups.dart | 4 +- .../presentation/widgets/user_label_chip.dart | 5 +- lib/src/shared/comment_reference.dart | 4 +- lib/src/shared/full_name_widgets.dart | 28 +- lib/src/shared/gesture_fab.dart | 72 +- .../shared/markdown/common_markdown_body.dart | 13 +- lib/src/shared/markdown/markdown_spoiler.dart | 15 +- .../markdown/markdown_subsuperscript.dart | 6 +- lib/src/shared/pages/loading_page.dart | 202 ++-- lib/src/shared/utils/colors.dart | 6 +- lib/src/shared/utils/links.dart | 24 +- lib/src/shared/utils/media/video.dart | 12 +- lib/src/shared/utils/swipe.dart | 39 +- .../src/thunder_video_player.dart | 20 +- .../src/thunder_youtube_player.dart | 32 +- .../shared/widgets/chips/community_chip.dart | 13 +- lib/src/shared/widgets/chips/user_chip.dart | 21 +- .../shared/widgets/comment_navigator_fab.dart | 10 +- .../media/compact_thumbnail_preview.dart | 7 +- .../widgets/media/media_type_badge.dart | 4 +- lib/src/shared/widgets/media/media_view.dart | 4 +- .../widgets/text/selectable_text_modal.dart | 28 +- test/utils/user_groups_test.dart | 8 +- 117 files changed, 3422 insertions(+), 3493 deletions(-) create mode 100644 lib/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart create mode 100644 lib/src/app/cubits/comment_preferences_cubit/comment_preferences_state.dart create mode 100644 lib/src/app/cubits/fab_cubit/fab_cubit.dart create mode 100644 lib/src/app/cubits/fab_cubit/fab_state.dart create mode 100644 lib/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart create mode 100644 lib/src/app/cubits/fab_preferences_cubit/fab_preferences_state.dart create mode 100644 lib/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart create mode 100644 lib/src/app/cubits/feed_preferences_cubit/feed_preferences_state.dart create mode 100644 lib/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart create mode 100644 lib/src/app/cubits/feed_ui_cubit/feed_ui_state.dart create mode 100644 lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart create mode 100644 lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_state.dart create mode 100644 lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_cubit.dart create mode 100644 lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_state.dart create mode 100644 lib/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart create mode 100644 lib/src/app/cubits/theme_preferences_cubit/theme_preferences_state.dart create mode 100644 lib/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart create mode 100644 lib/src/app/cubits/video_preferences_cubit/video_preferences_state.dart delete mode 100644 lib/src/app/theme/bloc/theme_bloc.dart delete mode 100644 lib/src/app/theme/bloc/theme_event.dart delete mode 100644 lib/src/app/theme/bloc/theme_state.dart delete mode 100644 lib/src/app/theme/theme.dart create mode 100644 lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_cubit.dart create mode 100644 lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_state.dart diff --git a/lib/main.dart b/lib/main.dart index def5885e4..184c9eade 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,12 +26,18 @@ import 'package:thunder/src/core/database/database.dart'; import 'package:thunder/src/core/enums/local_settings.dart'; import 'package:thunder/src/core/enums/theme_type.dart'; import 'package:thunder/src/core/singletons/preferences.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/features/inbox/inbox.dart'; import 'package:thunder/src/features/notification/notification.dart'; import 'package:thunder/src/features/search/search.dart'; import 'package:thunder/src/app/cubits/notifications_cubit/notifications_cubit.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; +import 'package:thunder/src/app/cubits/nav_bar_state_cubit/nav_bar_state_cubit.dart'; +import 'package:thunder/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart'; import 'package:thunder/src/app/thunder.dart'; import 'package:thunder/src/shared/utils/cache.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -140,20 +146,24 @@ class _ThunderAppState extends State { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => ThemeBloc()), BlocProvider(create: (context) => DeepLinksCubit()), BlocProvider(create: (context) => NotificationsCubit(notificationsStream: notificationsStreamController.stream)), BlocProvider(create: (context) => ThunderBloc()), + BlocProvider(create: (context) => GesturePreferencesCubit()), + BlocProvider(create: (context) => FeedPreferencesCubit()), + BlocProvider(create: (context) => CommentPreferencesCubit()), + BlocProvider(create: (context) => ThemePreferencesCubit()), + BlocProvider(create: (context) => VideoPreferencesCubit()), + BlocProvider(create: (context) => FabPreferencesCubit()), + BlocProvider(create: (context) => FabStateCubit()), + BlocProvider(create: (context) => NavBarStateCubit()), + BlocProvider(create: (context) => FeedUiCubit()), BlocProvider(create: (context) => AnonymousSubscriptionsBloc()), BlocProvider(create: (context) => NetworkCheckerCubit()..getConnectionType()) ], - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { - final ThunderBloc thunderBloc = context.watch(); - - if (state.status == ThemeStatus.initial) { - context.read().add(ThemeChangeEvent()); - } + final appLanguageCode = context.select((bloc) => bloc.state.appLanguageCode); return DynamicColorBuilder( builder: (lightColorScheme, darkColorScheme) { @@ -206,7 +216,7 @@ class _ThunderAppState extends State { ), ); - Locale? locale = LanguageLocal.parseLanguageTag(thunderBloc.state.appLanguageCode); + Locale? locale = LanguageLocal.parseLanguageTag(appLanguageCode ?? 'en'); return OverlaySupport.global( child: AnnotatedRegion( diff --git a/lib/src/app/bloc/thunder_bloc.dart b/lib/src/app/bloc/thunder_bloc.dart index 6adf96753..74240dc36 100644 --- a/lib/src/app/bloc/thunder_bloc.dart +++ b/lib/src/app/bloc/thunder_bloc.dart @@ -1,35 +1,15 @@ -import 'package:flutter/material.dart'; - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:bloc_concurrency/bloc_concurrency.dart'; -import 'package:intl/intl.dart'; import 'package:stream_transform/stream_transform.dart'; -import 'package:thunder/src/core/enums/action_color.dart'; import 'package:thunder/src/core/enums/browser_mode.dart'; -import 'package:thunder/src/core/enums/comment_sort_type.dart'; -import 'package:thunder/src/core/enums/custom_theme_type.dart'; -import 'package:thunder/src/core/enums/fab_action.dart'; -import 'package:thunder/src/core/enums/feed_card_divider_thickness.dart'; -import 'package:thunder/src/core/enums/enums.dart'; -import 'package:thunder/src/core/enums/post_sort_type.dart'; -import 'package:thunder/src/core/enums/font_scale.dart'; -import 'package:thunder/src/core/enums/full_name.dart'; import 'package:thunder/src/core/enums/image_caching_mode.dart'; import 'package:thunder/src/core/enums/local_settings.dart'; -import 'package:thunder/src/core/enums/nested_comment_indicator.dart'; -import 'package:thunder/src/core/enums/video_player_mode.dart'; import 'package:thunder/src/features/notification/notification.dart'; -import 'package:thunder/src/core/enums/post_body_view_type.dart'; -import 'package:thunder/src/core/enums/swipe_action.dart'; -import 'package:thunder/src/core/enums/theme_type.dart'; -import 'package:thunder/src/core/enums/video_auto_play.dart'; -import 'package:thunder/src/core/enums/video_playback_speed.dart'; import 'package:thunder/src/core/models/version.dart'; import 'package:thunder/src/core/singletons/preferences.dart'; import 'package:thunder/src/core/update/check_github_update.dart'; -import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/shared/utils/constants.dart'; part 'thunder_event.dart'; @@ -52,20 +32,9 @@ class ThunderBloc extends Bloc { _userPreferencesChangeEvent, transformer: throttleDroppable(throttleDuration), ); - on( - _onFabToggle, - transformer: throttleDroppable(throttleDuration), - ); - on( - _onFabSummonToggle, - transformer: throttleDroppable(throttleDuration), - ); on( _onSetCurrentAnonymousInstance, ); - on( - _onBottomNavBarVisibilityChange, - ); } /// This event should be triggered at the start of the app. @@ -77,387 +46,57 @@ class ThunderBloc extends Bloc { Version version = await fetchVersion(); add(UserPreferencesChangeEvent()); - emit(state.copyWith(status: ThunderStatus.success, feedCardDividerColor: state.feedCardDividerColor, version: version)); + emit(state.copyWith(status: ThunderStatus.success, version: version)); } catch (e) { - return emit(state.copyWith(status: ThunderStatus.failure, feedCardDividerColor: state.feedCardDividerColor, errorMessage: e.toString())); + return emit(state.copyWith(status: ThunderStatus.failure, errorMessage: e.toString())); } } Future _userPreferencesChangeEvent(UserPreferencesChangeEvent event, Emitter emit) async { try { - emit(state.copyWith(status: ThunderStatus.refreshing, feedCardDividerColor: state.feedCardDividerColor)); - - /// -------------------------- Feed Related Settings -------------------------- - // Default Listing/Sort Settings - FeedListType defaultFeedListType = DEFAULT_LISTING_TYPE; - PostSortType defaultPostSortType = DEFAULT_POST_SORT_TYPE; - try { - defaultFeedListType = FeedListType.values.byName(UserPreferences.getLocalSetting(LocalSettings.defaultFeedListType) ?? DEFAULT_LISTING_TYPE.name); - defaultPostSortType = PostSortType.values.byName(UserPreferences.getLocalSetting(LocalSettings.defaultFeedPostSortType) ?? DEFAULT_POST_SORT_TYPE.name); - } catch (e) { - defaultFeedListType = FeedListType.values.byName(DEFAULT_LISTING_TYPE.name); - defaultPostSortType = PostSortType.values.byName(DEFAULT_POST_SORT_TYPE.name); - } - - bool useProfilePictureForDrawer = UserPreferences.getLocalSetting(LocalSettings.useProfilePictureForDrawer) ?? false; - - // NSFW Settings - bool hideNsfwPosts = UserPreferences.getLocalSetting(LocalSettings.hideNsfwPosts) ?? false; - bool hideNsfwPreviews = UserPreferences.getLocalSetting(LocalSettings.hideNsfwPreviews) ?? true; + emit(state.copyWith(status: ThunderStatus.refreshing)); // Tablet Settings bool tabletMode = UserPreferences.getLocalSetting(LocalSettings.useTabletMode) ?? false; // General Settings + BrowserMode browserMode = BrowserMode.values.byName(UserPreferences.getLocalSetting(LocalSettings.browserMode) ?? BrowserMode.customTabs.name); bool openInReaderMode = UserPreferences.getLocalSetting(LocalSettings.openLinksInReaderMode) ?? false; - bool useDisplayNamesForUsers = UserPreferences.getLocalSetting(LocalSettings.useDisplayNamesForUsers) ?? false; - bool useDisplayNamesForCommunities = UserPreferences.getLocalSetting(LocalSettings.useDisplayNamesForCommunities) ?? false; - bool markPostReadOnMediaView = UserPreferences.getLocalSetting(LocalSettings.markPostAsReadOnMediaView) ?? false; - bool markPostReadOnScroll = UserPreferences.getLocalSetting(LocalSettings.markPostAsReadOnScroll) ?? false; bool showInAppUpdateNotification = UserPreferences.getLocalSetting(LocalSettings.showInAppUpdateNotification) ?? false; bool showUpdateChangelogs = UserPreferences.getLocalSetting(LocalSettings.showUpdateChangelogs) ?? true; NotificationType inboxNotificationType = NotificationType.values.byName(UserPreferences.getLocalSetting(LocalSettings.inboxNotificationType) ?? NotificationType.none.name); String? appLanguageCode = UserPreferences.getLocalSetting(LocalSettings.appLanguageCode) ?? 'en'; - FullNameSeparator userSeparator = FullNameSeparator.values.byName(UserPreferences.getLocalSetting(LocalSettings.userFormat) ?? FullNameSeparator.at.name); - NameThickness userFullNameUserNameThickness = NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.userFullNameUserNameThickness) ?? NameThickness.normal.name); - NameColor userFullNameUserNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.userFullNameUserNameColor) ?? NameColor.defaultColor); - NameThickness userFullNameInstanceNameThickness = NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.userFullNameInstanceNameThickness) ?? NameThickness.light.name); - NameColor userFullNameInstanceNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.userFullNameInstanceNameColor) ?? NameColor.defaultColor); - FullNameSeparator communitySeparator = FullNameSeparator.values.byName(UserPreferences.getLocalSetting(LocalSettings.communityFormat) ?? FullNameSeparator.dot.name); - NameThickness communityFullNameCommunityNameThickness = - NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.communityFullNameCommunityNameThickness) ?? NameThickness.normal.name); - NameColor communityFullNameCommunityNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.communityFullNameCommunityNameColor) ?? NameColor.defaultColor); - NameThickness communityFullNameInstanceNameThickness = - NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.communityFullNameInstanceNameThickness) ?? NameThickness.light.name); - NameColor communityFullNameInstanceNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.communityFullNameInstanceNameColor) ?? NameColor.defaultColor); + bool useProfilePictureForDrawer = UserPreferences.getLocalSetting(LocalSettings.useProfilePictureForDrawer) ?? false; ImageCachingMode imageCachingMode = ImageCachingMode.values.byName(UserPreferences.getLocalSetting(LocalSettings.imageCachingMode) ?? ImageCachingMode.relaxed.name); bool showNavigationLabels = UserPreferences.getLocalSetting(LocalSettings.showNavigationLabels) ?? true; bool hideTopBarOnScroll = UserPreferences.getLocalSetting(LocalSettings.hideTopBarOnScroll) ?? false; bool hideBottomBarOnScroll = UserPreferences.getLocalSetting(LocalSettings.hideBottomBarOnScroll) ?? false; - bool showHiddenPosts = UserPreferences.getLocalSetting(LocalSettings.showHiddenPosts) ?? false; - bool showExpandedTaglines = UserPreferences.getLocalSetting(LocalSettings.showExpandedTaglines) ?? false; - - BrowserMode browserMode = BrowserMode.values.byName(UserPreferences.getLocalSetting(LocalSettings.browserMode) ?? BrowserMode.customTabs.name); - - /// -------------------------- Feed Post Related Settings -------------------------- - // Compact Related Settings - bool useCompactView = UserPreferences.getLocalSetting(LocalSettings.useCompactView) ?? false; - bool showTitleFirst = UserPreferences.getLocalSetting(LocalSettings.showPostTitleFirst) ?? false; - bool hideThumbnails = UserPreferences.getLocalSetting(LocalSettings.hideThumbnails) ?? false; - bool showThumbnailPreviewOnRight = UserPreferences.getLocalSetting(LocalSettings.showThumbnailPreviewOnRight) ?? false; - bool showTextPostIndicator = UserPreferences.getLocalSetting(LocalSettings.showTextPostIndicator) ?? false; - bool tappableAuthorCommunity = UserPreferences.getLocalSetting(LocalSettings.tappableAuthorCommunity) ?? false; - - // General Settings - bool showVoteActions = UserPreferences.getLocalSetting(LocalSettings.showPostVoteActions) ?? true; - bool showSaveAction = UserPreferences.getLocalSetting(LocalSettings.showPostSaveAction) ?? true; - bool showCommunityIcons = UserPreferences.getLocalSetting(LocalSettings.showPostCommunityIcons) ?? false; - bool showFullHeightImages = UserPreferences.getLocalSetting(LocalSettings.showPostFullHeightImages) ?? true; - bool showEdgeToEdgeImages = UserPreferences.getLocalSetting(LocalSettings.showPostEdgeToEdgeImages) ?? false; - bool showTextContent = UserPreferences.getLocalSetting(LocalSettings.showPostTextContentPreview) ?? false; - bool showPostAuthor = UserPreferences.getLocalSetting(LocalSettings.showPostAuthor) ?? false; - bool postShowUserInstance = UserPreferences.getLocalSetting(LocalSettings.postShowUserInstance) ?? false; bool scoreCounters = UserPreferences.getLocalSetting(LocalSettings.scoreCounters) ?? false; - bool dimReadPosts = UserPreferences.getLocalSetting(LocalSettings.dimReadPosts) ?? true; - bool showFullPostDate = UserPreferences.getLocalSetting(LocalSettings.showFullPostDate) ?? false; - DateFormat dateFormat = DateFormat(UserPreferences.getLocalSetting(LocalSettings.dateFormat) ?? DateFormat.yMMMMd(Intl.systemLocale).add_jm().pattern); - FeedCardDividerThickness feedCardDividerThickness = - FeedCardDividerThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.feedCardDividerThickness) ?? FeedCardDividerThickness.compact.name); - Color? feedCardDividerColor = UserPreferences.getLocalSetting(LocalSettings.feedCardDividerColor) != null ? Color(UserPreferences.getLocalSetting(LocalSettings.feedCardDividerColor)!) : null; - List compactPostCardMetadataItems = - UserPreferences.getLocalSetting>(LocalSettings.compactPostCardMetadataItems)?.map((e) => PostCardMetadataItem.values.byName(e)).toList() ?? DEFAULT_COMPACT_POST_CARD_METADATA; - List cardPostCardMetadataItems = - UserPreferences.getLocalSetting>(LocalSettings.cardPostCardMetadataItems)?.map((e) => PostCardMetadataItem.values.byName(e)).toList() ?? DEFAULT_CARD_POST_CARD_METADATA; - - // Post body settings - bool showCrossPosts = UserPreferences.getLocalSetting(LocalSettings.showCrossPosts) ?? true; - PostBodyViewType postBodyViewType = PostBodyViewType.values.byName(UserPreferences.getLocalSetting(LocalSettings.postBodyViewType) ?? PostBodyViewType.expanded.name); - bool postBodyShowUserInstance = UserPreferences.getLocalSetting(LocalSettings.postBodyShowUserInstance) ?? false; - bool postBodyShowCommunityInstance = UserPreferences.getLocalSetting(LocalSettings.postBodyShowCommunityInstance) ?? false; - bool postBodyShowCommunityAvatar = UserPreferences.getLocalSetting(LocalSettings.postBodyShowCommunityAvatar) ?? false; - - List keywordFilters = UserPreferences.getLocalSetting(LocalSettings.keywordFilters) ?? []; - - /// -------------------------- Post Page Related Settings -------------------------- - // Comment Related Settings - CommentSortType defaultCommentSortType = CommentSortType.values.byName(UserPreferences.getLocalSetting(LocalSettings.defaultCommentSortType) ?? DEFAULT_COMMENT_SORT_TYPE.name); - bool collapseParentCommentOnGesture = UserPreferences.getLocalSetting(LocalSettings.collapseParentCommentBodyOnGesture) ?? true; - bool showCommentButtonActions = UserPreferences.getLocalSetting(LocalSettings.showCommentActionButtons) ?? false; - bool commentShowUserInstance = UserPreferences.getLocalSetting(LocalSettings.commentShowUserInstance) ?? false; - bool commentShowUserAvatar = UserPreferences.getLocalSetting(LocalSettings.commentShowUserAvatar) ?? false; - bool combineCommentScores = UserPreferences.getLocalSetting(LocalSettings.combineCommentScores) ?? false; - NestedCommentIndicatorStyle nestedCommentIndicatorStyle = - NestedCommentIndicatorStyle.values.byName(UserPreferences.getLocalSetting(LocalSettings.nestedCommentIndicatorStyle) ?? DEFAULT_NESTED_COMMENT_INDICATOR_STYLE.name); - NestedCommentIndicatorColor nestedCommentIndicatorColor = - NestedCommentIndicatorColor.values.byName(UserPreferences.getLocalSetting(LocalSettings.nestedCommentIndicatorColor) ?? DEFAULT_NESTED_COMMENT_INDICATOR_COLOR.name); - - /// -------------------------- Theme Related Settings -------------------------- - // Theme Settings - ThemeType themeType = ThemeType.values[UserPreferences.getLocalSetting(LocalSettings.appTheme) ?? ThemeType.system.index]; - CustomThemeType selectedTheme = CustomThemeType.values.byName(UserPreferences.getLocalSetting(LocalSettings.appThemeAccentColor) ?? CustomThemeType.deepBlue.name); - bool useMaterialYouTheme = UserPreferences.getLocalSetting(LocalSettings.useMaterialYouTheme) ?? false; - - // Color Settings - ActionColor upvoteColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.upvoteColor) ?? ActionColor.orange); - ActionColor downvoteColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.downvoteColor) ?? ActionColor.blue); - ActionColor saveColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.saveColor) ?? ActionColor.purple); - ActionColor markReadColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.markReadColor) ?? ActionColor.teal); - ActionColor replyColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.replyColor) ?? ActionColor.green); - ActionColor hideColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.hideColor) ?? ActionColor.red); - - // Font Settings - FontScale titleFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.titleFontSizeScale) ?? FontScale.base.name); - FontScale contentFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.contentFontSizeScale) ?? FontScale.base.name); - FontScale commentFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentFontSizeScale) ?? FontScale.base.name); - FontScale metadataFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.metadataFontSizeScale) ?? FontScale.base.name); - - /// -------------------------- Gesture Related Settings -------------------------- - // Sidebar Gesture Settings - bool bottomNavBarSwipeGestures = UserPreferences.getLocalSetting(LocalSettings.sidebarBottomNavBarSwipeGesture) ?? true; - bool bottomNavBarDoubleTapGestures = UserPreferences.getLocalSetting(LocalSettings.sidebarBottomNavBarDoubleTapGesture) ?? false; - - // Post Gestures - bool enablePostGestures = UserPreferences.getLocalSetting(LocalSettings.enablePostGestures) ?? true; - SwipeAction leftPrimaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureLeftPrimary) ?? SwipeAction.upvote.name); - SwipeAction leftSecondaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureLeftSecondary) ?? SwipeAction.downvote.name); - SwipeAction rightPrimaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureRightPrimary) ?? SwipeAction.save.name); - SwipeAction rightSecondaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureRightSecondary) ?? SwipeAction.toggleRead.name); - - // Comment Gestures - bool enableCommentGestures = UserPreferences.getLocalSetting(LocalSettings.enableCommentGestures) ?? true; - SwipeAction leftPrimaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureLeftPrimary) ?? SwipeAction.upvote.name); - SwipeAction leftSecondaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureLeftSecondary) ?? SwipeAction.downvote.name); - SwipeAction rightPrimaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureRightPrimary) ?? SwipeAction.reply.name); - SwipeAction rightSecondaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureRightSecondary) ?? SwipeAction.save.name); - - bool enableFullScreenSwipeNavigationGesture = UserPreferences.getLocalSetting(LocalSettings.enableFullScreenSwipeNavigationGesture) ?? true; - - // Image Peek Settings - int imagePeekDuration = UserPreferences.getLocalSetting(LocalSettings.imagePeekDuration) ?? 300; - - /// -------------------------- FAB Related Settings -------------------------- - bool enableFeedsFab = UserPreferences.getLocalSetting(LocalSettings.enableFeedsFab) ?? true; - bool enablePostsFab = UserPreferences.getLocalSetting(LocalSettings.enablePostsFab) ?? true; - - bool enableBackToTop = UserPreferences.getLocalSetting(LocalSettings.enableBackToTop) ?? true; - bool enableSubscriptions = UserPreferences.getLocalSetting(LocalSettings.enableSubscriptions) ?? true; - bool enableRefresh = UserPreferences.getLocalSetting(LocalSettings.enableRefresh) ?? true; - bool enableDismissRead = UserPreferences.getLocalSetting(LocalSettings.enableDismissRead) ?? true; - bool enableChangeSort = UserPreferences.getLocalSetting(LocalSettings.enableChangeSort) ?? true; - bool enableNewPost = UserPreferences.getLocalSetting(LocalSettings.enableNewPost) ?? true; - - bool postFabEnableBackToTop = UserPreferences.getLocalSetting(LocalSettings.postFabEnableBackToTop) ?? true; - bool postFabEnableChangeSort = UserPreferences.getLocalSetting(LocalSettings.postFabEnableChangeSort) ?? true; - bool postFabEnableReplyToPost = UserPreferences.getLocalSetting(LocalSettings.postFabEnableReplyToPost) ?? true; - bool postFabEnableRefresh = UserPreferences.getLocalSetting(LocalSettings.postFabEnableRefresh) ?? true; - bool postFabEnableSearch = UserPreferences.getLocalSetting(LocalSettings.postFabEnableSearch) ?? true; - - FeedFabAction feedFabSinglePressAction = FeedFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.feedFabSinglePressAction) ?? FeedFabAction.newPost.name); - FeedFabAction feedFabLongPressAction = FeedFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.feedFabLongPressAction) ?? FeedFabAction.openFab.name); - PostFabAction postFabSinglePressAction = PostFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postFabSinglePressAction) ?? PostFabAction.replyToPost.name); - PostFabAction postFabLongPressAction = PostFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postFabLongPressAction) ?? PostFabAction.openFab.name); - - bool enableCommentNavigation = UserPreferences.getLocalSetting(LocalSettings.enableCommentNavigation) ?? true; - bool combineNavAndFab = UserPreferences.getLocalSetting(LocalSettings.combineNavAndFab) ?? true; - - /// -------------------------- Accessibility Related Settings -------------------------- - bool reduceAnimations = UserPreferences.getLocalSetting(LocalSettings.reduceAnimations) ?? false; - - /// ------------------ VIDEO PLAYER SETTINGS ----------------------------------------- - bool videoAutoFullscreen = UserPreferences.getLocalSetting(LocalSettings.videoAutoFullscreen) ?? false; - bool videoAutoLoop = UserPreferences.getLocalSetting(LocalSettings.videoAutoLoop) ?? false; - bool videoAutoMute = UserPreferences.getLocalSetting(LocalSettings.videoAutoMute) ?? true; - VideoAutoPlay videoAutoPlay = VideoAutoPlay.values.byName(UserPreferences.getLocalSetting(LocalSettings.videoAutoPlay) ?? VideoAutoPlay.never.name); - VideoPlayBackSpeed videoDefaultPlaybackSpeed = VideoPlayBackSpeed.values.byName(UserPreferences.getLocalSetting(LocalSettings.videoDefaultPlaybackSpeed) ?? VideoPlayBackSpeed.normal.name); - VideoPlayerMode videoPlayerMode = VideoPlayerMode.values.byName(UserPreferences.getLocalSetting(LocalSettings.videoPlayerMode) ?? VideoPlayerMode.inApp.name); String currentAnonymousInstance = UserPreferences.getLocalSetting(LocalSettings.currentAnonymousInstance) ?? DEFAULT_INSTANCE; return emit(state.copyWith( status: ThunderStatus.success, - - /// -------------------------- Feed Related Settings -------------------------- - // Default Listing/Sort Settings - defaultFeedListType: defaultFeedListType, - defaultPostSortType: defaultPostSortType, - useProfilePictureForDrawer: useProfilePictureForDrawer, - - // NSFW Settings - hideNsfwPosts: hideNsfwPosts, - hideNsfwPreviews: hideNsfwPreviews, - - // Tablet Settings tabletMode: tabletMode, - - // General Settings browserMode: browserMode, openInReaderMode: openInReaderMode, - useDisplayNamesForUsers: useDisplayNamesForUsers, - useDisplayNamesForCommunities: useDisplayNamesForCommunities, - markPostReadOnMediaView: markPostReadOnMediaView, - markPostReadOnScroll: markPostReadOnScroll, showInAppUpdateNotification: showInAppUpdateNotification, showUpdateChangelogs: showUpdateChangelogs, inboxNotificationType: inboxNotificationType, appLanguageCode: appLanguageCode, - userSeparator: userSeparator, - userFullNameUserNameThickness: userFullNameUserNameThickness, - userFullNameUserNameColor: userFullNameUserNameColor, - userFullNameInstanceNameThickness: userFullNameInstanceNameThickness, - userFullNameInstanceNameColor: userFullNameInstanceNameColor, - communitySeparator: communitySeparator, - communityFullNameCommunityNameThickness: communityFullNameCommunityNameThickness, - communityFullNameCommunityNameColor: communityFullNameCommunityNameColor, - communityFullNameInstanceNameThickness: communityFullNameInstanceNameThickness, - communityFullNameInstanceNameColor: communityFullNameInstanceNameColor, + useProfilePictureForDrawer: useProfilePictureForDrawer, imageCachingMode: imageCachingMode, showNavigationLabels: showNavigationLabels, hideTopBarOnScroll: hideTopBarOnScroll, hideBottomBarOnScroll: hideBottomBarOnScroll, - showHiddenPosts: showHiddenPosts, - showExpandedTaglines: showExpandedTaglines, - - /// -------------------------- Feed Post Related Settings -------------------------- - // Compact Related Settings - useCompactView: useCompactView, - showTitleFirst: showTitleFirst, - hideThumbnails: hideThumbnails, - showThumbnailPreviewOnRight: showThumbnailPreviewOnRight, - showTextPostIndicator: showTextPostIndicator, - tappableAuthorCommunity: tappableAuthorCommunity, - - // General Settings - showVoteActions: showVoteActions, - showSaveAction: showSaveAction, - showCommunityIcons: showCommunityIcons, - showFullHeightImages: showFullHeightImages, - showEdgeToEdgeImages: showEdgeToEdgeImages, - showTextContent: showTextContent, - showPostAuthor: showPostAuthor, - postShowUserInstance: postShowUserInstance, scoreCounters: scoreCounters, - dimReadPosts: dimReadPosts, - showFullPostDate: showFullPostDate, - dateFormat: dateFormat, - feedCardDividerThickness: feedCardDividerThickness, - feedCardDividerColor: feedCardDividerColor, - compactPostCardMetadataItems: compactPostCardMetadataItems, - cardPostCardMetadataItems: cardPostCardMetadataItems, - keywordFilters: keywordFilters, - - // Post body settings - showCrossPosts: showCrossPosts, - postBodyViewType: postBodyViewType, - postBodyShowUserInstance: postBodyShowUserInstance, - postBodyShowCommunityInstance: postBodyShowCommunityInstance, - postBodyShowCommunityAvatar: postBodyShowCommunityAvatar, - - /// -------------------------- Post Page Related Settings -------------------------- - // Comment Related Settings - defaultCommentSortType: defaultCommentSortType, - collapseParentCommentOnGesture: collapseParentCommentOnGesture, - showCommentButtonActions: showCommentButtonActions, - commentShowUserInstance: commentShowUserInstance, - commentShowUserAvatar: commentShowUserAvatar, - combineCommentScores: combineCommentScores, - nestedCommentIndicatorStyle: nestedCommentIndicatorStyle, - nestedCommentIndicatorColor: nestedCommentIndicatorColor, - - /// -------------------------- Theme Related Settings -------------------------- - // Theme Settings - themeType: themeType, - selectedTheme: selectedTheme, - useMaterialYouTheme: useMaterialYouTheme, - - // Color Settings - upvoteColor: upvoteColor, - downvoteColor: downvoteColor, - saveColor: saveColor, - markReadColor: markReadColor, - replyColor: replyColor, - hideColor: hideColor, - - // Font Settings - titleFontSizeScale: titleFontSizeScale, - contentFontSizeScale: contentFontSizeScale, - commentFontSizeScale: commentFontSizeScale, - metadataFontSizeScale: metadataFontSizeScale, - - /// -------------------------- Gesture Related Settings -------------------------- - // Sidebar Gesture Settings - bottomNavBarSwipeGestures: bottomNavBarSwipeGestures, - bottomNavBarDoubleTapGestures: bottomNavBarDoubleTapGestures, - - // Post Gestures - enablePostGestures: enablePostGestures, - leftPrimaryPostGesture: leftPrimaryPostGesture, - leftSecondaryPostGesture: leftSecondaryPostGesture, - rightPrimaryPostGesture: rightPrimaryPostGesture, - rightSecondaryPostGesture: rightSecondaryPostGesture, - - // Comment Gestures - enableCommentGestures: enableCommentGestures, - leftPrimaryCommentGesture: leftPrimaryCommentGesture, - leftSecondaryCommentGesture: leftSecondaryCommentGesture, - rightPrimaryCommentGesture: rightPrimaryCommentGesture, - rightSecondaryCommentGesture: rightSecondaryCommentGesture, - - enableFullScreenSwipeNavigationGesture: enableFullScreenSwipeNavigationGesture, - - // Image Peek Settings - imagePeekDuration: imagePeekDuration, - - /// -------------------------- FAB Related Settings -------------------------- - enablePostsFab: enablePostsFab, - enableFeedsFab: enableFeedsFab, - - enableBackToTop: enableBackToTop, - enableSubscriptions: enableSubscriptions, - enableRefresh: enableRefresh, - enableDismissRead: enableDismissRead, - enableChangeSort: enableChangeSort, - enableNewPost: enableNewPost, - - postFabEnableBackToTop: postFabEnableBackToTop, - postFabEnableChangeSort: postFabEnableChangeSort, - postFabEnableReplyToPost: postFabEnableReplyToPost, - postFabEnableRefresh: postFabEnableRefresh, - postFabEnableSearch: postFabEnableSearch, - - feedFabSinglePressAction: feedFabSinglePressAction, - feedFabLongPressAction: feedFabLongPressAction, - postFabSinglePressAction: postFabSinglePressAction, - postFabLongPressAction: postFabLongPressAction, - - enableCommentNavigation: enableCommentNavigation, - combineNavAndFab: combineNavAndFab, - - /// -------------------------- Accessibility Related Settings -------------------------- - reduceAnimations: reduceAnimations, - - /// ------------------ VIDEO PLAYER SETTINGS ----------------------------------------- - videoAutoMute: videoAutoMute, - videoAutoFullscreen: videoAutoFullscreen, - videoAutoLoop: videoAutoLoop, - videoAutoPlay: videoAutoPlay, - videoDefaultPlaybackSpeed: videoDefaultPlaybackSpeed, - videoPlayerMode: videoPlayerMode, - currentAnonymousInstance: currentAnonymousInstance, )); } catch (e) { - return emit(state.copyWith(status: ThunderStatus.failure, feedCardDividerColor: state.feedCardDividerColor, errorMessage: e.toString())); + return emit(state.copyWith(status: ThunderStatus.failure, errorMessage: e.toString())); } } - void _onFabToggle(OnFabToggle event, Emitter emit) { - emit(state.copyWith(isFabOpen: !state.isFabOpen, feedCardDividerColor: state.feedCardDividerColor)); - } - - void _onFabSummonToggle(OnFabSummonToggle event, Emitter emit) { - emit(state.copyWith(isFabSummoned: !state.isFabSummoned, feedCardDividerColor: state.feedCardDividerColor)); - } - void _onSetCurrentAnonymousInstance(OnSetCurrentAnonymousInstance event, Emitter emit) async { if (event.instance != null) { UserPreferences.setSetting(LocalSettings.currentAnonymousInstance, event.instance!); @@ -465,10 +104,6 @@ class ThunderBloc extends Bloc { UserPreferences.removeSetting(LocalSettings.currentAnonymousInstance); } - emit(state.copyWith(currentAnonymousInstance: event.instance, feedCardDividerColor: state.feedCardDividerColor)); - } - - void _onBottomNavBarVisibilityChange(OnBottomNavBarVisibilityChange event, Emitter emit) { - emit(state.copyWith(isBottomNavBarVisible: event.isVisible, feedCardDividerColor: state.feedCardDividerColor)); + emit(state.copyWith(currentAnonymousInstance: event.instance)); } } diff --git a/lib/src/app/bloc/thunder_event.dart b/lib/src/app/bloc/thunder_event.dart index 8f235be66..cf7805596 100644 --- a/lib/src/app/bloc/thunder_event.dart +++ b/lib/src/app/bloc/thunder_event.dart @@ -18,24 +18,7 @@ class OnDismissEvent extends ThunderEvent { const OnDismissEvent(this.isBeingDismissed); } -class OnFabToggle extends ThunderEvent { - final bool isFabOpen; - const OnFabToggle(this.isFabOpen); -} - -class OnFabSummonToggle extends ThunderEvent { - final bool isFabSummoned; - const OnFabSummonToggle(this.isFabSummoned); -} - class OnSetCurrentAnonymousInstance extends ThunderEvent { final String? instance; const OnSetCurrentAnonymousInstance(this.instance); } - -class OnBottomNavBarVisibilityChange extends ThunderEvent { - /// Whether the bottom navigation bar is visible - final bool isVisible; - - const OnBottomNavBarVisibilityChange(this.isVisible); -} diff --git a/lib/src/app/bloc/thunder_state.dart b/lib/src/app/bloc/thunder_state.dart index 727404b99..ac633aa04 100644 --- a/lib/src/app/bloc/thunder_state.dart +++ b/lib/src/app/bloc/thunder_state.dart @@ -10,729 +10,84 @@ class ThunderState extends Equatable { this.version, this.errorMessage, - /// -------------------------- Feed Related Settings -------------------------- - // Default Listing/Sort Settings - this.defaultFeedListType = DEFAULT_LISTING_TYPE, - this.defaultPostSortType = DEFAULT_POST_SORT_TYPE, - this.useProfilePictureForDrawer = false, - - // NSFW Settings - this.hideNsfwPosts = false, - this.hideNsfwPreviews = true, - // Tablet Settings this.tabletMode = false, // General Settings this.browserMode = BrowserMode.customTabs, this.openInReaderMode = false, - this.useDisplayNamesForUsers = false, - this.useDisplayNamesForCommunities = false, - this.markPostReadOnMediaView = false, - this.markPostReadOnScroll = false, - this.disableFeedFab = false, + this.useProfilePictureForDrawer = false, this.showInAppUpdateNotification = false, this.showUpdateChangelogs = true, this.inboxNotificationType = NotificationType.none, this.scoreCounters = false, - this.userSeparator = FullNameSeparator.at, - this.userFullNameUserNameThickness = NameThickness.normal, - this.userFullNameUserNameColor = const NameColor.fromString(color: NameColor.defaultColor), - this.userFullNameInstanceNameThickness = NameThickness.light, - this.userFullNameInstanceNameColor = const NameColor.fromString(color: NameColor.defaultColor), - this.communitySeparator = FullNameSeparator.dot, - this.communityFullNameCommunityNameThickness = NameThickness.normal, - this.communityFullNameCommunityNameColor = const NameColor.fromString(color: NameColor.defaultColor), - this.communityFullNameInstanceNameThickness = NameThickness.light, - this.communityFullNameInstanceNameColor = const NameColor.fromString(color: NameColor.defaultColor), this.imageCachingMode = ImageCachingMode.relaxed, this.showNavigationLabels = true, this.hideTopBarOnScroll = false, this.hideBottomBarOnScroll = false, - this.showHiddenPosts = false, - this.showExpandedTaglines = false, - - /// -------------------------- Feed Post Related Settings -------------------------- - // Compact Related Settings - this.useCompactView = false, - this.showTitleFirst = false, - this.hideThumbnails = false, - this.showThumbnailPreviewOnRight = false, - this.showTextPostIndicator = false, - this.tappableAuthorCommunity = false, - - // General Settings - this.showVoteActions = true, - this.showSaveAction = true, - this.showCommunityIcons = false, - this.showFullHeightImages = true, - this.showEdgeToEdgeImages = false, - this.showTextContent = false, - this.showPostAuthor = false, - this.postShowUserInstance = false, - this.dimReadPosts = true, - this.showFullPostDate = false, - this.dateFormat, - this.feedCardDividerThickness = FeedCardDividerThickness.compact, - this.feedCardDividerColor, - this.compactPostCardMetadataItems = const [], - this.cardPostCardMetadataItems = const [], - this.keywordFilters = const [], this.appLanguageCode = 'en', - - // Post body settings - this.showCrossPosts = true, - this.postBodyViewType = PostBodyViewType.expanded, - this.postBodyShowUserInstance = false, - this.postBodyShowCommunityInstance = false, - this.postBodyShowCommunityAvatar = false, - - /// -------------------------- Post Page Related Settings -------------------------- - this.disablePostFabs = false, - - // Comment Related Settings - this.defaultCommentSortType = DEFAULT_COMMENT_SORT_TYPE, - this.collapseParentCommentOnGesture = true, - this.showCommentButtonActions = false, - this.commentShowUserInstance = false, - this.commentShowUserAvatar = false, - this.combineCommentScores = false, - this.nestedCommentIndicatorStyle = NestedCommentIndicatorStyle.thick, - this.nestedCommentIndicatorColor = NestedCommentIndicatorColor.colorful, - - // -------------------- - this.bottomNavBarSwipeGestures = true, - this.bottomNavBarDoubleTapGestures = false, - - // Post Gestures - this.enablePostGestures = true, - this.leftPrimaryPostGesture = SwipeAction.upvote, - this.leftSecondaryPostGesture = SwipeAction.downvote, - this.rightPrimaryPostGesture = SwipeAction.reply, - this.rightSecondaryPostGesture = SwipeAction.save, - - // Comment Gestures - this.enableCommentGestures = true, - this.leftPrimaryCommentGesture = SwipeAction.upvote, - this.leftSecondaryCommentGesture = SwipeAction.downvote, - this.rightPrimaryCommentGesture = SwipeAction.reply, - this.rightSecondaryCommentGesture = SwipeAction.save, - this.enableFullScreenSwipeNavigationGesture = true, - - // Image Peek Settings - this.imagePeekDuration = 300, - - // Theme Settings - this.themeType = ThemeType.system, - this.selectedTheme = CustomThemeType.deepBlue, - this.useMaterialYouTheme = false, - - // Color Settings - this.upvoteColor = const ActionColor.fromString(colorRaw: ActionColor.orange), - this.downvoteColor = const ActionColor.fromString(colorRaw: ActionColor.blue), - this.saveColor = const ActionColor.fromString(colorRaw: ActionColor.purple), - this.markReadColor = const ActionColor.fromString(colorRaw: ActionColor.teal), - this.replyColor = const ActionColor.fromString(colorRaw: ActionColor.green), - this.hideColor = const ActionColor.fromString(colorRaw: ActionColor.red), - - // Font Scale - this.titleFontSizeScale = FontScale.base, - this.contentFontSizeScale = FontScale.base, - this.commentFontSizeScale = FontScale.base, - this.metadataFontSizeScale = FontScale.base, - - /// -------------------------- FAB Related Settings -------------------------- - this.enableFeedsFab = true, - this.enablePostsFab = true, - this.enableBackToTop = true, - this.enableSubscriptions = true, - this.enableRefresh = true, - this.enableDismissRead = true, - this.enableChangeSort = true, - this.enableNewPost = true, - this.postFabEnableBackToTop = true, - this.postFabEnableChangeSort = true, - this.postFabEnableReplyToPost = true, - this.postFabEnableRefresh = true, - this.postFabEnableSearch = true, - this.feedFabSinglePressAction = FeedFabAction.newPost, - this.feedFabLongPressAction = FeedFabAction.openFab, - this.postFabSinglePressAction = PostFabAction.replyToPost, - this.postFabLongPressAction = PostFabAction.openFab, - this.enableCommentNavigation = true, - this.combineNavAndFab = true, - - /// ------------------ Video Player ------------------------ - this.videoAutoFullscreen = false, - this.videoAutoLoop = false, - this.videoAutoMute = true, - this.videoAutoPlay = VideoAutoPlay.never, - this.videoDefaultPlaybackSpeed = VideoPlayBackSpeed.normal, - this.videoPlayerMode = VideoPlayerMode.inApp, - - /// -------------------------- Accessibility Related Settings -------------------------- - this.reduceAnimations = false, this.currentAnonymousInstance = DEFAULT_INSTANCE, - - /// --------------------------------- UI Events --------------------------------- - // Expand/Close FAB event - this.isFabOpen = false, - // Summon/Unsummon FAB event - this.isFabSummoned = true, - // Bottom navigation bar visibility (for scroll-based hiding) - this.isBottomNavBarVisible = true, }); final ThunderStatus status; final Version? version; final String? errorMessage; - /// -------------------------- Feed Related Settings -------------------------- - // Default Listing/Sort Settings - final FeedListType defaultFeedListType; - final PostSortType defaultPostSortType; - PostSortType get postSortTypeForInstance => defaultPostSortType; - final bool useProfilePictureForDrawer; - - // NSFW Settings - final bool hideNsfwPosts; - final bool hideNsfwPreviews; - // Tablet Settings final bool tabletMode; // General Settings final BrowserMode browserMode; final bool openInReaderMode; - final bool useDisplayNamesForUsers; - final bool useDisplayNamesForCommunities; - final bool markPostReadOnMediaView; - final bool markPostReadOnScroll; - final bool disableFeedFab; + final bool useProfilePictureForDrawer; final bool showInAppUpdateNotification; final bool showUpdateChangelogs; final NotificationType inboxNotificationType; - final String? appLanguageCode; - final FullNameSeparator userSeparator; - final NameThickness userFullNameUserNameThickness; - final NameColor userFullNameUserNameColor; - final NameThickness userFullNameInstanceNameThickness; - final NameColor userFullNameInstanceNameColor; - final FullNameSeparator communitySeparator; - final NameThickness communityFullNameCommunityNameThickness; - final NameColor communityFullNameCommunityNameColor; - final NameThickness communityFullNameInstanceNameThickness; - final NameColor communityFullNameInstanceNameColor; + final bool scoreCounters; final ImageCachingMode imageCachingMode; final bool showNavigationLabels; final bool hideTopBarOnScroll; final bool hideBottomBarOnScroll; - final bool showHiddenPosts; - final bool showExpandedTaglines; - - /// -------------------------- Feed Post Related Settings -------------------------- - /// Compact Related Settings - final bool useCompactView; - final bool showTitleFirst; - final bool hideThumbnails; - final bool showThumbnailPreviewOnRight; - final bool showTextPostIndicator; - final bool tappableAuthorCommunity; - - // General Settings - final bool showVoteActions; - final bool showSaveAction; - final bool showCommunityIcons; - final bool showFullHeightImages; - final bool showEdgeToEdgeImages; - final bool showTextContent; - final bool showPostAuthor; - final bool postShowUserInstance; - final bool scoreCounters; - final bool dimReadPosts; - final bool showFullPostDate; - final DateFormat? dateFormat; - final FeedCardDividerThickness feedCardDividerThickness; - final Color? feedCardDividerColor; - final List compactPostCardMetadataItems; - final List cardPostCardMetadataItems; - final List keywordFilters; - - // Post body settings - final bool showCrossPosts; - final PostBodyViewType postBodyViewType; - final bool postBodyShowUserInstance; - final bool postBodyShowCommunityInstance; - final bool postBodyShowCommunityAvatar; - - /// -------------------------- Post Page Related Settings -------------------------- - final bool disablePostFabs; - - // Comment Related Settings - final CommentSortType defaultCommentSortType; - final bool collapseParentCommentOnGesture; - final bool showCommentButtonActions; - final bool commentShowUserInstance; - final bool commentShowUserAvatar; - final bool combineCommentScores; - final NestedCommentIndicatorStyle nestedCommentIndicatorStyle; - final NestedCommentIndicatorColor nestedCommentIndicatorColor; - - /// -------------------------- Theme Related Settings -------------------------- - // Theme Settings - final ThemeType themeType; - final CustomThemeType selectedTheme; - final bool useMaterialYouTheme; - - // Color Settings - final ActionColor upvoteColor; - final ActionColor downvoteColor; - final ActionColor saveColor; - final ActionColor markReadColor; - final ActionColor replyColor; - final ActionColor hideColor; - - // Font Scale - final FontScale titleFontSizeScale; - final FontScale contentFontSizeScale; - final FontScale commentFontSizeScale; - final FontScale metadataFontSizeScale; - - /// -------------------------- Gesture Related Settings -------------------------- - // Sidebar Gesture Settings - final bool bottomNavBarSwipeGestures; - final bool bottomNavBarDoubleTapGestures; - - // Post Gesture Settings - final bool enablePostGestures; - final SwipeAction leftPrimaryPostGesture; - final SwipeAction leftSecondaryPostGesture; - final SwipeAction rightPrimaryPostGesture; - final SwipeAction rightSecondaryPostGesture; - - // Comment Gesture Settings - final bool enableCommentGestures; - final SwipeAction leftPrimaryCommentGesture; - final SwipeAction leftSecondaryCommentGesture; - final SwipeAction rightPrimaryCommentGesture; - final SwipeAction rightSecondaryCommentGesture; - - final bool enableFullScreenSwipeNavigationGesture; - - // Image Peek Settings - /// Duration in milliseconds before image peek is triggered (default: 300ms) - final int imagePeekDuration; - - /// -------------------------- FAB Related Settings -------------------------- - final bool enableFeedsFab; - final bool enablePostsFab; - - final bool enableBackToTop; - final bool enableSubscriptions; - final bool enableRefresh; - final bool enableDismissRead; - final bool enableChangeSort; - final bool enableNewPost; - - final bool postFabEnableBackToTop; - final bool postFabEnableChangeSort; - final bool postFabEnableReplyToPost; - final bool postFabEnableRefresh; - final bool postFabEnableSearch; - - final FeedFabAction feedFabSinglePressAction; - final FeedFabAction feedFabLongPressAction; - final PostFabAction postFabSinglePressAction; - final PostFabAction postFabLongPressAction; - - final bool enableCommentNavigation; - final bool combineNavAndFab; - - /// -------------------------- Accessibility Related Settings -------------------------- - final bool reduceAnimations; - + final String? appLanguageCode; final String? currentAnonymousInstance; - /// ------------------ Video Player ------------------------ - final bool videoAutoFullscreen; - final bool videoAutoLoop; - final bool videoAutoMute; - final VideoAutoPlay videoAutoPlay; - final VideoPlayBackSpeed videoDefaultPlaybackSpeed; - final VideoPlayerMode videoPlayerMode; - - /// --------------------------------- UI Events --------------------------------- - // Expand/Close FAB event - final bool isFabOpen; - - // Expand/Close FAB event - final bool isFabSummoned; - - // Bottom navigation bar visibility (for scroll-based hiding) - final bool isBottomNavBarVisible; - ThunderState copyWith({ ThunderStatus? status, Version? version, String? errorMessage, - - /// -------------------------- Feed Related Settings -------------------------- - // Default Listing/Sort Settings - FeedListType? defaultFeedListType, - PostSortType? defaultPostSortType, - bool? useProfilePictureForDrawer, - - // NSFW Settings - bool? hideNsfwPosts, - bool? hideNsfwPreviews, - - // Tablet Settings bool? tabletMode, - - // General Settings BrowserMode? browserMode, bool? openInReaderMode, - bool? useDisplayNamesForUsers, - bool? useDisplayNamesForCommunities, - bool? markPostReadOnMediaView, - bool? markPostReadOnScroll, + bool? useProfilePictureForDrawer, bool? showInAppUpdateNotification, bool? showUpdateChangelogs, NotificationType? inboxNotificationType, bool? scoreCounters, - FullNameSeparator? userSeparator, - NameThickness? userFullNameUserNameThickness, - NameColor? userFullNameUserNameColor, - NameThickness? userFullNameInstanceNameThickness, - NameColor? userFullNameInstanceNameColor, - FullNameSeparator? communitySeparator, - NameThickness? communityFullNameCommunityNameThickness, - NameColor? communityFullNameCommunityNameColor, - NameThickness? communityFullNameInstanceNameThickness, - NameColor? communityFullNameInstanceNameColor, ImageCachingMode? imageCachingMode, bool? showNavigationLabels, bool? hideTopBarOnScroll, bool? hideBottomBarOnScroll, - bool? showHiddenPosts, - bool? showExpandedTaglines, - - /// -------------------------- Feed Post Related Settings -------------------------- - /// Compact Related Settings - bool? useCompactView, - bool? showTitleFirst, - bool? hideThumbnails, - bool? showThumbnailPreviewOnRight, - bool? showTextPostIndicator, - bool? tappableAuthorCommunity, - - // General Settings - bool? showVoteActions, - bool? showSaveAction, - bool? showCommunityIcons, - bool? showFullHeightImages, - bool? showEdgeToEdgeImages, - bool? showTextContent, - bool? showPostAuthor, - bool? postShowUserInstance, - bool? dimReadPosts, - bool? showFullPostDate, - DateFormat? dateFormat, - FeedCardDividerThickness? feedCardDividerThickness, - Color? feedCardDividerColor, - List? compactPostCardMetadataItems, - List? cardPostCardMetadataItems, - String? appLanguageCode = 'en', - - // Post body settings - bool? showCrossPosts, - PostBodyViewType? postBodyViewType, - bool? postBodyShowUserInstance, - bool? postBodyShowCommunityInstance, - bool? postBodyShowCommunityAvatar, - - // Keyword filters - List? keywordFilters, - - /// -------------------------- Post Page Related Settings -------------------------- - // Comment Related Settings - CommentSortType? defaultCommentSortType, - bool? collapseParentCommentOnGesture, - bool? showCommentButtonActions, - bool? commentShowUserInstance, - bool? commentShowUserAvatar, - bool? combineCommentScores, - NestedCommentIndicatorStyle? nestedCommentIndicatorStyle, - NestedCommentIndicatorColor? nestedCommentIndicatorColor, - - /// -------------------------- Theme Related Settings -------------------------- - // Theme Settings - ThemeType? themeType, - CustomThemeType? selectedTheme, - bool? useMaterialYouTheme, - - // Color Settings - ActionColor? upvoteColor, - ActionColor? downvoteColor, - ActionColor? saveColor, - ActionColor? markReadColor, - ActionColor? replyColor, - ActionColor? hideColor, - - // Font Scale - FontScale? titleFontSizeScale, - FontScale? contentFontSizeScale, - FontScale? commentFontSizeScale, - FontScale? metadataFontSizeScale, - - /// -------------------------- Gesture Related Settings -------------------------- - // Sidebar Gesture Settings - bool? bottomNavBarSwipeGestures, - bool? bottomNavBarDoubleTapGestures, - - // Post Gesture Settings - bool? enablePostGestures, - SwipeAction? leftPrimaryPostGesture, - SwipeAction? leftSecondaryPostGesture, - SwipeAction? rightPrimaryPostGesture, - SwipeAction? rightSecondaryPostGesture, - - // Comment Gesture Settings - bool? enableCommentGestures, - SwipeAction? leftPrimaryCommentGesture, - SwipeAction? leftSecondaryCommentGesture, - SwipeAction? rightPrimaryCommentGesture, - SwipeAction? rightSecondaryCommentGesture, - bool? enableFullScreenSwipeNavigationGesture, - - // Image Peek Settings - int? imagePeekDuration, - - /// -------------------------- FAB Related Settings -------------------------- - bool? enableFeedsFab, - bool? enablePostsFab, - bool? enableBackToTop, - bool? enableSubscriptions, - bool? enableRefresh, - bool? enableDismissRead, - bool? enableChangeSort, - bool? enableNewPost, - bool? postFabEnableBackToTop, - bool? postFabEnableChangeSort, - bool? postFabEnableReplyToPost, - bool? postFabEnableRefresh, - bool? postFabEnableSearch, - FeedFabAction? feedFabSinglePressAction, - FeedFabAction? feedFabLongPressAction, - PostFabAction? postFabSinglePressAction, - PostFabAction? postFabLongPressAction, - bool? enableCommentNavigation, - bool? combineNavAndFab, - - /// -------------------------- Accessibility Related Settings -------------------------- - bool? reduceAnimations, + String? appLanguageCode, String? currentAnonymousInstance, - - /// ------------------ Video Player ------------------------ - bool? videoAutoFullscreen, - bool? videoAutoLoop, - bool? videoAutoMute, - VideoAutoPlay? videoAutoPlay, - VideoPlayBackSpeed? videoDefaultPlaybackSpeed, - VideoPlayerMode? videoPlayerMode, - - /// --------------------------------- UI Events --------------------------------- - // Expand/Close FAB event - bool? isFabOpen, - // Summon/Unsummon FAB event - bool? isFabSummoned, - // Bottom navigation bar visibility (for scroll-based hiding) - bool? isBottomNavBarVisible, }) { return ThunderState( status: status ?? this.status, version: version ?? this.version, errorMessage: errorMessage, - - /// -------------------------- Feed Related Settings -------------------------- - /// Default Listing/Sort Settings - defaultFeedListType: defaultFeedListType ?? this.defaultFeedListType, - defaultPostSortType: defaultPostSortType ?? this.defaultPostSortType, - useProfilePictureForDrawer: useProfilePictureForDrawer ?? this.useProfilePictureForDrawer, - - // NSFW Settings - hideNsfwPosts: hideNsfwPosts ?? this.hideNsfwPosts, - hideNsfwPreviews: hideNsfwPreviews ?? this.hideNsfwPreviews, - - // Tablet Settings tabletMode: tabletMode ?? this.tabletMode, - - // General Settings browserMode: browserMode ?? this.browserMode, openInReaderMode: openInReaderMode ?? this.openInReaderMode, - useDisplayNamesForUsers: useDisplayNamesForUsers ?? this.useDisplayNamesForUsers, - useDisplayNamesForCommunities: useDisplayNamesForCommunities ?? this.useDisplayNamesForCommunities, - markPostReadOnMediaView: markPostReadOnMediaView ?? this.markPostReadOnMediaView, - markPostReadOnScroll: markPostReadOnScroll ?? this.markPostReadOnScroll, - disableFeedFab: disableFeedFab, + useProfilePictureForDrawer: useProfilePictureForDrawer ?? this.useProfilePictureForDrawer, showInAppUpdateNotification: showInAppUpdateNotification ?? this.showInAppUpdateNotification, showUpdateChangelogs: showUpdateChangelogs ?? this.showUpdateChangelogs, inboxNotificationType: inboxNotificationType ?? this.inboxNotificationType, scoreCounters: scoreCounters ?? this.scoreCounters, - appLanguageCode: appLanguageCode ?? this.appLanguageCode, - userSeparator: userSeparator ?? this.userSeparator, - userFullNameUserNameThickness: userFullNameUserNameThickness ?? this.userFullNameUserNameThickness, - userFullNameUserNameColor: userFullNameUserNameColor ?? this.userFullNameUserNameColor, - userFullNameInstanceNameThickness: userFullNameInstanceNameThickness ?? this.userFullNameInstanceNameThickness, - userFullNameInstanceNameColor: userFullNameInstanceNameColor ?? this.userFullNameInstanceNameColor, - communitySeparator: communitySeparator ?? this.communitySeparator, - communityFullNameCommunityNameThickness: communityFullNameCommunityNameThickness ?? this.communityFullNameCommunityNameThickness, - communityFullNameCommunityNameColor: communityFullNameCommunityNameColor ?? this.communityFullNameCommunityNameColor, - communityFullNameInstanceNameThickness: communityFullNameInstanceNameThickness ?? this.communityFullNameInstanceNameThickness, - communityFullNameInstanceNameColor: communityFullNameInstanceNameColor ?? this.communityFullNameInstanceNameColor, imageCachingMode: imageCachingMode ?? this.imageCachingMode, showNavigationLabels: showNavigationLabels ?? this.showNavigationLabels, hideTopBarOnScroll: hideTopBarOnScroll ?? this.hideTopBarOnScroll, hideBottomBarOnScroll: hideBottomBarOnScroll ?? this.hideBottomBarOnScroll, - showHiddenPosts: showHiddenPosts ?? this.showHiddenPosts, - showExpandedTaglines: showExpandedTaglines ?? this.showExpandedTaglines, - - /// -------------------------- Feed Post Related Settings -------------------------- - // Compact Related Settings - useCompactView: useCompactView ?? this.useCompactView, - showTitleFirst: showTitleFirst ?? this.showTitleFirst, - hideThumbnails: hideThumbnails ?? this.hideThumbnails, - showThumbnailPreviewOnRight: showThumbnailPreviewOnRight ?? this.showThumbnailPreviewOnRight, - showTextPostIndicator: showTextPostIndicator ?? this.showTextPostIndicator, - tappableAuthorCommunity: tappableAuthorCommunity ?? this.tappableAuthorCommunity, - - // General Settings - showVoteActions: showVoteActions ?? this.showVoteActions, - showSaveAction: showSaveAction ?? this.showSaveAction, - showCommunityIcons: showCommunityIcons ?? this.showCommunityIcons, - showFullHeightImages: showFullHeightImages ?? this.showFullHeightImages, - showEdgeToEdgeImages: showEdgeToEdgeImages ?? this.showEdgeToEdgeImages, - showTextContent: showTextContent ?? this.showTextContent, - showPostAuthor: showPostAuthor ?? this.showPostAuthor, - postShowUserInstance: postShowUserInstance ?? this.postShowUserInstance, - dimReadPosts: dimReadPosts ?? this.dimReadPosts, - showFullPostDate: showFullPostDate ?? this.showFullPostDate, - dateFormat: dateFormat ?? this.dateFormat, - feedCardDividerThickness: feedCardDividerThickness ?? this.feedCardDividerThickness, - feedCardDividerColor: feedCardDividerColor, - compactPostCardMetadataItems: compactPostCardMetadataItems ?? this.compactPostCardMetadataItems, - cardPostCardMetadataItems: cardPostCardMetadataItems ?? this.cardPostCardMetadataItems, - - // Post body settings - showCrossPosts: showCrossPosts ?? this.showCrossPosts, - postBodyViewType: postBodyViewType ?? this.postBodyViewType, - postBodyShowUserInstance: postBodyShowUserInstance ?? this.postBodyShowUserInstance, - postBodyShowCommunityInstance: postBodyShowCommunityInstance ?? this.postBodyShowCommunityInstance, - postBodyShowCommunityAvatar: postBodyShowCommunityAvatar ?? this.postBodyShowCommunityAvatar, - - keywordFilters: keywordFilters ?? this.keywordFilters, - - /// -------------------------- Post Page Related Settings -------------------------- - disablePostFabs: disablePostFabs, - - // Comment Related Settings - defaultCommentSortType: defaultCommentSortType ?? this.defaultCommentSortType, - collapseParentCommentOnGesture: collapseParentCommentOnGesture ?? this.collapseParentCommentOnGesture, - showCommentButtonActions: showCommentButtonActions ?? this.showCommentButtonActions, - commentShowUserInstance: commentShowUserInstance ?? this.commentShowUserInstance, - commentShowUserAvatar: commentShowUserAvatar ?? this.commentShowUserAvatar, - combineCommentScores: combineCommentScores ?? this.combineCommentScores, - nestedCommentIndicatorStyle: nestedCommentIndicatorStyle ?? this.nestedCommentIndicatorStyle, - nestedCommentIndicatorColor: nestedCommentIndicatorColor ?? this.nestedCommentIndicatorColor, - - /// -------------------------- Theme Related Settings -------------------------- - // Theme Settings - themeType: themeType ?? this.themeType, - selectedTheme: selectedTheme ?? this.selectedTheme, - useMaterialYouTheme: useMaterialYouTheme ?? this.useMaterialYouTheme, - - // Color Settings - upvoteColor: upvoteColor ?? this.upvoteColor, - downvoteColor: downvoteColor ?? this.downvoteColor, - saveColor: saveColor ?? this.saveColor, - markReadColor: markReadColor ?? this.markReadColor, - replyColor: replyColor ?? this.replyColor, - hideColor: hideColor ?? this.hideColor, - - // Font Scale - titleFontSizeScale: titleFontSizeScale ?? this.titleFontSizeScale, - contentFontSizeScale: contentFontSizeScale ?? this.contentFontSizeScale, - commentFontSizeScale: commentFontSizeScale ?? this.commentFontSizeScale, - metadataFontSizeScale: metadataFontSizeScale ?? this.metadataFontSizeScale, - - /// -------------------------- Gesture Related Settings -------------------------- - // Sidebar Gesture Settings - bottomNavBarSwipeGestures: bottomNavBarSwipeGestures ?? this.bottomNavBarSwipeGestures, - bottomNavBarDoubleTapGestures: bottomNavBarDoubleTapGestures ?? this.bottomNavBarDoubleTapGestures, - - // Post Gestures - enablePostGestures: enablePostGestures ?? this.enablePostGestures, - leftPrimaryPostGesture: leftPrimaryPostGesture ?? this.leftPrimaryPostGesture, - leftSecondaryPostGesture: leftSecondaryPostGesture ?? this.leftSecondaryPostGesture, - rightPrimaryPostGesture: rightPrimaryPostGesture ?? this.rightPrimaryPostGesture, - rightSecondaryPostGesture: rightSecondaryPostGesture ?? this.rightSecondaryPostGesture, - - enableFullScreenSwipeNavigationGesture: enableFullScreenSwipeNavigationGesture ?? this.enableFullScreenSwipeNavigationGesture, - - // Image Peek Settings - imagePeekDuration: imagePeekDuration ?? this.imagePeekDuration, - - // Comment Gestures - enableCommentGestures: enableCommentGestures ?? this.enableCommentGestures, - leftPrimaryCommentGesture: leftPrimaryCommentGesture ?? this.leftPrimaryCommentGesture, - leftSecondaryCommentGesture: leftSecondaryCommentGesture ?? this.leftSecondaryCommentGesture, - rightPrimaryCommentGesture: rightPrimaryCommentGesture ?? this.rightPrimaryCommentGesture, - rightSecondaryCommentGesture: rightSecondaryCommentGesture ?? this.rightSecondaryCommentGesture, - - /// -------------------------- FAB Related Settings -------------------------- - enableFeedsFab: enableFeedsFab ?? this.enableFeedsFab, - enablePostsFab: enablePostsFab ?? this.enablePostsFab, - - enableBackToTop: enableBackToTop ?? this.enableBackToTop, - enableSubscriptions: enableSubscriptions ?? this.enableSubscriptions, - enableRefresh: enableRefresh ?? this.enableRefresh, - enableDismissRead: enableDismissRead ?? this.enableDismissRead, - enableChangeSort: enableChangeSort ?? this.enableChangeSort, - enableNewPost: enableNewPost ?? this.enableNewPost, - postFabEnableBackToTop: postFabEnableBackToTop ?? this.postFabEnableBackToTop, - postFabEnableChangeSort: postFabEnableChangeSort ?? this.postFabEnableChangeSort, - postFabEnableReplyToPost: postFabEnableReplyToPost ?? this.postFabEnableReplyToPost, - postFabEnableRefresh: postFabEnableRefresh ?? this.postFabEnableRefresh, - postFabEnableSearch: postFabEnableSearch ?? this.postFabEnableSearch, - feedFabSinglePressAction: feedFabSinglePressAction ?? this.feedFabSinglePressAction, - feedFabLongPressAction: feedFabLongPressAction ?? this.feedFabLongPressAction, - postFabSinglePressAction: postFabSinglePressAction ?? this.postFabSinglePressAction, - postFabLongPressAction: postFabLongPressAction ?? this.postFabLongPressAction, - - enableCommentNavigation: enableCommentNavigation ?? this.enableCommentNavigation, - combineNavAndFab: combineNavAndFab ?? this.combineNavAndFab, - - /// -------------------------- Accessibility Related Settings -------------------------- - reduceAnimations: reduceAnimations ?? this.reduceAnimations, - - /// ------------------ Video Player ------------------------ - videoAutoFullscreen: videoAutoFullscreen ?? this.videoAutoFullscreen, - videoAutoLoop: videoAutoLoop ?? this.videoAutoLoop, - videoAutoMute: videoAutoMute ?? this.videoAutoMute, - videoAutoPlay: videoAutoPlay ?? this.videoAutoPlay, - videoDefaultPlaybackSpeed: videoDefaultPlaybackSpeed ?? this.videoDefaultPlaybackSpeed, - videoPlayerMode: videoPlayerMode ?? this.videoPlayerMode, + appLanguageCode: appLanguageCode ?? this.appLanguageCode, currentAnonymousInstance: currentAnonymousInstance, - - /// ------------------ Video Player ------------------------ - - /// --------------------------------- UI Events --------------------------------- - // Expand/Close FAB event - isFabOpen: isFabOpen ?? this.isFabOpen, - // Summon/Unsummon FAB event - isFabSummoned: isFabSummoned ?? this.isFabSummoned, - // Bottom navigation bar visibility (for scroll-based hiding) - isBottomNavBarVisible: isBottomNavBarVisible ?? this.isBottomNavBarVisible, ); } @@ -741,180 +96,19 @@ class ThunderState extends Equatable { status, version, errorMessage, - - /// -------------------------- Feed Related Settings -------------------------- - /// Default Listing/Sort Settings - defaultFeedListType, - defaultPostSortType, - useProfilePictureForDrawer, - - // NSFW Settings - hideNsfwPosts, - hideNsfwPreviews, - - // Tablet Settings tabletMode, - - // General Settings browserMode, - useDisplayNamesForUsers, - useDisplayNamesForCommunities, - markPostReadOnMediaView, - markPostReadOnScroll, - disableFeedFab, + openInReaderMode, + useProfilePictureForDrawer, showInAppUpdateNotification, showUpdateChangelogs, inboxNotificationType, - userSeparator, - userFullNameUserNameThickness, - userFullNameUserNameColor, - userFullNameInstanceNameThickness, - userFullNameInstanceNameColor, - communitySeparator, - communityFullNameCommunityNameThickness, - communityFullNameCommunityNameColor, - communityFullNameInstanceNameThickness, - communityFullNameInstanceNameColor, + scoreCounters, imageCachingMode, showNavigationLabels, hideTopBarOnScroll, hideBottomBarOnScroll, - showExpandedTaglines, - - /// -------------------------- Feed Post Related Settings -------------------------- - /// Compact Related Settings - useCompactView, - showTitleFirst, - hideThumbnails, - showThumbnailPreviewOnRight, - showTextPostIndicator, - tappableAuthorCommunity, - - // General Settings - showVoteActions, - showSaveAction, - showCommunityIcons, - showFullHeightImages, - showEdgeToEdgeImages, - showTextContent, - showPostAuthor, - postShowUserInstance, - dimReadPosts, - showFullPostDate, - dateFormat, - feedCardDividerThickness, - feedCardDividerColor, - compactPostCardMetadataItems, - cardPostCardMetadataItems, appLanguageCode, - - // Post body settings - showCrossPosts, - postBodyViewType, - postBodyShowUserInstance, - postBodyShowCommunityInstance, - postBodyShowCommunityAvatar, - - keywordFilters, - - /// -------------------------- Post Page Related Settings -------------------------- - disablePostFabs, - - // Comment Related Settings - defaultCommentSortType, - collapseParentCommentOnGesture, - showCommentButtonActions, - commentShowUserInstance, - commentShowUserAvatar, - combineCommentScores, - - nestedCommentIndicatorStyle, - nestedCommentIndicatorColor, - - /// -------------------------- Theme Related Settings -------------------------- - // Theme Settings - themeType, - selectedTheme, - useMaterialYouTheme, - - // Color Settings - upvoteColor, - downvoteColor, - saveColor, - markReadColor, - replyColor, - hideColor, - - // Font Scale - titleFontSizeScale, - contentFontSizeScale, - commentFontSizeScale, - metadataFontSizeScale, - - /// -------------------------- Gesture Related Settings -------------------------- - // Sidebar Gesture Settings - bottomNavBarSwipeGestures, - bottomNavBarDoubleTapGestures, - - // Post Gestures - enablePostGestures, - leftPrimaryPostGesture, - leftSecondaryPostGesture, - rightPrimaryPostGesture, - rightSecondaryPostGesture, - - // Comment Gestures - enableCommentGestures, - leftPrimaryCommentGesture, - leftSecondaryCommentGesture, - rightPrimaryCommentGesture, - rightSecondaryCommentGesture, - - enableFullScreenSwipeNavigationGesture, - - // Image Peek Settings - imagePeekDuration, - - /// -------------------------- FAB Related Settings -------------------------- - enableFeedsFab, - enablePostsFab, - - enableBackToTop, - enableSubscriptions, - enableRefresh, - enableDismissRead, - postFabEnableBackToTop, - postFabEnableChangeSort, - postFabEnableReplyToPost, - postFabEnableRefresh, - postFabEnableSearch, - feedFabSinglePressAction, - feedFabLongPressAction, - postFabSinglePressAction, - postFabLongPressAction, - - enableCommentNavigation, - combineNavAndFab, - - /// ------------------ Video Player ------------------------ - videoAutoFullscreen, - videoAutoLoop, - videoAutoMute, - videoAutoPlay, - videoDefaultPlaybackSpeed, - videoPlayerMode, - - /// -------------------------- Accessibility Related Settings -------------------------- - reduceAnimations, - currentAnonymousInstance, - - /// --------------------------------- UI Events --------------------------------- - // Expand/Close FAB event - isFabOpen, - // Expand/Close FAB event - isFabSummoned, - // Bottom navigation bar visibility - isBottomNavBarVisible, ]; } diff --git a/lib/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart b/lib/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart new file mode 100644 index 000000000..b84ecc1c7 --- /dev/null +++ b/lib/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart @@ -0,0 +1,49 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +import 'package:thunder/src/core/enums/local_settings.dart'; +import 'package:thunder/src/core/enums/comment_sort_type.dart'; +import 'package:thunder/src/core/enums/nested_comment_indicator.dart'; +import 'package:thunder/src/core/singletons/preferences.dart'; +import 'package:thunder/src/shared/utils/constants.dart'; + +part 'comment_preferences_state.dart'; + +/// Cubit for managing comment-related preferences +class CommentPreferencesCubit extends Cubit { + CommentPreferencesCubit() : super(const CommentPreferencesState()) { + load(); + } + + /// Loads comment preferences from UserPreferences + void load() { + final defaultCommentSortType = CommentSortType.values.byName(UserPreferences.getLocalSetting(LocalSettings.defaultCommentSortType) ?? DEFAULT_COMMENT_SORT_TYPE.name); + final collapseParentCommentOnGesture = UserPreferences.getLocalSetting(LocalSettings.collapseParentCommentBodyOnGesture) ?? true; + final showCommentButtonActions = UserPreferences.getLocalSetting(LocalSettings.showCommentActionButtons) ?? false; + final commentShowUserInstance = UserPreferences.getLocalSetting(LocalSettings.commentShowUserInstance) ?? false; + final commentShowUserAvatar = UserPreferences.getLocalSetting(LocalSettings.commentShowUserAvatar) ?? false; + final combineCommentScores = UserPreferences.getLocalSetting(LocalSettings.combineCommentScores) ?? false; + final nestedCommentIndicatorStyle = + NestedCommentIndicatorStyle.values.byName(UserPreferences.getLocalSetting(LocalSettings.nestedCommentIndicatorStyle) ?? DEFAULT_NESTED_COMMENT_INDICATOR_STYLE.name); + final nestedCommentIndicatorColor = + NestedCommentIndicatorColor.values.byName(UserPreferences.getLocalSetting(LocalSettings.nestedCommentIndicatorColor) ?? DEFAULT_NESTED_COMMENT_INDICATOR_COLOR.name); + + emit( + CommentPreferencesState( + defaultCommentSortType: defaultCommentSortType, + collapseParentCommentOnGesture: collapseParentCommentOnGesture, + showCommentButtonActions: showCommentButtonActions, + commentShowUserInstance: commentShowUserInstance, + commentShowUserAvatar: commentShowUserAvatar, + combineCommentScores: combineCommentScores, + nestedCommentIndicatorStyle: nestedCommentIndicatorStyle, + nestedCommentIndicatorColor: nestedCommentIndicatorColor, + ), + ); + } + + /// Reloads preferences from storage. This should be called when preferences are updated elsewhere + void reload() { + load(); + } +} diff --git a/lib/src/app/cubits/comment_preferences_cubit/comment_preferences_state.dart b/lib/src/app/cubits/comment_preferences_cubit/comment_preferences_state.dart new file mode 100644 index 000000000..ccc74856b --- /dev/null +++ b/lib/src/app/cubits/comment_preferences_cubit/comment_preferences_state.dart @@ -0,0 +1,72 @@ +part of 'comment_preferences_cubit.dart'; + +class CommentPreferencesState extends Equatable { + const CommentPreferencesState({ + this.defaultCommentSortType = DEFAULT_COMMENT_SORT_TYPE, + this.collapseParentCommentOnGesture = true, + this.showCommentButtonActions = false, + this.commentShowUserInstance = false, + this.commentShowUserAvatar = false, + this.combineCommentScores = false, + this.nestedCommentIndicatorStyle = DEFAULT_NESTED_COMMENT_INDICATOR_STYLE, + this.nestedCommentIndicatorColor = DEFAULT_NESTED_COMMENT_INDICATOR_COLOR, + }); + + /// The default comment sort type + final CommentSortType defaultCommentSortType; + + /// Whether the parent comment body should be collapsed when tapped + final bool collapseParentCommentOnGesture; + + /// Whether to show action buttons for comments + final bool showCommentButtonActions; + + /// Whether to show the user instance in the comment header + final bool commentShowUserInstance; + + /// Whether to show the user avatar in the comment header + final bool commentShowUserAvatar; + + /// Whether to show combined comment scores + final bool combineCommentScores; + + /// The comment depth indicator style + final NestedCommentIndicatorStyle nestedCommentIndicatorStyle; + + /// The comment depth indicator color + final NestedCommentIndicatorColor nestedCommentIndicatorColor; + + CommentPreferencesState copyWith({ + CommentSortType? defaultCommentSortType, + bool? collapseParentCommentOnGesture, + bool? showCommentButtonActions, + bool? commentShowUserInstance, + bool? commentShowUserAvatar, + bool? combineCommentScores, + NestedCommentIndicatorStyle? nestedCommentIndicatorStyle, + NestedCommentIndicatorColor? nestedCommentIndicatorColor, + }) { + return CommentPreferencesState( + defaultCommentSortType: defaultCommentSortType ?? this.defaultCommentSortType, + collapseParentCommentOnGesture: collapseParentCommentOnGesture ?? this.collapseParentCommentOnGesture, + showCommentButtonActions: showCommentButtonActions ?? this.showCommentButtonActions, + commentShowUserInstance: commentShowUserInstance ?? this.commentShowUserInstance, + commentShowUserAvatar: commentShowUserAvatar ?? this.commentShowUserAvatar, + combineCommentScores: combineCommentScores ?? this.combineCommentScores, + nestedCommentIndicatorStyle: nestedCommentIndicatorStyle ?? this.nestedCommentIndicatorStyle, + nestedCommentIndicatorColor: nestedCommentIndicatorColor ?? this.nestedCommentIndicatorColor, + ); + } + + @override + List get props => [ + defaultCommentSortType, + collapseParentCommentOnGesture, + showCommentButtonActions, + commentShowUserInstance, + commentShowUserAvatar, + combineCommentScores, + nestedCommentIndicatorStyle, + nestedCommentIndicatorColor, + ]; +} diff --git a/lib/src/app/cubits/fab_cubit/fab_cubit.dart b/lib/src/app/cubits/fab_cubit/fab_cubit.dart new file mode 100644 index 000000000..8f715589b --- /dev/null +++ b/lib/src/app/cubits/fab_cubit/fab_cubit.dart @@ -0,0 +1,49 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'fab_state.dart'; + +/// Cubit for managing FAB's state +class FabStateCubit extends Cubit { + FabStateCubit() : super(const FabStateState()); + + /// Toggles the feed FAB's open state + void toggleFeedFab() { + emit(state.copyWith(isFeedFabOpen: !state.isFeedFabOpen)); + } + + /// Sets the feed FAB's open state + void setFeedFabOpen(bool isOpen) { + emit(state.copyWith(isFeedFabOpen: isOpen)); + } + + /// Toggles the feed FAB's summoned/visible state + void toggleFeedFabSummoned() { + emit(state.copyWith(isFeedFabSummoned: !state.isFeedFabSummoned)); + } + + /// Sets the feed FAB's summoned/visible state + void setFeedFabSummoned(bool isSummoned) { + emit(state.copyWith(isFeedFabSummoned: isSummoned)); + } + + /// Toggles the post FAB's open state + void togglePostFab() { + emit(state.copyWith(isPostFabOpen: !state.isPostFabOpen)); + } + + /// Sets the post FAB's open state + void setPostFabOpen(bool isOpen) { + emit(state.copyWith(isPostFabOpen: isOpen)); + } + + /// Toggles the post FAB's summoned/visible state + void togglePostFabSummoned() { + emit(state.copyWith(isPostFabSummoned: !state.isPostFabSummoned)); + } + + /// Sets the post FAB's summoned/visible state + void setPostFabSummoned(bool isSummoned) { + emit(state.copyWith(isPostFabSummoned: isSummoned)); + } +} diff --git a/lib/src/app/cubits/fab_cubit/fab_state.dart b/lib/src/app/cubits/fab_cubit/fab_state.dart new file mode 100644 index 000000000..986495529 --- /dev/null +++ b/lib/src/app/cubits/fab_cubit/fab_state.dart @@ -0,0 +1,39 @@ +part of 'fab_cubit.dart'; + +class FabStateState extends Equatable { + const FabStateState({ + this.isFeedFabOpen = false, + this.isFeedFabSummoned = true, + this.isPostFabOpen = false, + this.isPostFabSummoned = true, + }); + + /// Whether the feed FAB is currently open + final bool isFeedFabOpen; + + /// Whether the feed FAB is currently summoned (visible on screen) + final bool isFeedFabSummoned; + + /// Whether the post FAB is currently open + final bool isPostFabOpen; + + /// Whether the post FAB is currently summoned (visible on screen) + final bool isPostFabSummoned; + + FabStateState copyWith({ + bool? isFeedFabOpen, + bool? isFeedFabSummoned, + bool? isPostFabOpen, + bool? isPostFabSummoned, + }) { + return FabStateState( + isFeedFabOpen: isFeedFabOpen ?? this.isFeedFabOpen, + isFeedFabSummoned: isFeedFabSummoned ?? this.isFeedFabSummoned, + isPostFabOpen: isPostFabOpen ?? this.isPostFabOpen, + isPostFabSummoned: isPostFabSummoned ?? this.isPostFabSummoned, + ); + } + + @override + List get props => [isFeedFabOpen, isFeedFabSummoned, isPostFabOpen, isPostFabSummoned]; +} diff --git a/lib/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart b/lib/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart new file mode 100644 index 000000000..4537224c0 --- /dev/null +++ b/lib/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart @@ -0,0 +1,71 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +import 'package:thunder/src/core/enums/local_settings.dart'; +import 'package:thunder/src/core/enums/fab_action.dart'; +import 'package:thunder/src/core/singletons/preferences.dart'; + +part 'fab_preferences_state.dart'; + +/// Cubit for managing floating action button (FAB) preferences +class FabPreferencesCubit extends Cubit { + FabPreferencesCubit() : super(const FabPreferencesState()) { + load(); + } + + /// Loads FAB preferences from UserPreferences + void load() { + final enableFeedsFab = UserPreferences.getLocalSetting(LocalSettings.enableFeedsFab) ?? true; + final enablePostsFab = UserPreferences.getLocalSetting(LocalSettings.enablePostsFab) ?? true; + + final enableBackToTop = UserPreferences.getLocalSetting(LocalSettings.enableBackToTop) ?? true; + final enableSubscriptions = UserPreferences.getLocalSetting(LocalSettings.enableSubscriptions) ?? true; + final enableRefresh = UserPreferences.getLocalSetting(LocalSettings.enableRefresh) ?? true; + final enableDismissRead = UserPreferences.getLocalSetting(LocalSettings.enableDismissRead) ?? true; + final enableChangeSort = UserPreferences.getLocalSetting(LocalSettings.enableChangeSort) ?? true; + final enableNewPost = UserPreferences.getLocalSetting(LocalSettings.enableNewPost) ?? true; + + final postFabEnableBackToTop = UserPreferences.getLocalSetting(LocalSettings.postFabEnableBackToTop) ?? true; + final postFabEnableChangeSort = UserPreferences.getLocalSetting(LocalSettings.postFabEnableChangeSort) ?? true; + final postFabEnableReplyToPost = UserPreferences.getLocalSetting(LocalSettings.postFabEnableReplyToPost) ?? true; + final postFabEnableRefresh = UserPreferences.getLocalSetting(LocalSettings.postFabEnableRefresh) ?? true; + final postFabEnableSearch = UserPreferences.getLocalSetting(LocalSettings.postFabEnableSearch) ?? true; + + final feedFabSinglePressAction = FeedFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.feedFabSinglePressAction) ?? FeedFabAction.newPost.name); + final feedFabLongPressAction = FeedFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.feedFabLongPressAction) ?? FeedFabAction.openFab.name); + final postFabSinglePressAction = PostFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postFabSinglePressAction) ?? PostFabAction.replyToPost.name); + final postFabLongPressAction = PostFabAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postFabLongPressAction) ?? PostFabAction.openFab.name); + + final enableCommentNavigation = UserPreferences.getLocalSetting(LocalSettings.enableCommentNavigation) ?? true; + final combineNavAndFab = UserPreferences.getLocalSetting(LocalSettings.combineNavAndFab) ?? true; + + emit( + FabPreferencesState( + enableFeedsFab: enableFeedsFab, + enablePostsFab: enablePostsFab, + enableBackToTop: enableBackToTop, + enableSubscriptions: enableSubscriptions, + enableRefresh: enableRefresh, + enableDismissRead: enableDismissRead, + enableChangeSort: enableChangeSort, + enableNewPost: enableNewPost, + postFabEnableBackToTop: postFabEnableBackToTop, + postFabEnableChangeSort: postFabEnableChangeSort, + postFabEnableReplyToPost: postFabEnableReplyToPost, + postFabEnableRefresh: postFabEnableRefresh, + postFabEnableSearch: postFabEnableSearch, + feedFabSinglePressAction: feedFabSinglePressAction, + feedFabLongPressAction: feedFabLongPressAction, + postFabSinglePressAction: postFabSinglePressAction, + postFabLongPressAction: postFabLongPressAction, + enableCommentNavigation: enableCommentNavigation, + combineNavAndFab: combineNavAndFab, + ), + ); + } + + /// Reloads preferences from storage. This should be called when preferences are updated elsewhere + void reload() { + load(); + } +} diff --git a/lib/src/app/cubits/fab_preferences_cubit/fab_preferences_state.dart b/lib/src/app/cubits/fab_preferences_cubit/fab_preferences_state.dart new file mode 100644 index 000000000..9e237677d --- /dev/null +++ b/lib/src/app/cubits/fab_preferences_cubit/fab_preferences_state.dart @@ -0,0 +1,149 @@ +part of 'fab_preferences_cubit.dart'; + +class FabPreferencesState extends Equatable { + const FabPreferencesState({ + this.enableFeedsFab = true, + this.enablePostsFab = true, + this.enableBackToTop = true, + this.enableSubscriptions = true, + this.enableRefresh = true, + this.enableDismissRead = true, + this.enableChangeSort = true, + this.enableNewPost = true, + this.postFabEnableBackToTop = true, + this.postFabEnableChangeSort = true, + this.postFabEnableReplyToPost = true, + this.postFabEnableRefresh = true, + this.postFabEnableSearch = true, + this.feedFabSinglePressAction = FeedFabAction.newPost, + this.feedFabLongPressAction = FeedFabAction.openFab, + this.postFabSinglePressAction = PostFabAction.replyToPost, + this.postFabLongPressAction = PostFabAction.openFab, + this.enableCommentNavigation = true, + this.combineNavAndFab = true, + }); + + /// Whether to enable the feed page FAB + final bool enableFeedsFab; + + /// Whether to enable the post page FAB + final bool enablePostsFab; + + /// Back to top action (feed page FAB) + final bool enableBackToTop; + + /// Open subscriptions drawer action (feed page FAB) + final bool enableSubscriptions; + + /// Refresh feed action (feed page FAB) + final bool enableRefresh; + + /// Dismiss read posts action (feed page FAB) + final bool enableDismissRead; + + /// Change sort type action (feed page FAB) + final bool enableChangeSort; + + /// New post action (feed page FAB) + final bool enableNewPost; + + /// Back to top action (post page FAB) + final bool postFabEnableBackToTop; + + /// Change sort type action (post page FAB) + final bool postFabEnableChangeSort; + + /// Reply to post action (post page FAB) + final bool postFabEnableReplyToPost; + + /// Refresh post/comment action (post page FAB) + final bool postFabEnableRefresh; + + /// Search comment action (post page FAB) + final bool postFabEnableSearch; + + /// The action to perform when the feed page FAB is tapped + final FeedFabAction feedFabSinglePressAction; + + /// The action to perform when the feed page FAB is long pressed + final FeedFabAction feedFabLongPressAction; + + /// The action to perform when the post page FAB is tapped + final PostFabAction postFabSinglePressAction; + + /// The action to perform when the post page FAB is long pressed + final PostFabAction postFabLongPressAction; + + /// Whether to enable comment navigation on the post page + final bool enableCommentNavigation; + + /// Whether to combine navigation and FAB on the post page + final bool combineNavAndFab; + + FabPreferencesState copyWith({ + bool? enableFeedsFab, + bool? enablePostsFab, + bool? enableBackToTop, + bool? enableSubscriptions, + bool? enableRefresh, + bool? enableDismissRead, + bool? enableChangeSort, + bool? enableNewPost, + bool? postFabEnableBackToTop, + bool? postFabEnableChangeSort, + bool? postFabEnableReplyToPost, + bool? postFabEnableRefresh, + bool? postFabEnableSearch, + FeedFabAction? feedFabSinglePressAction, + FeedFabAction? feedFabLongPressAction, + PostFabAction? postFabSinglePressAction, + PostFabAction? postFabLongPressAction, + bool? enableCommentNavigation, + bool? combineNavAndFab, + }) { + return FabPreferencesState( + enableFeedsFab: enableFeedsFab ?? this.enableFeedsFab, + enablePostsFab: enablePostsFab ?? this.enablePostsFab, + enableBackToTop: enableBackToTop ?? this.enableBackToTop, + enableSubscriptions: enableSubscriptions ?? this.enableSubscriptions, + enableRefresh: enableRefresh ?? this.enableRefresh, + enableDismissRead: enableDismissRead ?? this.enableDismissRead, + enableChangeSort: enableChangeSort ?? this.enableChangeSort, + enableNewPost: enableNewPost ?? this.enableNewPost, + postFabEnableBackToTop: postFabEnableBackToTop ?? this.postFabEnableBackToTop, + postFabEnableChangeSort: postFabEnableChangeSort ?? this.postFabEnableChangeSort, + postFabEnableReplyToPost: postFabEnableReplyToPost ?? this.postFabEnableReplyToPost, + postFabEnableRefresh: postFabEnableRefresh ?? this.postFabEnableRefresh, + postFabEnableSearch: postFabEnableSearch ?? this.postFabEnableSearch, + feedFabSinglePressAction: feedFabSinglePressAction ?? this.feedFabSinglePressAction, + feedFabLongPressAction: feedFabLongPressAction ?? this.feedFabLongPressAction, + postFabSinglePressAction: postFabSinglePressAction ?? this.postFabSinglePressAction, + postFabLongPressAction: postFabLongPressAction ?? this.postFabLongPressAction, + enableCommentNavigation: enableCommentNavigation ?? this.enableCommentNavigation, + combineNavAndFab: combineNavAndFab ?? this.combineNavAndFab, + ); + } + + @override + List get props => [ + enableFeedsFab, + enablePostsFab, + enableBackToTop, + enableSubscriptions, + enableRefresh, + enableDismissRead, + enableChangeSort, + enableNewPost, + postFabEnableBackToTop, + postFabEnableChangeSort, + postFabEnableReplyToPost, + postFabEnableRefresh, + postFabEnableSearch, + feedFabSinglePressAction, + feedFabLongPressAction, + postFabSinglePressAction, + postFabLongPressAction, + enableCommentNavigation, + combineNavAndFab, + ]; +} diff --git a/lib/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart b/lib/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart new file mode 100644 index 000000000..f287d1314 --- /dev/null +++ b/lib/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:intl/intl.dart'; + +import 'package:thunder/src/core/enums/enums.dart'; +import 'package:thunder/src/core/enums/local_settings.dart'; +import 'package:thunder/src/core/enums/post_sort_type.dart'; +import 'package:thunder/src/core/enums/feed_card_divider_thickness.dart'; +import 'package:thunder/src/core/enums/post_body_view_type.dart'; +import 'package:thunder/src/core/singletons/preferences.dart'; +import 'package:thunder/src/shared/utils/constants.dart'; +import 'package:thunder/src/features/post/post.dart'; + +part 'feed_preferences_state.dart'; + +/// Cubit for managing feed-related preferences. This includes settings for the feed list, post cards, and post body. +class FeedPreferencesCubit extends Cubit { + FeedPreferencesCubit() : super(const FeedPreferencesState()) { + load(); + } + + /// Loads feed preferences from UserPreferences + void load() { + // Default Listing/Sort Settings + FeedListType defaultFeedListType = DEFAULT_LISTING_TYPE; + PostSortType defaultPostSortType = DEFAULT_POST_SORT_TYPE; + + try { + defaultFeedListType = FeedListType.values.byName(UserPreferences.getLocalSetting(LocalSettings.defaultFeedListType) ?? DEFAULT_LISTING_TYPE.name); + defaultPostSortType = PostSortType.values.byName(UserPreferences.getLocalSetting(LocalSettings.defaultFeedPostSortType) ?? DEFAULT_POST_SORT_TYPE.name); + } catch (e) { + defaultFeedListType = FeedListType.values.byName(DEFAULT_LISTING_TYPE.name); + defaultPostSortType = PostSortType.values.byName(DEFAULT_POST_SORT_TYPE.name); + } + + // NSFW Settings + final hideNsfwPosts = UserPreferences.getLocalSetting(LocalSettings.hideNsfwPosts) ?? false; + final hideNsfwPreviews = UserPreferences.getLocalSetting(LocalSettings.hideNsfwPreviews) ?? true; + + // General Settings + final markPostReadOnMediaView = UserPreferences.getLocalSetting(LocalSettings.markPostAsReadOnMediaView) ?? false; + final markPostReadOnScroll = UserPreferences.getLocalSetting(LocalSettings.markPostAsReadOnScroll) ?? false; + final showHiddenPosts = UserPreferences.getLocalSetting(LocalSettings.showHiddenPosts) ?? false; + final showExpandedTaglines = UserPreferences.getLocalSetting(LocalSettings.showExpandedTaglines) ?? false; + + /// -------------------------- Feed Post Related Settings -------------------------- + // Compact Related Settings + final useCompactView = UserPreferences.getLocalSetting(LocalSettings.useCompactView) ?? false; + final showTitleFirst = UserPreferences.getLocalSetting(LocalSettings.showPostTitleFirst) ?? false; + final hideThumbnails = UserPreferences.getLocalSetting(LocalSettings.hideThumbnails) ?? false; + final showThumbnailPreviewOnRight = UserPreferences.getLocalSetting(LocalSettings.showThumbnailPreviewOnRight) ?? false; + final showTextPostIndicator = UserPreferences.getLocalSetting(LocalSettings.showTextPostIndicator) ?? false; + final tappableAuthorCommunity = UserPreferences.getLocalSetting(LocalSettings.tappableAuthorCommunity) ?? false; + + // General Settings + final showVoteActions = UserPreferences.getLocalSetting(LocalSettings.showPostVoteActions) ?? true; + final showSaveAction = UserPreferences.getLocalSetting(LocalSettings.showPostSaveAction) ?? true; + final showCommunityIcons = UserPreferences.getLocalSetting(LocalSettings.showPostCommunityIcons) ?? false; + final showFullHeightImages = UserPreferences.getLocalSetting(LocalSettings.showPostFullHeightImages) ?? true; + final showEdgeToEdgeImages = UserPreferences.getLocalSetting(LocalSettings.showPostEdgeToEdgeImages) ?? false; + final showTextContent = UserPreferences.getLocalSetting(LocalSettings.showPostTextContentPreview) ?? false; + final showPostAuthor = UserPreferences.getLocalSetting(LocalSettings.showPostAuthor) ?? false; + final postShowUserInstance = UserPreferences.getLocalSetting(LocalSettings.postShowUserInstance) ?? false; + final dimReadPosts = UserPreferences.getLocalSetting(LocalSettings.dimReadPosts) ?? true; + final showFullPostDate = UserPreferences.getLocalSetting(LocalSettings.showFullPostDate) ?? false; + final dateFormat = DateFormat(UserPreferences.getLocalSetting(LocalSettings.dateFormat) ?? DateFormat.yMMMMd(Intl.systemLocale).add_jm().pattern); + final feedCardDividerThickness = FeedCardDividerThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.feedCardDividerThickness) ?? FeedCardDividerThickness.compact.name); + final feedCardDividerColor = UserPreferences.getLocalSetting(LocalSettings.feedCardDividerColor) != null ? Color(UserPreferences.getLocalSetting(LocalSettings.feedCardDividerColor)!) : null; + final compactPostCardMetadataItems = + UserPreferences.getLocalSetting>(LocalSettings.compactPostCardMetadataItems)?.map((e) => PostCardMetadataItem.values.byName(e)).toList() ?? DEFAULT_COMPACT_POST_CARD_METADATA; + final cardPostCardMetadataItems = + UserPreferences.getLocalSetting>(LocalSettings.cardPostCardMetadataItems)?.map((e) => PostCardMetadataItem.values.byName(e)).toList() ?? DEFAULT_CARD_POST_CARD_METADATA; + + // Post body settings + final showCrossPosts = UserPreferences.getLocalSetting(LocalSettings.showCrossPosts) ?? true; + final postBodyViewType = PostBodyViewType.values.byName(UserPreferences.getLocalSetting(LocalSettings.postBodyViewType) ?? PostBodyViewType.expanded.name); + final postBodyShowUserInstance = UserPreferences.getLocalSetting(LocalSettings.postBodyShowUserInstance) ?? false; + final postBodyShowCommunityInstance = UserPreferences.getLocalSetting(LocalSettings.postBodyShowCommunityInstance) ?? false; + final postBodyShowCommunityAvatar = UserPreferences.getLocalSetting(LocalSettings.postBodyShowCommunityAvatar) ?? false; + + final keywordFilters = (UserPreferences.getLocalSetting(LocalSettings.keywordFilters) as List?)?.cast().toList() ?? []; + + emit( + FeedPreferencesState( + defaultFeedListType: defaultFeedListType, + defaultPostSortType: defaultPostSortType, + hideNsfwPosts: hideNsfwPosts, + hideNsfwPreviews: hideNsfwPreviews, + markPostReadOnMediaView: markPostReadOnMediaView, + markPostReadOnScroll: markPostReadOnScroll, + showHiddenPosts: showHiddenPosts, + showExpandedTaglines: showExpandedTaglines, + useCompactView: useCompactView, + showTitleFirst: showTitleFirst, + hideThumbnails: hideThumbnails, + showThumbnailPreviewOnRight: showThumbnailPreviewOnRight, + showTextPostIndicator: showTextPostIndicator, + tappableAuthorCommunity: tappableAuthorCommunity, + showVoteActions: showVoteActions, + showSaveAction: showSaveAction, + showCommunityIcons: showCommunityIcons, + showFullHeightImages: showFullHeightImages, + showEdgeToEdgeImages: showEdgeToEdgeImages, + showTextContent: showTextContent, + showPostAuthor: showPostAuthor, + postShowUserInstance: postShowUserInstance, + dimReadPosts: dimReadPosts, + showFullPostDate: showFullPostDate, + dateFormat: dateFormat, + feedCardDividerThickness: feedCardDividerThickness, + feedCardDividerColor: feedCardDividerColor, + compactPostCardMetadataItems: compactPostCardMetadataItems, + cardPostCardMetadataItems: cardPostCardMetadataItems, + showCrossPosts: showCrossPosts, + postBodyViewType: postBodyViewType, + postBodyShowUserInstance: postBodyShowUserInstance, + postBodyShowCommunityInstance: postBodyShowCommunityInstance, + postBodyShowCommunityAvatar: postBodyShowCommunityAvatar, + keywordFilters: keywordFilters, + ), + ); + } + + /// Reloads preferences from storage. This should be called when preferences are updated elsewhere + void reload() { + load(); + } +} diff --git a/lib/src/app/cubits/feed_preferences_cubit/feed_preferences_state.dart b/lib/src/app/cubits/feed_preferences_cubit/feed_preferences_state.dart new file mode 100644 index 000000000..934c91ef7 --- /dev/null +++ b/lib/src/app/cubits/feed_preferences_cubit/feed_preferences_state.dart @@ -0,0 +1,262 @@ +part of 'feed_preferences_cubit.dart'; + +class FeedPreferencesState extends Equatable { + const FeedPreferencesState({ + this.defaultFeedListType = DEFAULT_LISTING_TYPE, + this.defaultPostSortType = DEFAULT_POST_SORT_TYPE, + this.hideNsfwPosts = false, + this.hideNsfwPreviews = true, + this.markPostReadOnMediaView = false, + this.markPostReadOnScroll = false, + this.showHiddenPosts = false, + this.showExpandedTaglines = false, + this.useCompactView = false, + this.showTitleFirst = false, + this.hideThumbnails = false, + this.showThumbnailPreviewOnRight = false, + this.showTextPostIndicator = false, + this.tappableAuthorCommunity = false, + this.showVoteActions = true, + this.showSaveAction = true, + this.showCommunityIcons = false, + this.showFullHeightImages = true, + this.showEdgeToEdgeImages = false, + this.showTextContent = false, + this.showPostAuthor = false, + this.postShowUserInstance = false, + this.dimReadPosts = true, + this.showFullPostDate = false, + this.dateFormat, + this.feedCardDividerThickness = FeedCardDividerThickness.compact, + this.feedCardDividerColor, + this.compactPostCardMetadataItems = const [], + this.cardPostCardMetadataItems = const [], + this.showCrossPosts = true, + this.postBodyViewType = PostBodyViewType.expanded, + this.postBodyShowUserInstance = false, + this.postBodyShowCommunityInstance = false, + this.postBodyShowCommunityAvatar = false, + this.keywordFilters = const [], + }); + + /// The default feed list type for guest profiles + final FeedListType defaultFeedListType; + + /// The default post sort type for guest profiles + final PostSortType defaultPostSortType; + + /// Whether to hide NSFW posts + final bool hideNsfwPosts; + + /// Whether to blur NSFW previews + final bool hideNsfwPreviews; + + /// Whether to mark a post as read when opening the media (image, video, link, etc.) + final bool markPostReadOnMediaView; + + /// Whether to mark a post as read when scrolling to it + final bool markPostReadOnScroll; + + /// Whether to show hidden posts + final bool showHiddenPosts; + + /// Whether to show expanded taglines + final bool showExpandedTaglines; + + /// Whether to use compact view for the post list + final bool useCompactView; + + /// Whether to show the title of the post at the top (card view) + final bool showTitleFirst; + + /// Whether to hide thumbnails + final bool hideThumbnails; + + /// Whether to show thumbnail preview on the right (compact view) + final bool showThumbnailPreviewOnRight; + + /// Whether to show text post indicator (compact view) + final bool showTextPostIndicator; + + /// Whether to make the author/community metadata tappable + final bool tappableAuthorCommunity; + + /// Whether to show vote actions (card view) + final bool showVoteActions; + + /// Whether to show save action (card view) + final bool showSaveAction; + + /// Whether to show community icons beside the community metadata + final bool showCommunityIcons; + + /// Whether to show full height images (card view) + final bool showFullHeightImages; + + /// Whether to show edge to edge images (card view) + final bool showEdgeToEdgeImages; + + /// Whether to show a preview of the post's text content (card view) + final bool showTextContent; + + /// Whether to show the post author + final bool showPostAuthor; + + /// Whether to show the instance when displaying the post author metadata + /// Only has an effect if [showPostAuthor] is true + final bool postShowUserInstance; + + /// Whether to dim the background of read posts + final bool dimReadPosts; + + /// Whether to show the full post date + final bool showFullPostDate; + + /// The date format to use for the post date + final DateFormat? dateFormat; + + /// The thickness of the feed card divider + final FeedCardDividerThickness feedCardDividerThickness; + + /// The color of the feed card divider + final Color? feedCardDividerColor; + + /// The metadata items to show in the compact post card + final List compactPostCardMetadataItems; + + /// The metadata items to show in the card post card + final List cardPostCardMetadataItems; + + /// Whether to show cross posts (post page body) + final bool showCrossPosts; + + /// The view type to use for the body of the post (post page body) + final PostBodyViewType postBodyViewType; + + /// Whether to show the user instance on the author metadata (post page body) + final bool postBodyShowUserInstance; + + /// Whether to show the community instance on the community metadata (post page body) + final bool postBodyShowCommunityInstance; + + /// Whether to show the community avatar on the community metadata (post page body) + final bool postBodyShowCommunityAvatar; + + /// The list of keywords to filter out posts from the feed + final List keywordFilters; + + FeedPreferencesState copyWith({ + FeedListType? defaultFeedListType, + PostSortType? defaultPostSortType, + bool? hideNsfwPosts, + bool? hideNsfwPreviews, + bool? markPostReadOnMediaView, + bool? markPostReadOnScroll, + bool? showHiddenPosts, + bool? showExpandedTaglines, + bool? useCompactView, + bool? showTitleFirst, + bool? hideThumbnails, + bool? showThumbnailPreviewOnRight, + bool? showTextPostIndicator, + bool? tappableAuthorCommunity, + bool? showVoteActions, + bool? showSaveAction, + bool? showCommunityIcons, + bool? showFullHeightImages, + bool? showEdgeToEdgeImages, + bool? showTextContent, + bool? showPostAuthor, + bool? postShowUserInstance, + bool? dimReadPosts, + bool? showFullPostDate, + DateFormat? dateFormat, + FeedCardDividerThickness? feedCardDividerThickness, + Color? feedCardDividerColor, + List? compactPostCardMetadataItems, + List? cardPostCardMetadataItems, + bool? showCrossPosts, + PostBodyViewType? postBodyViewType, + bool? postBodyShowUserInstance, + bool? postBodyShowCommunityInstance, + bool? postBodyShowCommunityAvatar, + List? keywordFilters, + }) { + return FeedPreferencesState( + defaultFeedListType: defaultFeedListType ?? this.defaultFeedListType, + defaultPostSortType: defaultPostSortType ?? this.defaultPostSortType, + hideNsfwPosts: hideNsfwPosts ?? this.hideNsfwPosts, + hideNsfwPreviews: hideNsfwPreviews ?? this.hideNsfwPreviews, + markPostReadOnMediaView: markPostReadOnMediaView ?? this.markPostReadOnMediaView, + markPostReadOnScroll: markPostReadOnScroll ?? this.markPostReadOnScroll, + showHiddenPosts: showHiddenPosts ?? this.showHiddenPosts, + showExpandedTaglines: showExpandedTaglines ?? this.showExpandedTaglines, + useCompactView: useCompactView ?? this.useCompactView, + showTitleFirst: showTitleFirst ?? this.showTitleFirst, + hideThumbnails: hideThumbnails ?? this.hideThumbnails, + showThumbnailPreviewOnRight: showThumbnailPreviewOnRight ?? this.showThumbnailPreviewOnRight, + showTextPostIndicator: showTextPostIndicator ?? this.showTextPostIndicator, + tappableAuthorCommunity: tappableAuthorCommunity ?? this.tappableAuthorCommunity, + showVoteActions: showVoteActions ?? this.showVoteActions, + showSaveAction: showSaveAction ?? this.showSaveAction, + showCommunityIcons: showCommunityIcons ?? this.showCommunityIcons, + showFullHeightImages: showFullHeightImages ?? this.showFullHeightImages, + showEdgeToEdgeImages: showEdgeToEdgeImages ?? this.showEdgeToEdgeImages, + showTextContent: showTextContent ?? this.showTextContent, + showPostAuthor: showPostAuthor ?? this.showPostAuthor, + postShowUserInstance: postShowUserInstance ?? this.postShowUserInstance, + dimReadPosts: dimReadPosts ?? this.dimReadPosts, + showFullPostDate: showFullPostDate ?? this.showFullPostDate, + dateFormat: dateFormat ?? this.dateFormat, + feedCardDividerThickness: feedCardDividerThickness ?? this.feedCardDividerThickness, + feedCardDividerColor: feedCardDividerColor, + compactPostCardMetadataItems: compactPostCardMetadataItems ?? this.compactPostCardMetadataItems, + cardPostCardMetadataItems: cardPostCardMetadataItems ?? this.cardPostCardMetadataItems, + showCrossPosts: showCrossPosts ?? this.showCrossPosts, + postBodyViewType: postBodyViewType ?? this.postBodyViewType, + postBodyShowUserInstance: postBodyShowUserInstance ?? this.postBodyShowUserInstance, + postBodyShowCommunityInstance: postBodyShowCommunityInstance ?? this.postBodyShowCommunityInstance, + postBodyShowCommunityAvatar: postBodyShowCommunityAvatar ?? this.postBodyShowCommunityAvatar, + keywordFilters: keywordFilters ?? this.keywordFilters, + ); + } + + @override + List get props => [ + defaultFeedListType, + defaultPostSortType, + hideNsfwPosts, + hideNsfwPreviews, + markPostReadOnMediaView, + markPostReadOnScroll, + showHiddenPosts, + showExpandedTaglines, + useCompactView, + showTitleFirst, + hideThumbnails, + showThumbnailPreviewOnRight, + showTextPostIndicator, + tappableAuthorCommunity, + showVoteActions, + showSaveAction, + showCommunityIcons, + showFullHeightImages, + showEdgeToEdgeImages, + showTextContent, + showPostAuthor, + postShowUserInstance, + dimReadPosts, + showFullPostDate, + dateFormat, + feedCardDividerThickness, + feedCardDividerColor, + compactPostCardMetadataItems, + cardPostCardMetadataItems, + showCrossPosts, + postBodyViewType, + postBodyShowUserInstance, + postBodyShowCommunityInstance, + postBodyShowCommunityAvatar, + keywordFilters, + ]; +} diff --git a/lib/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart b/lib/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart new file mode 100644 index 000000000..090e072af --- /dev/null +++ b/lib/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart @@ -0,0 +1,45 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'feed_ui_state.dart'; + +/// Cubit for managing feed's state for certain actions +class FeedUiCubit extends Cubit { + FeedUiCubit() : super(const FeedUiState()); + + /// Increments the scrollId. This is used to trigger a scroll to top action + void scrollToTop() { + emit(state.copyWith(scrollId: state.scrollId + 1)); + } + + /// Increments the dismissReadId. This is used to trigger dismissing read posts action + void dismissRead() { + emit(state.copyWith(dismissReadId: state.dismissReadId + 1)); + } + + /// Sets the dismiss blocked user/community IDs. This is used to trigger dismissing blocked user/community posts action + void dismissBlocked({int? userId, int? communityId}) { + emit(state.copyWith( + dismissBlockedUserId: userId, + dismissBlockedCommunityId: communityId, + )); + } + + /// Sets the dismiss hidden post ID. This is used to trigger dismissing hidden posts action + void dismissHiddenPost(int postId) { + emit(state.copyWith(dismissHiddenPostId: postId)); + } + + /// Clears the dismiss hidden post ID + void clearDismissHiddenPost() { + emit(state.copyWith(dismissHiddenPostId: null)); + } + + /// Clears the dismiss blocked user/community IDs + void clearDismissBlocked() { + emit(state.copyWith( + dismissBlockedUserId: null, + dismissBlockedCommunityId: null, + )); + } +} diff --git a/lib/src/app/cubits/feed_ui_cubit/feed_ui_state.dart b/lib/src/app/cubits/feed_ui_cubit/feed_ui_state.dart new file mode 100644 index 000000000..188ac860b --- /dev/null +++ b/lib/src/app/cubits/feed_ui_cubit/feed_ui_state.dart @@ -0,0 +1,51 @@ +part of 'feed_ui_cubit.dart'; + +class FeedUiState extends Equatable { + const FeedUiState({ + this.scrollId = 0, + this.dismissReadId = 0, + this.dismissBlockedUserId, + this.dismissBlockedCommunityId, + this.dismissHiddenPostId, + }); + + /// This id is used for scrolling back to the top + final int scrollId; + + /// This id is used for dismissing already read posts in the feed + final int dismissReadId; + + /// This id is used for dismissing posts from blocked users + final int? dismissBlockedUserId; + + /// This id is used for dismissing posts from blocked communities + final int? dismissBlockedCommunityId; + + /// This id is used for dismissing posts that have been hidden by the user + final int? dismissHiddenPostId; + + FeedUiState copyWith({ + int? scrollId, + int? dismissReadId, + int? dismissBlockedUserId, + int? dismissBlockedCommunityId, + int? dismissHiddenPostId, + }) { + return FeedUiState( + scrollId: scrollId ?? this.scrollId, + dismissReadId: dismissReadId ?? this.dismissReadId, + dismissBlockedUserId: dismissBlockedUserId, + dismissBlockedCommunityId: dismissBlockedCommunityId, + dismissHiddenPostId: dismissHiddenPostId, + ); + } + + @override + List get props => [ + scrollId, + dismissReadId, + dismissBlockedUserId, + dismissBlockedCommunityId, + dismissHiddenPostId, + ]; +} diff --git a/lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart b/lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart new file mode 100644 index 000000000..fbcfa172a --- /dev/null +++ b/lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart @@ -0,0 +1,66 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +import 'package:thunder/src/core/enums/local_settings.dart'; +import 'package:thunder/src/core/enums/swipe_action.dart'; +import 'package:thunder/src/core/singletons/preferences.dart'; + +part 'gesture_preferences_state.dart'; + +/// Cubit for managing gesture-related preferences +class GesturePreferencesCubit extends Cubit { + GesturePreferencesCubit() : super(const GesturePreferencesState()) { + load(); + } + + /// Loads gesture preferences from UserPreferences + void load() { + // Sidebar Gesture Settings + final bottomNavBarSwipeGestures = UserPreferences.getLocalSetting(LocalSettings.sidebarBottomNavBarSwipeGesture) ?? true; + final bottomNavBarDoubleTapGestures = UserPreferences.getLocalSetting(LocalSettings.sidebarBottomNavBarDoubleTapGesture) ?? false; + + // Post Gestures + final enablePostGestures = UserPreferences.getLocalSetting(LocalSettings.enablePostGestures) ?? true; + final leftPrimaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureLeftPrimary) ?? SwipeAction.upvote.name); + final leftSecondaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureLeftSecondary) ?? SwipeAction.downvote.name); + final rightPrimaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureRightPrimary) ?? SwipeAction.save.name); + final rightSecondaryPostGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.postGestureRightSecondary) ?? SwipeAction.toggleRead.name); + + // Comment Gestures + final enableCommentGestures = UserPreferences.getLocalSetting(LocalSettings.enableCommentGestures) ?? true; + final leftPrimaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureLeftPrimary) ?? SwipeAction.upvote.name); + final leftSecondaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureLeftSecondary) ?? SwipeAction.downvote.name); + final rightPrimaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureRightPrimary) ?? SwipeAction.reply.name); + final rightSecondaryCommentGesture = SwipeAction.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentGestureRightSecondary) ?? SwipeAction.save.name); + + // Navigation Gestures + final enableFullScreenSwipeNavigationGesture = UserPreferences.getLocalSetting(LocalSettings.enableFullScreenSwipeNavigationGesture) ?? true; + + // Image Peek Settings + final imagePeekDuration = UserPreferences.getLocalSetting(LocalSettings.imagePeekDuration) ?? 300; + + emit( + GesturePreferencesState( + bottomNavBarSwipeGestures: bottomNavBarSwipeGestures, + bottomNavBarDoubleTapGestures: bottomNavBarDoubleTapGestures, + enablePostGestures: enablePostGestures, + leftPrimaryPostGesture: leftPrimaryPostGesture, + leftSecondaryPostGesture: leftSecondaryPostGesture, + rightPrimaryPostGesture: rightPrimaryPostGesture, + rightSecondaryPostGesture: rightSecondaryPostGesture, + enableCommentGestures: enableCommentGestures, + leftPrimaryCommentGesture: leftPrimaryCommentGesture, + leftSecondaryCommentGesture: leftSecondaryCommentGesture, + rightPrimaryCommentGesture: rightPrimaryCommentGesture, + rightSecondaryCommentGesture: rightSecondaryCommentGesture, + enableFullScreenSwipeNavigationGesture: enableFullScreenSwipeNavigationGesture, + imagePeekDuration: imagePeekDuration, + ), + ); + } + + /// Reloads preferences from storage. This should be called when preferences are updated elsewhere + void reload() { + load(); + } +} diff --git a/lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_state.dart b/lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_state.dart new file mode 100644 index 000000000..49c6d9e6c --- /dev/null +++ b/lib/src/app/cubits/gesture_preferences_cubit/gesture_preferences_state.dart @@ -0,0 +1,116 @@ +part of 'gesture_preferences_cubit.dart'; + +class GesturePreferencesState extends Equatable { + const GesturePreferencesState({ + this.bottomNavBarSwipeGestures = true, + this.bottomNavBarDoubleTapGestures = false, + this.enablePostGestures = true, + this.leftPrimaryPostGesture = SwipeAction.upvote, + this.leftSecondaryPostGesture = SwipeAction.downvote, + this.rightPrimaryPostGesture = SwipeAction.save, + this.rightSecondaryPostGesture = SwipeAction.toggleRead, + this.enableCommentGestures = true, + this.leftPrimaryCommentGesture = SwipeAction.upvote, + this.leftSecondaryCommentGesture = SwipeAction.downvote, + this.rightPrimaryCommentGesture = SwipeAction.reply, + this.rightSecondaryCommentGesture = SwipeAction.save, + this.enableFullScreenSwipeNavigationGesture = true, + this.imagePeekDuration = 300, + }); + + /// Whether the bottom navigation bar swipe gestures is enabled. + /// This gesture is used to open the drawer when swiping from the bottom of the screen. + final bool bottomNavBarSwipeGestures; + + /// Whether the bottom navigation bar double tap gestures is enabled. + /// This gesture is used to open the drawer when double tapping the bottom of the screen. + final bool bottomNavBarDoubleTapGestures; + + /// Whether post swipe gestures are enabled. + final bool enablePostGestures; + + /// The primary swipe action for posts when swiping from the left of the screen. + final SwipeAction leftPrimaryPostGesture; + + /// The secondary swipe action for posts when swiping from the left of the screen. + final SwipeAction leftSecondaryPostGesture; + + /// The primary swipe action for posts when swiping from the right of the screen. + final SwipeAction rightPrimaryPostGesture; + + /// The secondary swipe action for posts when swiping from the right of the screen. + final SwipeAction rightSecondaryPostGesture; + + /// Whether comment swipe gestures are enabled. + final bool enableCommentGestures; + + /// The primary swipe action for comments when swiping from the left of the screen. + final SwipeAction leftPrimaryCommentGesture; + + /// The secondary swipe action for comments when swiping from the left of the screen. + final SwipeAction leftSecondaryCommentGesture; + + /// The primary swipe action for comments when swiping from the right of the screen. + final SwipeAction rightPrimaryCommentGesture; + + /// The secondary swipe action for comments when swiping from the right of the screen. + final SwipeAction rightSecondaryCommentGesture; + + /// Whether full screen swipe navigation gestures are enabled. This allows navigating back to previous screen when swiping anywhere on the screen. + final bool enableFullScreenSwipeNavigationGesture; + + /// The duration in milliseconds before image peek is triggered (default: 300ms) + final int imagePeekDuration; + + GesturePreferencesState copyWith({ + bool? bottomNavBarSwipeGestures, + bool? bottomNavBarDoubleTapGestures, + bool? enablePostGestures, + SwipeAction? leftPrimaryPostGesture, + SwipeAction? leftSecondaryPostGesture, + SwipeAction? rightPrimaryPostGesture, + SwipeAction? rightSecondaryPostGesture, + bool? enableCommentGestures, + SwipeAction? leftPrimaryCommentGesture, + SwipeAction? leftSecondaryCommentGesture, + SwipeAction? rightPrimaryCommentGesture, + SwipeAction? rightSecondaryCommentGesture, + bool? enableFullScreenSwipeNavigationGesture, + int? imagePeekDuration, + }) { + return GesturePreferencesState( + bottomNavBarSwipeGestures: bottomNavBarSwipeGestures ?? this.bottomNavBarSwipeGestures, + bottomNavBarDoubleTapGestures: bottomNavBarDoubleTapGestures ?? this.bottomNavBarDoubleTapGestures, + enablePostGestures: enablePostGestures ?? this.enablePostGestures, + leftPrimaryPostGesture: leftPrimaryPostGesture ?? this.leftPrimaryPostGesture, + leftSecondaryPostGesture: leftSecondaryPostGesture ?? this.leftSecondaryPostGesture, + rightPrimaryPostGesture: rightPrimaryPostGesture ?? this.rightPrimaryPostGesture, + rightSecondaryPostGesture: rightSecondaryPostGesture ?? this.rightSecondaryPostGesture, + enableCommentGestures: enableCommentGestures ?? this.enableCommentGestures, + leftPrimaryCommentGesture: leftPrimaryCommentGesture ?? this.leftPrimaryCommentGesture, + leftSecondaryCommentGesture: leftSecondaryCommentGesture ?? this.leftSecondaryCommentGesture, + rightPrimaryCommentGesture: rightPrimaryCommentGesture ?? this.rightPrimaryCommentGesture, + rightSecondaryCommentGesture: rightSecondaryCommentGesture ?? this.rightSecondaryCommentGesture, + enableFullScreenSwipeNavigationGesture: enableFullScreenSwipeNavigationGesture ?? this.enableFullScreenSwipeNavigationGesture, + imagePeekDuration: imagePeekDuration ?? this.imagePeekDuration, + ); + } + + @override + List get props => [ + bottomNavBarSwipeGestures, + bottomNavBarDoubleTapGestures, + enablePostGestures, + leftPrimaryPostGesture, + leftSecondaryPostGesture, + rightPrimaryPostGesture, + rightSecondaryPostGesture, + enableCommentGestures, + leftPrimaryCommentGesture, + leftSecondaryCommentGesture, + rightPrimaryCommentGesture, + rightSecondaryCommentGesture, + enableFullScreenSwipeNavigationGesture, + imagePeekDuration, + ]; +} diff --git a/lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_cubit.dart b/lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_cubit.dart new file mode 100644 index 000000000..a925d3841 --- /dev/null +++ b/lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_cubit.dart @@ -0,0 +1,14 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'nav_bar_state_state.dart'; + +/// Cubit for managing bottom navigation bar state +class NavBarStateCubit extends Cubit { + NavBarStateCubit() : super(const NavBarStateState()); + + /// Sets the bottom navigation bar visibility + void setBottomNavBarVisible(bool isVisible) { + emit(state.copyWith(isBottomNavBarVisible: isVisible)); + } +} diff --git a/lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_state.dart b/lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_state.dart new file mode 100644 index 000000000..0fc309417 --- /dev/null +++ b/lib/src/app/cubits/nav_bar_state_cubit/nav_bar_state_state.dart @@ -0,0 +1,21 @@ +part of 'nav_bar_state_cubit.dart'; + +class NavBarStateState extends Equatable { + const NavBarStateState({ + this.isBottomNavBarVisible = true, + }); + + /// Whether the bottom navigation bar is currently visible + final bool isBottomNavBarVisible; + + NavBarStateState copyWith({ + bool? isBottomNavBarVisible, + }) { + return NavBarStateState( + isBottomNavBarVisible: isBottomNavBarVisible ?? this.isBottomNavBarVisible, + ); + } + + @override + List get props => [isBottomNavBarVisible]; +} diff --git a/lib/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart b/lib/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart new file mode 100644 index 000000000..a38dd216d --- /dev/null +++ b/lib/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +import 'package:thunder/src/core/enums/local_settings.dart'; +import 'package:thunder/src/core/enums/theme_type.dart'; +import 'package:thunder/src/core/enums/custom_theme_type.dart'; +import 'package:thunder/src/core/enums/action_color.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; +import 'package:thunder/src/core/enums/full_name.dart'; +import 'package:thunder/src/core/singletons/preferences.dart'; + +part 'theme_preferences_state.dart'; + +/// Cubit for managing theme-related preferences +class ThemePreferencesCubit extends Cubit { + ThemePreferencesCubit() : super(const ThemePreferencesState()) { + load(); + } + + /// Loads theme preferences from UserPreferences + void load() { + // Theme Settings + ThemeType themeType = ThemeType.values[UserPreferences.getLocalSetting(LocalSettings.appTheme) ?? ThemeType.system.index]; + Brightness brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness; + + // Check if the user has selected to use a pure black theme, if so override the themeType to pureBlack + bool usePureBlackTheme = UserPreferences.getLocalSetting(LocalSettings.usePureBlackTheme) ?? false; + if (usePureBlackTheme && (themeType == ThemeType.dark || (themeType == ThemeType.system && brightness == Brightness.dark))) { + themeType = ThemeType.pureBlack; + } + + final selectedTheme = CustomThemeType.values.byName(UserPreferences.getLocalSetting(LocalSettings.appThemeAccentColor) ?? CustomThemeType.deepBlue.name); + final useMaterialYouTheme = UserPreferences.getLocalSetting(LocalSettings.useMaterialYouTheme) ?? false; + + // Fetch reduce animations preferences to remove overscrolling effects + final reduceAnimations = UserPreferences.getLocalSetting(LocalSettings.reduceAnimations) ?? false; + + // Color Settings + final upvoteColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.upvoteColor) ?? ActionColor.orange); + final downvoteColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.downvoteColor) ?? ActionColor.blue); + final saveColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.saveColor) ?? ActionColor.purple); + final markReadColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.markReadColor) ?? ActionColor.teal); + final replyColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.replyColor) ?? ActionColor.green); + final hideColor = ActionColor.fromString(colorRaw: UserPreferences.getLocalSetting(LocalSettings.hideColor) ?? ActionColor.red); + + // Font Settings + final titleFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.titleFontSizeScale) ?? FontScale.base.name); + final contentFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.contentFontSizeScale) ?? FontScale.base.name); + final commentFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.commentFontSizeScale) ?? FontScale.base.name); + final metadataFontSizeScale = FontScale.values.byName(UserPreferences.getLocalSetting(LocalSettings.metadataFontSizeScale) ?? FontScale.base.name); + + // User/Community Display Name Settings + final useDisplayNamesForUsers = UserPreferences.getLocalSetting(LocalSettings.useDisplayNamesForUsers) ?? false; + final useDisplayNamesForCommunities = UserPreferences.getLocalSetting(LocalSettings.useDisplayNamesForCommunities) ?? false; + final userSeparator = FullNameSeparator.values.byName(UserPreferences.getLocalSetting(LocalSettings.userFormat) ?? FullNameSeparator.at.name); + final userFullNameUserNameThickness = NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.userFullNameUserNameThickness) ?? NameThickness.normal.name); + final userFullNameUserNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.userFullNameUserNameColor) ?? NameColor.defaultColor); + final userFullNameInstanceNameThickness = NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.userFullNameInstanceNameThickness) ?? NameThickness.light.name); + final userFullNameInstanceNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.userFullNameInstanceNameColor) ?? NameColor.defaultColor); + final communitySeparator = FullNameSeparator.values.byName(UserPreferences.getLocalSetting(LocalSettings.communityFormat) ?? FullNameSeparator.dot.name); + final communityFullNameCommunityNameThickness = NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.communityFullNameCommunityNameThickness) ?? NameThickness.normal.name); + final communityFullNameCommunityNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.communityFullNameCommunityNameColor) ?? NameColor.defaultColor); + final communityFullNameInstanceNameThickness = NameThickness.values.byName(UserPreferences.getLocalSetting(LocalSettings.communityFullNameInstanceNameThickness) ?? NameThickness.light.name); + final communityFullNameInstanceNameColor = NameColor.fromString(color: UserPreferences.getLocalSetting(LocalSettings.communityFullNameInstanceNameColor) ?? NameColor.defaultColor); + + emit(ThemePreferencesState( + themeType: themeType, + selectedTheme: selectedTheme, + useMaterialYouTheme: useMaterialYouTheme, + reduceAnimations: reduceAnimations, + upvoteColor: upvoteColor, + downvoteColor: downvoteColor, + saveColor: saveColor, + markReadColor: markReadColor, + replyColor: replyColor, + hideColor: hideColor, + titleFontSizeScale: titleFontSizeScale, + contentFontSizeScale: contentFontSizeScale, + commentFontSizeScale: commentFontSizeScale, + metadataFontSizeScale: metadataFontSizeScale, + useDisplayNamesForUsers: useDisplayNamesForUsers, + useDisplayNamesForCommunities: useDisplayNamesForCommunities, + userSeparator: userSeparator, + userFullNameUserNameThickness: userFullNameUserNameThickness, + userFullNameUserNameColor: userFullNameUserNameColor, + userFullNameInstanceNameThickness: userFullNameInstanceNameThickness, + userFullNameInstanceNameColor: userFullNameInstanceNameColor, + communitySeparator: communitySeparator, + communityFullNameCommunityNameThickness: communityFullNameCommunityNameThickness, + communityFullNameCommunityNameColor: communityFullNameCommunityNameColor, + communityFullNameInstanceNameThickness: communityFullNameInstanceNameThickness, + communityFullNameInstanceNameColor: communityFullNameInstanceNameColor, + )); + } + + /// Reloads preferences from storage. This should be called when preferences are updated elsewhere + void reload() { + load(); + } +} diff --git a/lib/src/app/cubits/theme_preferences_cubit/theme_preferences_state.dart b/lib/src/app/cubits/theme_preferences_cubit/theme_preferences_state.dart new file mode 100644 index 000000000..d6c8fd037 --- /dev/null +++ b/lib/src/app/cubits/theme_preferences_cubit/theme_preferences_state.dart @@ -0,0 +1,207 @@ +part of 'theme_preferences_cubit.dart'; + +class ThemePreferencesState extends Equatable { + const ThemePreferencesState({ + this.themeType = ThemeType.system, + this.selectedTheme = CustomThemeType.deepBlue, + this.useMaterialYouTheme = false, + this.reduceAnimations = false, + this.upvoteColor = const ActionColor.fromString(colorRaw: ActionColor.orange), + this.downvoteColor = const ActionColor.fromString(colorRaw: ActionColor.blue), + this.saveColor = const ActionColor.fromString(colorRaw: ActionColor.purple), + this.markReadColor = const ActionColor.fromString(colorRaw: ActionColor.teal), + this.replyColor = const ActionColor.fromString(colorRaw: ActionColor.green), + this.hideColor = const ActionColor.fromString(colorRaw: ActionColor.red), + this.titleFontSizeScale = FontScale.base, + this.contentFontSizeScale = FontScale.base, + this.commentFontSizeScale = FontScale.base, + this.metadataFontSizeScale = FontScale.base, + this.useDisplayNamesForUsers = false, + this.useDisplayNamesForCommunities = false, + this.userSeparator = FullNameSeparator.at, + this.userFullNameUserNameThickness = NameThickness.normal, + this.userFullNameUserNameColor = const NameColor.fromString(color: NameColor.defaultColor), + this.userFullNameInstanceNameThickness = NameThickness.light, + this.userFullNameInstanceNameColor = const NameColor.fromString(color: NameColor.defaultColor), + this.communitySeparator = FullNameSeparator.dot, + this.communityFullNameCommunityNameThickness = NameThickness.normal, + this.communityFullNameCommunityNameColor = const NameColor.fromString(color: NameColor.defaultColor), + this.communityFullNameInstanceNameThickness = NameThickness.light, + this.communityFullNameInstanceNameColor = const NameColor.fromString(color: NameColor.defaultColor), + }); + + /// The theme type to use (system, light, dark, pure black) + final ThemeType themeType; + + /// The selected accent color for the theme. This is only applied if [useMaterialYouTheme] is false + final CustomThemeType selectedTheme; + + /// Whether to use Material You theme colors + final bool useMaterialYouTheme; + + /// Whether to reduce animations across the app + final bool reduceAnimations; + + /// The color to use for the upvote action + final ActionColor upvoteColor; + + /// The color to use for the downvote action + final ActionColor downvoteColor; + + /// The color to use for the save action + final ActionColor saveColor; + + /// The color to use for the mark read action + final ActionColor markReadColor; + + /// The color to use for the reply action + final ActionColor replyColor; + + /// The color to use for the hide action + final ActionColor hideColor; + + /// The font scale to use for the title font size (post title) + final FontScale titleFontSizeScale; + + /// The font scale to use for the content font size (post content) + final FontScale contentFontSizeScale; + + /// The font scale to use for the comment font size (comment content) + final FontScale commentFontSizeScale; + + /// The font scale to use for the metadata font size (post/comment metadata) + final FontScale metadataFontSizeScale; + + /// Whether to use display names for users instead of username + final bool useDisplayNamesForUsers; + + /// Whether to use display names for communities instead of community name + final bool useDisplayNamesForCommunities; + + /// The separator type to use between the user name and instance name (at, dot, slash) + final FullNameSeparator userSeparator; + + /// The thickness of the user name (normal, light, thin) + final NameThickness userFullNameUserNameThickness; + + /// The color of the user name + final NameColor userFullNameUserNameColor; + + /// The thickness of the instance name in the user full name (normal, light, thin) + final NameThickness userFullNameInstanceNameThickness; + + /// The color of the instance name in the user full name + final NameColor userFullNameInstanceNameColor; + + /// The separator type to use between the community name and instance name (at, dot, slash) + final FullNameSeparator communitySeparator; + + /// The thickness of the community name (normal, light, thin) + final NameThickness communityFullNameCommunityNameThickness; + + /// The color of the community name + final NameColor communityFullNameCommunityNameColor; + + /// The thickness of the instance name in the community full name (normal, light, thin) + final NameThickness communityFullNameInstanceNameThickness; + + /// The color of the instance name in the community full name + final NameColor communityFullNameInstanceNameColor; + + /// Determines if dark theme should be used based on themeType and platform brightness + bool get useDarkTheme { + if (themeType == ThemeType.system) { + return SchedulerBinding.instance.platformDispatcher.platformBrightness == Brightness.dark; + } + return themeType == ThemeType.dark || themeType == ThemeType.pureBlack; + } + + ThemePreferencesState copyWith({ + ThemeType? themeType, + CustomThemeType? selectedTheme, + bool? useMaterialYouTheme, + bool? reduceAnimations, + ActionColor? upvoteColor, + ActionColor? downvoteColor, + ActionColor? saveColor, + ActionColor? markReadColor, + ActionColor? replyColor, + ActionColor? hideColor, + FontScale? titleFontSizeScale, + FontScale? contentFontSizeScale, + FontScale? commentFontSizeScale, + FontScale? metadataFontSizeScale, + bool? useDisplayNamesForUsers, + bool? useDisplayNamesForCommunities, + FullNameSeparator? userSeparator, + NameThickness? userFullNameUserNameThickness, + NameColor? userFullNameUserNameColor, + NameThickness? userFullNameInstanceNameThickness, + NameColor? userFullNameInstanceNameColor, + FullNameSeparator? communitySeparator, + NameThickness? communityFullNameCommunityNameThickness, + NameColor? communityFullNameCommunityNameColor, + NameThickness? communityFullNameInstanceNameThickness, + NameColor? communityFullNameInstanceNameColor, + }) { + return ThemePreferencesState( + themeType: themeType ?? this.themeType, + selectedTheme: selectedTheme ?? this.selectedTheme, + useMaterialYouTheme: useMaterialYouTheme ?? this.useMaterialYouTheme, + reduceAnimations: reduceAnimations ?? this.reduceAnimations, + upvoteColor: upvoteColor ?? this.upvoteColor, + downvoteColor: downvoteColor ?? this.downvoteColor, + saveColor: saveColor ?? this.saveColor, + markReadColor: markReadColor ?? this.markReadColor, + replyColor: replyColor ?? this.replyColor, + hideColor: hideColor ?? this.hideColor, + titleFontSizeScale: titleFontSizeScale ?? this.titleFontSizeScale, + contentFontSizeScale: contentFontSizeScale ?? this.contentFontSizeScale, + commentFontSizeScale: commentFontSizeScale ?? this.commentFontSizeScale, + metadataFontSizeScale: metadataFontSizeScale ?? this.metadataFontSizeScale, + useDisplayNamesForUsers: useDisplayNamesForUsers ?? this.useDisplayNamesForUsers, + useDisplayNamesForCommunities: useDisplayNamesForCommunities ?? this.useDisplayNamesForCommunities, + userSeparator: userSeparator ?? this.userSeparator, + userFullNameUserNameThickness: userFullNameUserNameThickness ?? this.userFullNameUserNameThickness, + userFullNameUserNameColor: userFullNameUserNameColor ?? this.userFullNameUserNameColor, + userFullNameInstanceNameThickness: userFullNameInstanceNameThickness ?? this.userFullNameInstanceNameThickness, + userFullNameInstanceNameColor: userFullNameInstanceNameColor ?? this.userFullNameInstanceNameColor, + communitySeparator: communitySeparator ?? this.communitySeparator, + communityFullNameCommunityNameThickness: communityFullNameCommunityNameThickness ?? this.communityFullNameCommunityNameThickness, + communityFullNameCommunityNameColor: communityFullNameCommunityNameColor ?? this.communityFullNameCommunityNameColor, + communityFullNameInstanceNameThickness: communityFullNameInstanceNameThickness ?? this.communityFullNameInstanceNameThickness, + communityFullNameInstanceNameColor: communityFullNameInstanceNameColor ?? this.communityFullNameInstanceNameColor, + ); + } + + @override + List get props => [ + themeType, + selectedTheme, + useMaterialYouTheme, + reduceAnimations, + useDarkTheme, + upvoteColor, + downvoteColor, + saveColor, + markReadColor, + replyColor, + hideColor, + titleFontSizeScale, + contentFontSizeScale, + commentFontSizeScale, + metadataFontSizeScale, + useDisplayNamesForUsers, + useDisplayNamesForCommunities, + userSeparator, + userFullNameUserNameThickness, + userFullNameUserNameColor, + userFullNameInstanceNameThickness, + userFullNameInstanceNameColor, + communitySeparator, + communityFullNameCommunityNameThickness, + communityFullNameCommunityNameColor, + communityFullNameInstanceNameThickness, + communityFullNameInstanceNameColor, + ]; +} diff --git a/lib/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart b/lib/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart new file mode 100644 index 000000000..de727a214 --- /dev/null +++ b/lib/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart @@ -0,0 +1,43 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +import 'package:thunder/src/core/enums/local_settings.dart'; +import 'package:thunder/src/core/enums/video_auto_play.dart'; +import 'package:thunder/src/core/enums/video_playback_speed.dart'; +import 'package:thunder/src/core/enums/video_player_mode.dart'; +import 'package:thunder/src/core/singletons/preferences.dart'; + +part 'video_preferences_state.dart'; + +/// Cubit for managing video player preferences +class VideoPreferencesCubit extends Cubit { + VideoPreferencesCubit() : super(const VideoPreferencesState()) { + load(); + } + + /// Loads video preferences from UserPreferences + void load() { + final videoAutoFullscreen = UserPreferences.getLocalSetting(LocalSettings.videoAutoFullscreen) ?? false; + final videoAutoLoop = UserPreferences.getLocalSetting(LocalSettings.videoAutoLoop) ?? false; + final videoAutoMute = UserPreferences.getLocalSetting(LocalSettings.videoAutoMute) ?? true; + final videoAutoPlay = VideoAutoPlay.values.byName(UserPreferences.getLocalSetting(LocalSettings.videoAutoPlay) ?? VideoAutoPlay.never.name); + final videoDefaultPlaybackSpeed = VideoPlayBackSpeed.values.byName(UserPreferences.getLocalSetting(LocalSettings.videoDefaultPlaybackSpeed) ?? VideoPlayBackSpeed.normal.name); + final videoPlayerMode = VideoPlayerMode.values.byName(UserPreferences.getLocalSetting(LocalSettings.videoPlayerMode) ?? VideoPlayerMode.inApp.name); + + emit( + VideoPreferencesState( + videoAutoFullscreen: videoAutoFullscreen, + videoAutoLoop: videoAutoLoop, + videoAutoMute: videoAutoMute, + videoAutoPlay: videoAutoPlay, + videoDefaultPlaybackSpeed: videoDefaultPlaybackSpeed, + videoPlayerMode: videoPlayerMode, + ), + ); + } + + /// Reloads preferences from storage. This should be called when preferences are updated elsewhere + void reload() { + load(); + } +} diff --git a/lib/src/app/cubits/video_preferences_cubit/video_preferences_state.dart b/lib/src/app/cubits/video_preferences_cubit/video_preferences_state.dart new file mode 100644 index 000000000..bfac79c2f --- /dev/null +++ b/lib/src/app/cubits/video_preferences_cubit/video_preferences_state.dart @@ -0,0 +1,58 @@ +part of 'video_preferences_cubit.dart'; + +class VideoPreferencesState extends Equatable { + const VideoPreferencesState({ + this.videoAutoFullscreen = false, + this.videoAutoLoop = false, + this.videoAutoMute = true, + this.videoAutoPlay = VideoAutoPlay.never, + this.videoDefaultPlaybackSpeed = VideoPlayBackSpeed.normal, + this.videoPlayerMode = VideoPlayerMode.inApp, + }); + + /// Whether to automatically fullscreen videos + final bool videoAutoFullscreen; + + /// Whether to automatically loop videos + final bool videoAutoLoop; + + /// Whether to automatically mute videos + final bool videoAutoMute; + + /// Whether to automatically play videos + final VideoAutoPlay videoAutoPlay; + + /// The default playback speed for videos + final VideoPlayBackSpeed videoDefaultPlaybackSpeed; + + /// The player mode to use for videos (in-app, external) + final VideoPlayerMode videoPlayerMode; + + VideoPreferencesState copyWith({ + bool? videoAutoFullscreen, + bool? videoAutoLoop, + bool? videoAutoMute, + VideoAutoPlay? videoAutoPlay, + VideoPlayBackSpeed? videoDefaultPlaybackSpeed, + VideoPlayerMode? videoPlayerMode, + }) { + return VideoPreferencesState( + videoAutoFullscreen: videoAutoFullscreen ?? this.videoAutoFullscreen, + videoAutoLoop: videoAutoLoop ?? this.videoAutoLoop, + videoAutoMute: videoAutoMute ?? this.videoAutoMute, + videoAutoPlay: videoAutoPlay ?? this.videoAutoPlay, + videoDefaultPlaybackSpeed: videoDefaultPlaybackSpeed ?? this.videoDefaultPlaybackSpeed, + videoPlayerMode: videoPlayerMode ?? this.videoPlayerMode, + ); + } + + @override + List get props => [ + videoAutoFullscreen, + videoAutoLoop, + videoAutoMute, + videoAutoPlay, + videoDefaultPlaybackSpeed, + videoPlayerMode, + ]; +} diff --git a/lib/src/app/pages/thunder_page.dart b/lib/src/app/pages/thunder_page.dart index 10698a61b..e893005d8 100644 --- a/lib/src/app/pages/thunder_page.dart +++ b/lib/src/app/pages/thunder_page.dart @@ -28,6 +28,14 @@ import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/app/cubits/deep_links_cubit/deep_links_cubit.dart'; import 'package:thunder/src/app/cubits/notifications_cubit/notifications_cubit.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; +import 'package:thunder/src/app/cubits/nav_bar_state_cubit/nav_bar_state_cubit.dart'; import 'package:thunder/src/app/widgets/bottom_nav_bar.dart'; import 'package:thunder/src/shared/utils/links.dart'; import 'package:thunder/src/features/inbox/inbox.dart'; @@ -235,10 +243,23 @@ class _ThunderState extends State { } }, ), + BlocListener( + listenWhen: (previous, current) => previous.status != current.status && current.status == ThunderStatus.success, + listener: (context, state) { + // Reload preference cubits when preferences change + context.read().reload(); + context.read().reload(); + context.read().reload(); + context.read().reload(); + context.read().reload(); + context.read().reload(); + }, + ), ], child: BlocBuilder( + buildWhen: (previous, current) => previous.status != current.status, builder: (context, thunderBlocState) { - reduceAnimations = thunderBlocState.reduceAnimations; + reduceAnimations = context.read().state.reduceAnimations; switch (thunderBlocState.status) { case ThunderStatus.initial: @@ -249,7 +270,7 @@ class _ThunderState extends State { case ThunderStatus.refreshing: case ThunderStatus.success: // Update the variable so that it can be used in _handleBackButtonPress - _isFabOpen = thunderBlocState.isFabOpen; + _isFabOpen = context.read().state.isFeedFabOpen; return Scaffold( key: scaffoldStateKey, @@ -261,7 +282,7 @@ class _ThunderState extends State { }, ) : null, - floatingActionButton: thunderBlocState.enableFeedsFab + floatingActionButton: context.select((cubit) => cubit.state.enableFeedsFab) ? AnimatedOpacity( opacity: selectedPageIndex == 0 ? 1.0 : 0.0, duration: const Duration(milliseconds: 150), @@ -270,32 +291,38 @@ class _ThunderState extends State { ) : null, floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling, - bottomNavigationBar: AnimatedSize( - duration: Duration(milliseconds: thunderBlocState.reduceAnimations ? 0 : 200), - curve: Curves.easeInOut, - clipBehavior: Clip.hardEdge, - alignment: Alignment.topCenter, - child: SizedBox( - height: (thunderBlocState.hideBottomBarOnScroll && !thunderBlocState.isBottomNavBarVisible) ? 0 : null, - child: AnimatedOpacity( - duration: Duration(milliseconds: thunderBlocState.reduceAnimations ? 0 : 150), - curve: Curves.easeOut, - opacity: (thunderBlocState.hideBottomBarOnScroll && !thunderBlocState.isBottomNavBarVisible) ? 0.0 : 1.0, - child: CustomBottomNavigationBar( - selectedPageIndex: selectedPageIndex, - onPageChange: (int index) { - // Reset bottom nav bar visibility when switching pages - if (thunderBlocState.hideBottomBarOnScroll && !thunderBlocState.isBottomNavBarVisible) { - context.read().add(const OnBottomNavBarVisibilityChange(true)); - } - setState(() { - selectedPageIndex = index; - widget.pageController.jumpToPage(index); - }); - }, + bottomNavigationBar: Builder( + builder: (context) { + final reduceAnimations = context.read().state.reduceAnimations; + final hideBottomBarOnScroll = context.read().state.hideBottomBarOnScroll; + return AnimatedSize( + duration: Duration(milliseconds: reduceAnimations ? 0 : 200), + curve: Curves.easeInOut, + clipBehavior: Clip.hardEdge, + alignment: Alignment.topCenter, + child: SizedBox( + height: (hideBottomBarOnScroll && !context.select((cubit) => cubit.state.isBottomNavBarVisible)) ? 0 : null, + child: AnimatedOpacity( + duration: Duration(milliseconds: reduceAnimations ? 0 : 150), + curve: Curves.easeOut, + opacity: (hideBottomBarOnScroll && !context.select((cubit) => cubit.state.isBottomNavBarVisible)) ? 0.0 : 1.0, + child: CustomBottomNavigationBar( + selectedPageIndex: selectedPageIndex, + onPageChange: (int index) { + // Reset bottom nav bar visibility when switching pages + if (hideBottomBarOnScroll && !context.read().state.isBottomNavBarVisible) { + context.read().setBottomNavBarVisible(true); + } + setState(() { + selectedPageIndex = index; + widget.pageController.jumpToPage(index); + }); + }, + ), + ), ), - ), - ), + ); + }, ), body: BlocConsumer( listenWhen: (ProfileState previous, ProfileState current) { @@ -322,13 +349,14 @@ class _ThunderState extends State { if (context.mounted) context.read().add(const GetInboxEvent(reset: true)); if (context.read().state.status != FeedStatus.initial) { + final feedCubit = context.read(); context.read().add( FeedFetchedEvent( feedType: FeedType.general, - feedListType: state.siteResponse?.myUser?.localUserView.localUser.defaultListingType ?? thunderBlocState.defaultFeedListType, - postSortType: state.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? thunderBlocState.postSortTypeForInstance, + feedListType: state.siteResponse?.myUser?.localUserView.localUser.defaultListingType ?? feedCubit.state.defaultFeedListType, + postSortType: state.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? feedCubit.state.defaultPostSortType, reset: true, - showHidden: thunderBlocState.showHiddenPosts, + showHidden: feedCubit.state.showHiddenPosts, ), ); } @@ -380,70 +408,60 @@ class _ThunderState extends State { showDragHandle: true, isScrollControlled: true, builder: (context) { - bool isChangelogExpanded = false; - - return StatefulBuilder( - builder: (context, setState) { - return AnimatedSize( - alignment: Alignment.bottomCenter, - duration: const Duration(milliseconds: 100), - child: FractionallySizedBox( - heightFactor: isChangelogExpanded ? 0.9 : 0.6, - child: Container( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom, left: 26.0, right: 16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - l10n.thunderHasBeenUpdated(currentVersion), - style: theme.textTheme.titleLarge, - ), - ), - const SizedBox(height: 24.0), - Expanded( - child: FadingEdgeScrollView.fromSingleChildScrollView( - gradientFractionOnStart: 0.1, - gradientFractionOnEnd: 0.1, - child: SingleChildScrollView( - controller: _changelogScrollController, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - CommonMarkdownBody(body: changelog), - ], - ), - ), - ), - ), - const SizedBox(height: 16.0), - Row( - mainAxisAlignment: MainAxisAlignment.end, + return FractionallySizedBox( + heightFactor: 0.8, + child: Container( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom, left: 26.0, right: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + l10n.thunderHasBeenUpdated(currentVersion), + style: theme.textTheme.titleLarge, + ), + ), + const SizedBox(height: 24.0), + Expanded( + child: FadingEdgeScrollView.fromSingleChildScrollView( + gradientFractionOnStart: 0.1, + gradientFractionOnEnd: 0.1, + child: SingleChildScrollView( + controller: _changelogScrollController, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - prefs.setBool(LocalSettings.showUpdateChangelogs.name, false); - }, - child: Text(l10n.doNotShowAgain), - ), - const SizedBox(width: 6.0), - FilledButton( - onPressed: () => Navigator.of(context).pop(), - child: Text(l10n.close), - ), + CommonMarkdownBody(body: changelog), ], ), - const SizedBox(height: 24.0), - ], + ), ), ), - ), - ); - }, + const SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + prefs.setBool(LocalSettings.showUpdateChangelogs.name, false); + }, + child: Text(l10n.doNotShowAgain), + ), + const SizedBox(width: 6.0), + FilledButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(l10n.close), + ), + ], + ), + const SizedBox(height: 24.0), + ], + ), + ), ); }, ); @@ -458,13 +476,18 @@ class _ThunderState extends State { onPageChanged: (index) => setState(() => selectedPageIndex = index), physics: const NeverScrollableScrollPhysics(), children: [ - FeedPage( - useGlobalFeedBloc: true, - feedType: FeedType.general, - feedListType: state.siteResponse?.myUser?.localUserView.localUser.defaultListingType ?? thunderBlocState.defaultFeedListType, - postSortType: state.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? thunderBlocState.postSortTypeForInstance, - scaffoldStateKey: scaffoldStateKey, - showHidden: thunderBlocState.showHiddenPosts, + Builder( + builder: (context) { + final feedCubit = context.read(); + return FeedPage( + useGlobalFeedBloc: true, + feedType: FeedType.general, + feedListType: state.siteResponse?.myUser?.localUserView.localUser.defaultListingType ?? feedCubit.state.defaultFeedListType, + postSortType: state.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? feedCubit.state.defaultPostSortType, + scaffoldStateKey: scaffoldStateKey, + showHidden: feedCubit.state.showHiddenPosts, + ); + }, ), const SearchPage(), const AccountPage(), diff --git a/lib/src/app/theme/bloc/theme_bloc.dart b/lib/src/app/theme/bloc/theme_bloc.dart deleted file mode 100644 index 5948b102e..000000000 --- a/lib/src/app/theme/bloc/theme_bloc.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:bloc_concurrency/bloc_concurrency.dart'; - -import 'package:stream_transform/stream_transform.dart'; -import 'package:thunder/src/core/enums/custom_theme_type.dart'; -import 'package:thunder/src/core/enums/local_settings.dart'; -import 'package:thunder/src/core/enums/theme_type.dart'; -import 'package:thunder/src/core/singletons/preferences.dart'; - -part 'theme_event.dart'; -part 'theme_state.dart'; - -const throttleDuration = Duration(milliseconds: 300); - -EventTransformer throttleDroppable(Duration duration) { - return (events, mapper) => droppable().call(events.throttle(duration), mapper); -} - -class ThemeBloc extends Bloc { - ThemeBloc() : super(const ThemeState()) { - on( - _themeChangeEvent, - transformer: throttleDroppable(throttleDuration), - ); - } - - Future _themeChangeEvent(ThemeChangeEvent event, Emitter emit) async { - try { - emit(state.copyWith(status: ThemeStatus.loading)); - - // Fetch the ThemeType from preferences (system, light, dark) - ThemeType themeType = ThemeType.values[UserPreferences.getLocalSetting(LocalSettings.appTheme) ?? ThemeType.system.index]; - Brightness brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness; - - // Check if the user has selected to use a pure black theme, if so override the themeType to pureBlack - bool usePureBlackTheme = UserPreferences.getLocalSetting(LocalSettings.usePureBlackTheme) ?? false; - if (usePureBlackTheme && (themeType == ThemeType.dark || (themeType == ThemeType.system && brightness == Brightness.dark))) themeType = ThemeType.pureBlack; - - CustomThemeType selectedTheme = CustomThemeType.values.byName(UserPreferences.getLocalSetting(LocalSettings.appThemeAccentColor) ?? CustomThemeType.deepBlue.name); - - bool useMaterialYouTheme = UserPreferences.getLocalSetting(LocalSettings.useMaterialYouTheme) ?? false; - - // Fetch reduce animations preferences to remove overscrolling effects - bool reduceAnimations = UserPreferences.getLocalSetting(LocalSettings.reduceAnimations) ?? false; - - return emit( - state.copyWith( - status: ThemeStatus.success, - themeType: themeType, - selectedTheme: selectedTheme, - useMaterialYouTheme: useMaterialYouTheme, - reduceAnimations: reduceAnimations, - ), - ); - } catch (e) { - return emit(state.copyWith(status: ThemeStatus.failure)); - } - } -} diff --git a/lib/src/app/theme/bloc/theme_event.dart b/lib/src/app/theme/bloc/theme_event.dart deleted file mode 100644 index c6501801c..000000000 --- a/lib/src/app/theme/bloc/theme_event.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of 'theme_bloc.dart'; - -abstract class ThemeEvent extends Equatable { - const ThemeEvent(); - - @override - List get props => []; -} - -class ThemeChangeEvent extends ThemeEvent {} diff --git a/lib/src/app/theme/bloc/theme_state.dart b/lib/src/app/theme/bloc/theme_state.dart deleted file mode 100644 index 41abd9749..000000000 --- a/lib/src/app/theme/bloc/theme_state.dart +++ /dev/null @@ -1,43 +0,0 @@ -part of 'theme_bloc.dart'; - -enum ThemeStatus { initial, loading, refreshing, success, failure } - -class ThemeState extends Equatable { - const ThemeState({ - this.status = ThemeStatus.initial, - this.themeType = ThemeType.system, - this.selectedTheme = CustomThemeType.deepBlue, - this.useMaterialYouTheme = false, - this.reduceAnimations = false, - }); - - final ThemeStatus status; - - // Theming options - final ThemeType themeType; - final CustomThemeType selectedTheme; - final bool useMaterialYouTheme; - final bool reduceAnimations; - - bool get useDarkTheme => - themeType == ThemeType.system ? SchedulerBinding.instance.platformDispatcher.platformBrightness == Brightness.dark : themeType == ThemeType.dark || themeType == ThemeType.pureBlack; - - ThemeState copyWith({ - required ThemeStatus status, - ThemeType? themeType, - CustomThemeType? selectedTheme, - bool? useMaterialYouTheme, - bool? reduceAnimations, - }) { - return ThemeState( - status: status, - themeType: themeType ?? ThemeType.system, - selectedTheme: selectedTheme ?? this.selectedTheme, - useMaterialYouTheme: useMaterialYouTheme ?? false, - reduceAnimations: reduceAnimations ?? false, - ); - } - - @override - List get props => [status, themeType, selectedTheme, useDarkTheme, useMaterialYouTheme, reduceAnimations]; -} diff --git a/lib/src/app/theme/theme.dart b/lib/src/app/theme/theme.dart deleted file mode 100644 index 7c6a05499..000000000 --- a/lib/src/app/theme/theme.dart +++ /dev/null @@ -1 +0,0 @@ -export 'bloc/theme_bloc.dart'; diff --git a/lib/src/app/thunder.dart b/lib/src/app/thunder.dart index a4578a6a7..ab41d40b9 100644 --- a/lib/src/app/thunder.dart +++ b/lib/src/app/thunder.dart @@ -1,5 +1,7 @@ export 'pages/thunder_page.dart'; export 'cubits/deep_links_cubit/deep_links_cubit.dart'; export 'cubits/network_checker_cubit/network_checker_cubit.dart'; +export 'cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; +export 'cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; export 'routing/deep_link_enums.dart'; export 'bloc/thunder_bloc.dart'; diff --git a/lib/src/app/utils/navigation.dart b/lib/src/app/utils/navigation.dart index 90fdda16e..0ccfed8c5 100644 --- a/lib/src/app/utils/navigation.dart +++ b/lib/src/app/utils/navigation.dart @@ -28,6 +28,10 @@ import 'package:thunder/src/features/search/search.dart'; import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/shared/pages/loading_page.dart'; import 'package:thunder/src/shared/snackbar.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/shared/utils/swipe.dart'; import 'package:thunder/src/shared/widgets/webview.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; import 'package:thunder/src/app/pages/notifications_pages.dart'; @@ -35,7 +39,6 @@ import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/shared/utils/instance.dart'; import 'package:thunder/src/shared/utils/links.dart'; -import 'package:thunder/src/shared/utils/swipe.dart'; import 'package:thunder/src/features/post/presentation/bloc/post_bloc.dart' as post_bloc; ({String postApId, post_bloc.PostBloc postBloc})? _cachedPostBloc; @@ -57,10 +60,11 @@ Future navigateToInstancePage( final profileBloc = context.read(); final thunderBloc = context.read(); - final state = thunderBloc.state; + final gestureCubit = context.read(); + final themeCubit = context.read(); - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; ThunderSiteResponse? getSiteResponse; bool? isBlocked; @@ -146,9 +150,10 @@ Future navigateToPost( feedBloc?.add(FeedItemActionedEvent(postId: pvm!.id, postAction: PostAction.read, value: true)); } - final state = thunderBloc.state; - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; final post_bloc.PostBloc postBloc = _cachedPostBloc?.postApId == pvm!.apId ? _cachedPostBloc!.postBloc @@ -168,17 +173,24 @@ Future navigateToPost( backGestureDetectionStartOffset: !kIsWeb && Platform.isAndroid ? 45 : 0, backGestureDetectionWidth: 45, canSwipe: !kIsWeb && Platform.isIOS || enableFullScreenSwipeNavigationGesture, - canOnlySwipeFromEdge: disableFullPageSwipe(isUserLoggedIn: profileBloc.state.isLoggedIn, state: state, isPostPage: true) || !enableFullScreenSwipeNavigationGesture, + canOnlySwipeFromEdge: disableFullPageSwipe(isUserLoggedIn: profileBloc.state.isLoggedIn, state: gestureCubit.state, isPostPage: true) || !enableFullScreenSwipeNavigationGesture, builder: (_) { + final postNavigationCubit = PostNavigationCubit(); + if (highlightedCommentId != null) { + postNavigationCubit.setHighlightedCommentId(highlightedCommentId); + } + return MultiBlocProvider( providers: [ BlocProvider.value(value: profileBloc), BlocProvider.value(value: thunderBloc), BlocProvider.value(value: postBloc), + BlocProvider.value(value: postNavigationCubit), BlocProvider(create: (context) => AnonymousSubscriptionsBloc()), ], child: PostPage( initialPost: postBloc.state.post ?? pvm!, + highlightedCommentId: highlightedCommentId, onPostUpdated: (ThunderPost post) { // Manually marking the read attribute as true when navigating to post since there is a case where the API call to mark the post as read from the feed page is not completed in time feedBloc?.add(FeedItemUpdatedEvent(post: post.copyWith(read: true))); @@ -208,9 +220,10 @@ Future navigateToModlogPage( final hasFeedBloc = context.findAncestorWidgetOfExactType>(); final feedBloc = hasFeedBloc != null ? context.read() : FeedBloc(account: account); - final state = thunderBloc.state; - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; final SwipeablePageRoute route = SwipeablePageRoute( transitionDuration: isLoadingPageShown @@ -250,11 +263,12 @@ Future getPostFromComment(ThunderComment comment, Account account) Future navigateToComment(BuildContext context, ThunderComment comment) async { final profileBloc = context.read(); final thunderBloc = context.read(); + final gestureCubit = context.read(); final account = context.read().state.account; - final state = context.read().state; - final reduceAnimations = state.reduceAnimations; + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; final route = SwipeablePageRoute( transitionDuration: isLoadingPageShown @@ -264,8 +278,8 @@ Future navigateToComment(BuildContext context, ThunderComment comment) asy : null, reverseTransitionDuration: reduceAnimations ? const Duration(milliseconds: 100) : const Duration(milliseconds: 500), backGestureDetectionWidth: 45, - canSwipe: !kIsWeb && Platform.isIOS || state.enableFullScreenSwipeNavigationGesture, - canOnlySwipeFromEdge: disableFullPageSwipe(isUserLoggedIn: profileBloc.state.isLoggedIn, state: thunderBloc.state, isPostPage: true) || !state.enableFullScreenSwipeNavigationGesture, + canSwipe: !kIsWeb && Platform.isIOS || gestureCubit.state.enableFullScreenSwipeNavigationGesture, + canOnlySwipeFromEdge: disableFullPageSwipe(isUserLoggedIn: profileBloc.state.isLoggedIn, state: gestureCubit.state, isPostPage: true) || !gestureCubit.state.enableFullScreenSwipeNavigationGesture, builder: (context) { return MultiBlocProvider( providers: [ @@ -308,9 +322,10 @@ Future navigateToCreateCommentPage( final profileBloc = context.read(); final thunderBloc = context.read(); - final state = thunderBloc.state; - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; final SwipeablePageRoute route = SwipeablePageRoute( transitionDuration: isLoadingPageShown @@ -363,9 +378,10 @@ Future navigateToCreatePostPage( ProfileBloc profileBloc = context.read(); CreatePostCubit createPostCubit = CreatePostCubit(account: account); - final ThunderState thunderState = context.read().state; - final bool reduceAnimations = thunderState.reduceAnimations; - final bool enableFullScreenSwipeNavigationGesture = thunderState.enableFullScreenSwipeNavigationGesture; + final themeCubit = context.read(); + final bool reduceAnimations = themeCubit.state.reduceAnimations; + final gestureCubit = context.read(); + final bool enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; try { feedBloc = context.read(); @@ -460,7 +476,9 @@ void navigateToNotificationPage( showLoadingPage(context); final thunderBloc = context.read(); - final reduceAnimations = thunderBloc.state.reduceAnimations; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; if (accountId == null) { hideLoadingPage(context); @@ -531,8 +549,8 @@ void navigateToNotificationPage( : null, reverseTransitionDuration: reduceAnimations ? const Duration(milliseconds: 100) : const Duration(milliseconds: 500), backGestureDetectionWidth: 45, - canSwipe: !kIsWeb && Platform.isIOS || thunderBloc.state.enableFullScreenSwipeNavigationGesture, - canOnlySwipeFromEdge: !thunderBloc.state.enableFullScreenSwipeNavigationGesture, + canSwipe: !kIsWeb && Platform.isIOS || gestureCubit.state.enableFullScreenSwipeNavigationGesture, + canOnlySwipeFromEdge: !gestureCubit.state.enableFullScreenSwipeNavigationGesture, builder: (context) => MultiBlocProvider( providers: [BlocProvider.value(value: thunderBloc)], child: notificationsPage, @@ -555,9 +573,10 @@ void navigateToReportPage(BuildContext context) { final feedBloc = context.read(); final thunderBloc = context.read(); - final state = thunderBloc.state; - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; Navigator.of(context).push( SwipeablePageRoute( @@ -598,10 +617,12 @@ Future navigateToFeedPage( // Push navigation ProfileBloc profileBloc = context.read(); ThunderBloc thunderBloc = context.read(); + final gestureCubit = context.read(); + final themeCubit = context.read(); + final feedCubit = context.read(); AnonymousSubscriptionsBloc anonymousSubscriptionsBloc = context.read(); - ThunderState thunderState = thunderBloc.state; - final bool reduceAnimations = thunderState.reduceAnimations; + final bool reduceAnimations = themeCubit.state.reduceAnimations; if (feedType == FeedType.general) { return context.read().add( @@ -611,13 +632,13 @@ Future navigateToFeedPage( postSortType: postSortType ?? (profileBloc.state.siteResponse?.myUser?.localUserView.localUser.defaultSortType != null ? profileBloc.state.siteResponse!.myUser!.localUserView.localUser.defaultSortType - : thunderBloc.state.postSortTypeForInstance), + : feedCubit.state.defaultPostSortType), communityId: communityId, communityName: communityName, userId: userId, username: username, reset: true, - showHidden: thunderBloc.state.showHiddenPosts, + showHidden: feedCubit.state.showHiddenPosts, ), ); } @@ -630,8 +651,8 @@ Future navigateToFeedPage( : null, reverseTransitionDuration: reduceAnimations ? const Duration(milliseconds: 100) : const Duration(milliseconds: 500), backGestureDetectionWidth: 45, - canSwipe: !kIsWeb && Platform.isIOS || thunderState.enableFullScreenSwipeNavigationGesture, - canOnlySwipeFromEdge: disableFullPageSwipe(isUserLoggedIn: profileBloc.state.isLoggedIn, state: thunderBloc.state, isFeedPage: true) || !thunderState.enableFullScreenSwipeNavigationGesture, + canSwipe: !kIsWeb && Platform.isIOS || gestureCubit.state.enableFullScreenSwipeNavigationGesture, + canOnlySwipeFromEdge: disableFullPageSwipe(isUserLoggedIn: profileBloc.state.isLoggedIn, state: gestureCubit.state, isFeedPage: true) || !gestureCubit.state.enableFullScreenSwipeNavigationGesture, builder: (context) => MultiBlocProvider( providers: [ BlocProvider.value(value: profileBloc), @@ -644,13 +665,13 @@ Future navigateToFeedPage( postSortType: postSortType ?? (profileBloc.state.siteResponse?.myUser?.localUserView.localUser.defaultSortType != null ? profileBloc.state.siteResponse!.myUser!.localUserView.localUser.defaultSortType - : thunderBloc.state.postSortTypeForInstance), + : feedCubit.state.defaultPostSortType), communityName: communityName, communityId: communityId, userId: userId, username: username, feedListType: feedListType, - showHidden: thunderBloc.state.showHiddenPosts, + showHidden: feedCubit.state.showHiddenPosts, ), ), ), @@ -669,9 +690,10 @@ void navigateToSearchPage(BuildContext context) { final feedBloc = context.read(); final thunderBloc = context.read(); - final state = thunderBloc.state; - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; final account = context.read().state.account; @@ -698,9 +720,10 @@ void navigateToSettingPage(BuildContext context, LocalSettings setting, {LocalSe final thunderBloc = context.read(); final profileBloc = context.read(); - final state = thunderBloc.state; - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; final account = context.read().state.account; @@ -802,11 +825,10 @@ void navigateToSettingPage(BuildContext context, LocalSettings setting, {LocalSe /// Navigates to the given [url] in a webview. void navigateToWebView(BuildContext context, String url) { - final thunderBloc = context.read(); - - final state = thunderBloc.state; - final reduceAnimations = state.reduceAnimations; - final enableFullScreenSwipeNavigationGesture = state.enableFullScreenSwipeNavigationGesture; + final gestureCubit = context.read(); + final themeCubit = context.read(); + final reduceAnimations = themeCubit.state.reduceAnimations; + final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture; SwipeablePageRoute route = SwipeablePageRoute( transitionDuration: isLoadingPageShown diff --git a/lib/src/app/widgets/bottom_nav_bar.dart b/lib/src/app/widgets/bottom_nav_bar.dart index a876186b3..503208fa3 100644 --- a/lib/src/app/widgets/bottom_nav_bar.dart +++ b/lib/src/app/widgets/bottom_nav_bar.dart @@ -3,14 +3,14 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; +import 'package:thunder/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart'; import 'package:thunder/src/features/account/account.dart'; -import 'package:thunder/src/core/enums/local_settings.dart'; -import 'package:thunder/src/core/singletons/preferences.dart'; -import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/features/inbox/inbox.dart'; import 'package:thunder/src/features/search/search.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; /// A custom bottom navigation bar that enables tap/swipe gestures class CustomBottomNavigationBar extends StatefulWidget { @@ -45,7 +45,7 @@ class _CustomBottomNavigationBarState extends State { void _handleDragEnd(DragEndDetails details, BuildContext context) async { if (widget.selectedPageIndex != 0) return; - bool bottomNavBarSwipeGestures = UserPreferences.getLocalSetting(LocalSettings.sidebarBottomNavBarSwipeGesture) ?? true; + bool bottomNavBarSwipeGestures = context.read().state.bottomNavBarSwipeGestures; if (bottomNavBarSwipeGestures == false) return; double delta = _dragEndX - _dragStartX; @@ -64,7 +64,7 @@ class _CustomBottomNavigationBarState extends State { void _handleDoubleTap(BuildContext context) async { if (widget.selectedPageIndex != 0) return; - bool bottomNavBarDoubleTapGestures = UserPreferences.getLocalSetting(LocalSettings.sidebarBottomNavBarDoubleTapGesture) ?? false; + bool bottomNavBarDoubleTapGestures = context.read().state.bottomNavBarDoubleTapGestures; if (bottomNavBarDoubleTapGestures == false) return; bool isDrawerOpen = context.mounted ? Scaffold.of(context).isDrawerOpen : false; @@ -80,17 +80,18 @@ class _CustomBottomNavigationBarState extends State { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; - final state = context.watch().state; + final showNavigationLabels = context.select((bloc) => bloc.state.showNavigationLabels); + final bottomNavBarDoubleTapGestures = context.select((cubit) => cubit.state.bottomNavBarDoubleTapGestures); final inboxState = context.watch().state; return GestureDetector( onHorizontalDragStart: _handleDragStart, onHorizontalDragUpdate: _handleDragUpdate, onHorizontalDragEnd: (DragEndDetails dragEndDetails) => _handleDragEnd(dragEndDetails, context), - onDoubleTap: state.bottomNavBarDoubleTapGestures == true ? () => _handleDoubleTap(context) : null, + onDoubleTap: bottomNavBarDoubleTapGestures == true ? () => _handleDoubleTap(context) : null, child: NavigationBar( selectedIndex: widget.selectedPageIndex, - labelBehavior: state.showNavigationLabels ? NavigationDestinationLabelBehavior.alwaysShow : NavigationDestinationLabelBehavior.alwaysHide, + labelBehavior: showNavigationLabels ? NavigationDestinationLabelBehavior.alwaysShow : NavigationDestinationLabelBehavior.alwaysHide, destinations: [ NavigationDestination( icon: const Icon(Icons.dashboard_outlined), @@ -134,12 +135,12 @@ class _CustomBottomNavigationBarState extends State { ), ], onDestinationSelected: (index) { - if (state.isFabOpen) { - context.read().add(const OnFabToggle(false)); + if (context.read().state.isFeedFabOpen) { + context.read().setFeedFabOpen(false); } if (widget.selectedPageIndex == 0 && index == 0) { - context.read().add(ScrollToTopEvent()); + context.read().scrollToTop(); } if (widget.selectedPageIndex == 1 && index != 1) { diff --git a/lib/src/core/database/database.g.dart b/lib/src/core/database/database.g.dart index 7469e48e1..02544c729 100644 --- a/lib/src/core/database/database.g.dart +++ b/lib/src/core/database/database.g.dart @@ -10,101 +10,61 @@ class $AccountsTable extends Accounts with TableInfo<$AccountsTable, Account> { $AccountsTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _usernameMeta = - const VerificationMeta('username'); - @override - late final GeneratedColumn username = GeneratedColumn( - 'username', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn id = GeneratedColumn('id', aliasedName, false, + hasAutoIncrement: true, type: DriftSqlType.int, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _usernameMeta = const VerificationMeta('username'); + @override + late final GeneratedColumn username = GeneratedColumn('username', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); static const VerificationMeta _jwtMeta = const VerificationMeta('jwt'); @override - late final GeneratedColumn jwt = GeneratedColumn( - 'jwt', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _instanceMeta = - const VerificationMeta('instance'); - @override - late final GeneratedColumn instance = GeneratedColumn( - 'instance', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _anonymousMeta = - const VerificationMeta('anonymous'); - @override - late final GeneratedColumn anonymous = GeneratedColumn( - 'anonymous', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("anonymous" IN (0, 1))'), - defaultValue: const Constant(false)); + late final GeneratedColumn jwt = GeneratedColumn('jwt', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _instanceMeta = const VerificationMeta('instance'); + @override + late final GeneratedColumn instance = GeneratedColumn('instance', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _anonymousMeta = const VerificationMeta('anonymous'); + @override + late final GeneratedColumn anonymous = GeneratedColumn('anonymous', aliasedName, false, + type: DriftSqlType.bool, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("anonymous" IN (0, 1))'), defaultValue: const Constant(false)); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override - late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _listIndexMeta = - const VerificationMeta('listIndex'); + late final GeneratedColumn userId = GeneratedColumn('user_id', aliasedName, true, type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _listIndexMeta = const VerificationMeta('listIndex'); @override - late final GeneratedColumn listIndex = GeneratedColumn( - 'list_index', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(-1)); + late final GeneratedColumn listIndex = GeneratedColumn('list_index', aliasedName, false, type: DriftSqlType.int, requiredDuringInsert: false, defaultValue: const Constant(-1)); @override - late final GeneratedColumnWithTypeConverter - platform = GeneratedColumn('platform', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter( - $AccountsTable.$converterplatform); + late final GeneratedColumnWithTypeConverter platform = + GeneratedColumn('platform', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false).withConverter($AccountsTable.$converterplatform); @override - List get $columns => - [id, username, jwt, instance, anonymous, userId, listIndex, platform]; + List get $columns => [id, username, jwt, instance, anonymous, userId, listIndex, platform]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'accounts'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('username')) { - context.handle(_usernameMeta, - username.isAcceptableOrUnknown(data['username']!, _usernameMeta)); + context.handle(_usernameMeta, username.isAcceptableOrUnknown(data['username']!, _usernameMeta)); } if (data.containsKey('jwt')) { - context.handle( - _jwtMeta, jwt.isAcceptableOrUnknown(data['jwt']!, _jwtMeta)); + context.handle(_jwtMeta, jwt.isAcceptableOrUnknown(data['jwt']!, _jwtMeta)); } if (data.containsKey('instance')) { - context.handle( - _instanceMeta, - this - .instance - .isAcceptableOrUnknown(data['instance']!, _instanceMeta)); + context.handle(_instanceMeta, this.instance.isAcceptableOrUnknown(data['instance']!, _instanceMeta)); } if (data.containsKey('anonymous')) { - context.handle(_anonymousMeta, - anonymous.isAcceptableOrUnknown(data['anonymous']!, _anonymousMeta)); + context.handle(_anonymousMeta, anonymous.isAcceptableOrUnknown(data['anonymous']!, _anonymousMeta)); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('list_index')) { - context.handle(_listIndexMeta, - listIndex.isAcceptableOrUnknown(data['list_index']!, _listIndexMeta)); + context.handle(_listIndexMeta, listIndex.isAcceptableOrUnknown(data['list_index']!, _listIndexMeta)); } return context; } @@ -115,23 +75,14 @@ class $AccountsTable extends Accounts with TableInfo<$AccountsTable, Account> { Account map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return Account( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - username: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}username']), - jwt: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}jwt']), - instance: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}instance']), - anonymous: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}anonymous'])!, - userId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}user_id']), - listIndex: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}list_index'])!, - platform: $AccountsTable.$converterplatform.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}platform'])), + id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!, + username: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}username']), + jwt: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}jwt']), + instance: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}instance']), + anonymous: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}anonymous'])!, + userId: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}user_id']), + listIndex: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}list_index'])!, + platform: $AccountsTable.$converterplatform.fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}platform'])), ); } @@ -140,8 +91,7 @@ class $AccountsTable extends Accounts with TableInfo<$AccountsTable, Account> { return $AccountsTable(attachedDatabase, alias); } - static TypeConverter $converterplatform = - const ThreadiversePlatformConverter(); + static TypeConverter $converterplatform = const ThreadiversePlatformConverter(); } class Account extends DataClass implements Insertable { @@ -153,15 +103,7 @@ class Account extends DataClass implements Insertable { final int? userId; final int listIndex; final ThreadiversePlatform? platform; - const Account( - {required this.id, - this.username, - this.jwt, - this.instance, - required this.anonymous, - this.userId, - required this.listIndex, - this.platform}); + const Account({required this.id, this.username, this.jwt, this.instance, required this.anonymous, this.userId, required this.listIndex, this.platform}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -181,8 +123,7 @@ class Account extends DataClass implements Insertable { } map['list_index'] = Variable(listIndex); if (!nullToAbsent || platform != null) { - map['platform'] = - Variable($AccountsTable.$converterplatform.toSql(platform)); + map['platform'] = Variable($AccountsTable.$converterplatform.toSql(platform)); } return map; } @@ -190,25 +131,17 @@ class Account extends DataClass implements Insertable { AccountsCompanion toCompanion(bool nullToAbsent) { return AccountsCompanion( id: Value(id), - username: username == null && nullToAbsent - ? const Value.absent() - : Value(username), + username: username == null && nullToAbsent ? const Value.absent() : Value(username), jwt: jwt == null && nullToAbsent ? const Value.absent() : Value(jwt), - instance: instance == null && nullToAbsent - ? const Value.absent() - : Value(instance), + instance: instance == null && nullToAbsent ? const Value.absent() : Value(instance), anonymous: Value(anonymous), - userId: - userId == null && nullToAbsent ? const Value.absent() : Value(userId), + userId: userId == null && nullToAbsent ? const Value.absent() : Value(userId), listIndex: Value(listIndex), - platform: platform == null && nullToAbsent - ? const Value.absent() - : Value(platform), + platform: platform == null && nullToAbsent ? const Value.absent() : Value(platform), ); } - factory Account.fromJson(Map json, - {ValueSerializer? serializer}) { + factory Account.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return Account( id: serializer.fromJson(json['id']), @@ -284,8 +217,7 @@ class Account extends DataClass implements Insertable { } @override - int get hashCode => Object.hash( - id, username, jwt, instance, anonymous, userId, listIndex, platform); + int get hashCode => Object.hash(id, username, jwt, instance, anonymous, userId, listIndex, platform); @override bool operator ==(Object other) => identical(this, other) || @@ -397,8 +329,7 @@ class AccountsCompanion extends UpdateCompanion { map['list_index'] = Variable(listIndex.value); } if (platform.present) { - map['platform'] = Variable( - $AccountsTable.$converterplatform.toSql(platform.value)); + map['platform'] = Variable($AccountsTable.$converterplatform.toSql(platform.value)); } return map; } @@ -419,33 +350,21 @@ class AccountsCompanion extends UpdateCompanion { } } -class $FavoritesTable extends Favorites - with TableInfo<$FavoritesTable, Favorite> { +class $FavoritesTable extends Favorites with TableInfo<$FavoritesTable, Favorite> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $FavoritesTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _accountIdMeta = - const VerificationMeta('accountId'); - @override - late final GeneratedColumn accountId = GeneratedColumn( - 'account_id', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); - static const VerificationMeta _communityIdMeta = - const VerificationMeta('communityId'); - @override - late final GeneratedColumn communityId = GeneratedColumn( - 'community_id', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn id = GeneratedColumn('id', aliasedName, false, + hasAutoIncrement: true, type: DriftSqlType.int, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _accountIdMeta = const VerificationMeta('accountId'); + @override + late final GeneratedColumn accountId = GeneratedColumn('account_id', aliasedName, false, type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _communityIdMeta = const VerificationMeta('communityId'); + @override + late final GeneratedColumn communityId = GeneratedColumn('community_id', aliasedName, false, type: DriftSqlType.int, requiredDuringInsert: true); @override List get $columns => [id, accountId, communityId]; @override @@ -454,24 +373,19 @@ class $FavoritesTable extends Favorites String get actualTableName => $name; static const String $name = 'favorites'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('account_id')) { - context.handle(_accountIdMeta, - accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta)); + context.handle(_accountIdMeta, accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta)); } else if (isInserting) { context.missing(_accountIdMeta); } if (data.containsKey('community_id')) { - context.handle( - _communityIdMeta, - communityId.isAcceptableOrUnknown( - data['community_id']!, _communityIdMeta)); + context.handle(_communityIdMeta, communityId.isAcceptableOrUnknown(data['community_id']!, _communityIdMeta)); } else if (isInserting) { context.missing(_communityIdMeta); } @@ -484,12 +398,9 @@ class $FavoritesTable extends Favorites Favorite map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return Favorite( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - accountId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}account_id'])!, - communityId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}community_id'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!, + accountId: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}account_id'])!, + communityId: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}community_id'])!, ); } @@ -503,8 +414,7 @@ class Favorite extends DataClass implements Insertable { final int id; final int accountId; final int communityId; - const Favorite( - {required this.id, required this.accountId, required this.communityId}); + const Favorite({required this.id, required this.accountId, required this.communityId}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -522,8 +432,7 @@ class Favorite extends DataClass implements Insertable { ); } - factory Favorite.fromJson(Map json, - {ValueSerializer? serializer}) { + factory Favorite.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return Favorite( id: serializer.fromJson(json['id']), @@ -550,8 +459,7 @@ class Favorite extends DataClass implements Insertable { return Favorite( id: data.id.present ? data.id.value : this.id, accountId: data.accountId.present ? data.accountId.value : this.accountId, - communityId: - data.communityId.present ? data.communityId.value : this.communityId, + communityId: data.communityId.present ? data.communityId.value : this.communityId, ); } @@ -568,12 +476,7 @@ class Favorite extends DataClass implements Insertable { @override int get hashCode => Object.hash(id, accountId, communityId); @override - bool operator ==(Object other) => - identical(this, other) || - (other is Favorite && - other.id == this.id && - other.accountId == this.accountId && - other.communityId == this.communityId); + bool operator ==(Object other) => identical(this, other) || (other is Favorite && other.id == this.id && other.accountId == this.accountId && other.communityId == this.communityId); } class FavoritesCompanion extends UpdateCompanion { @@ -603,8 +506,7 @@ class FavoritesCompanion extends UpdateCompanion { }); } - FavoritesCompanion copyWith( - {Value? id, Value? accountId, Value? communityId}) { + FavoritesCompanion copyWith({Value? id, Value? accountId, Value? communityId}) { return FavoritesCompanion( id: id ?? this.id, accountId: accountId ?? this.accountId, @@ -638,42 +540,27 @@ class FavoritesCompanion extends UpdateCompanion { } } -class $LocalSubscriptionsTable extends LocalSubscriptions - with TableInfo<$LocalSubscriptionsTable, LocalSubscription> { +class $LocalSubscriptionsTable extends LocalSubscriptions with TableInfo<$LocalSubscriptionsTable, LocalSubscription> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $LocalSubscriptionsTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn id = GeneratedColumn('id', aliasedName, false, + hasAutoIncrement: true, type: DriftSqlType.int, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); static const VerificationMeta _nameMeta = const VerificationMeta('name'); @override - late final GeneratedColumn name = GeneratedColumn( - 'name', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn name = GeneratedColumn('name', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _titleMeta = const VerificationMeta('title'); @override - late final GeneratedColumn title = GeneratedColumn( - 'title', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _actorIdMeta = - const VerificationMeta('actorId'); + late final GeneratedColumn title = GeneratedColumn('title', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _actorIdMeta = const VerificationMeta('actorId'); @override - late final GeneratedColumn actorId = GeneratedColumn( - 'actor_id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn actorId = GeneratedColumn('actor_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _iconMeta = const VerificationMeta('icon'); @override - late final GeneratedColumn icon = GeneratedColumn( - 'icon', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn icon = GeneratedColumn('icon', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); @override List get $columns => [id, name, title, actorId, icon]; @override @@ -682,34 +569,29 @@ class $LocalSubscriptionsTable extends LocalSubscriptions String get actualTableName => $name; static const String $name = 'local_subscriptions'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('name')) { - context.handle( - _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + context.handle(_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); } else if (isInserting) { context.missing(_nameMeta); } if (data.containsKey('title')) { - context.handle( - _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + context.handle(_titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); } else if (isInserting) { context.missing(_titleMeta); } if (data.containsKey('actor_id')) { - context.handle(_actorIdMeta, - actorId.isAcceptableOrUnknown(data['actor_id']!, _actorIdMeta)); + context.handle(_actorIdMeta, actorId.isAcceptableOrUnknown(data['actor_id']!, _actorIdMeta)); } else if (isInserting) { context.missing(_actorIdMeta); } if (data.containsKey('icon')) { - context.handle( - _iconMeta, icon.isAcceptableOrUnknown(data['icon']!, _iconMeta)); + context.handle(_iconMeta, icon.isAcceptableOrUnknown(data['icon']!, _iconMeta)); } return context; } @@ -720,16 +602,11 @@ class $LocalSubscriptionsTable extends LocalSubscriptions LocalSubscription map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return LocalSubscription( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - name: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}name'])!, - title: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}title'])!, - actorId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}actor_id'])!, - icon: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}icon']), + id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}name'])!, + title: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}title'])!, + actorId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}actor_id'])!, + icon: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}icon']), ); } @@ -739,19 +616,13 @@ class $LocalSubscriptionsTable extends LocalSubscriptions } } -class LocalSubscription extends DataClass - implements Insertable { +class LocalSubscription extends DataClass implements Insertable { final int id; final String name; final String title; final String actorId; final String? icon; - const LocalSubscription( - {required this.id, - required this.name, - required this.title, - required this.actorId, - this.icon}); + const LocalSubscription({required this.id, required this.name, required this.title, required this.actorId, this.icon}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -775,8 +646,7 @@ class LocalSubscription extends DataClass ); } - factory LocalSubscription.fromJson(Map json, - {ValueSerializer? serializer}) { + factory LocalSubscription.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return LocalSubscription( id: serializer.fromJson(json['id']), @@ -798,13 +668,7 @@ class LocalSubscription extends DataClass }; } - LocalSubscription copyWith( - {int? id, - String? name, - String? title, - String? actorId, - Value icon = const Value.absent()}) => - LocalSubscription( + LocalSubscription copyWith({int? id, String? name, String? title, String? actorId, Value icon = const Value.absent()}) => LocalSubscription( id: id ?? this.id, name: name ?? this.name, title: title ?? this.title, @@ -837,13 +701,7 @@ class LocalSubscription extends DataClass int get hashCode => Object.hash(id, name, title, actorId, icon); @override bool operator ==(Object other) => - identical(this, other) || - (other is LocalSubscription && - other.id == this.id && - other.name == this.name && - other.title == this.title && - other.actorId == this.actorId && - other.icon == this.icon); + identical(this, other) || (other is LocalSubscription && other.id == this.id && other.name == this.name && other.title == this.title && other.actorId == this.actorId && other.icon == this.icon); } class LocalSubscriptionsCompanion extends UpdateCompanion { @@ -884,12 +742,7 @@ class LocalSubscriptionsCompanion extends UpdateCompanion { }); } - LocalSubscriptionsCompanion copyWith( - {Value? id, - Value? name, - Value? title, - Value? actorId, - Value? icon}) { + LocalSubscriptionsCompanion copyWith({Value? id, Value? name, Value? title, Value? actorId, Value? icon}) { return LocalSubscriptionsCompanion( id: id ?? this.id, name: name ?? this.name, @@ -933,32 +786,21 @@ class LocalSubscriptionsCompanion extends UpdateCompanion { } } -class $UserLabelsTable extends UserLabels - with TableInfo<$UserLabelsTable, UserLabel> { +class $UserLabelsTable extends UserLabels with TableInfo<$UserLabelsTable, UserLabel> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $UserLabelsTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _usernameMeta = - const VerificationMeta('username'); - @override - late final GeneratedColumn username = GeneratedColumn( - 'username', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn id = GeneratedColumn('id', aliasedName, false, + hasAutoIncrement: true, type: DriftSqlType.int, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _usernameMeta = const VerificationMeta('username'); + @override + late final GeneratedColumn username = GeneratedColumn('username', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _labelMeta = const VerificationMeta('label'); @override - late final GeneratedColumn label = GeneratedColumn( - 'label', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn label = GeneratedColumn('label', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); @override List get $columns => [id, username, label]; @override @@ -967,22 +809,19 @@ class $UserLabelsTable extends UserLabels String get actualTableName => $name; static const String $name = 'user_labels'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('username')) { - context.handle(_usernameMeta, - username.isAcceptableOrUnknown(data['username']!, _usernameMeta)); + context.handle(_usernameMeta, username.isAcceptableOrUnknown(data['username']!, _usernameMeta)); } else if (isInserting) { context.missing(_usernameMeta); } if (data.containsKey('label')) { - context.handle( - _labelMeta, label.isAcceptableOrUnknown(data['label']!, _labelMeta)); + context.handle(_labelMeta, label.isAcceptableOrUnknown(data['label']!, _labelMeta)); } else if (isInserting) { context.missing(_labelMeta); } @@ -995,12 +834,9 @@ class $UserLabelsTable extends UserLabels UserLabel map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return UserLabel( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - username: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}username'])!, - label: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}label'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!, + username: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}username'])!, + label: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}label'])!, ); } @@ -1014,8 +850,7 @@ class UserLabel extends DataClass implements Insertable { final int id; final String username; final String label; - const UserLabel( - {required this.id, required this.username, required this.label}); + const UserLabel({required this.id, required this.username, required this.label}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -1033,8 +868,7 @@ class UserLabel extends DataClass implements Insertable { ); } - factory UserLabel.fromJson(Map json, - {ValueSerializer? serializer}) { + factory UserLabel.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return UserLabel( id: serializer.fromJson(json['id']), @@ -1078,12 +912,7 @@ class UserLabel extends DataClass implements Insertable { @override int get hashCode => Object.hash(id, username, label); @override - bool operator ==(Object other) => - identical(this, other) || - (other is UserLabel && - other.id == this.id && - other.username == this.username && - other.label == this.label); + bool operator ==(Object other) => identical(this, other) || (other is UserLabel && other.id == this.id && other.username == this.username && other.label == this.label); } class UserLabelsCompanion extends UpdateCompanion { @@ -1113,8 +942,7 @@ class UserLabelsCompanion extends UpdateCompanion { }); } - UserLabelsCompanion copyWith( - {Value? id, Value? username, Value? label}) { + UserLabelsCompanion copyWith({Value? id, Value? username, Value? label}) { return UserLabelsCompanion( id: id ?? this.id, username: username ?? this.username, @@ -1155,113 +983,66 @@ class $DraftsTable extends Drafts with TableInfo<$DraftsTable, Draft> { $DraftsTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn id = GeneratedColumn('id', aliasedName, false, + hasAutoIncrement: true, type: DriftSqlType.int, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); @override late final GeneratedColumnWithTypeConverter draftType = - GeneratedColumn('draft_type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter($DraftsTable.$converterdraftType); - static const VerificationMeta _existingIdMeta = - const VerificationMeta('existingId'); - @override - late final GeneratedColumn existingId = GeneratedColumn( - 'existing_id', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _replyIdMeta = - const VerificationMeta('replyId'); - @override - late final GeneratedColumn replyId = GeneratedColumn( - 'reply_id', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); + GeneratedColumn('draft_type', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true).withConverter($DraftsTable.$converterdraftType); + static const VerificationMeta _existingIdMeta = const VerificationMeta('existingId'); + @override + late final GeneratedColumn existingId = GeneratedColumn('existing_id', aliasedName, true, type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _replyIdMeta = const VerificationMeta('replyId'); + @override + late final GeneratedColumn replyId = GeneratedColumn('reply_id', aliasedName, true, type: DriftSqlType.int, requiredDuringInsert: false); static const VerificationMeta _titleMeta = const VerificationMeta('title'); @override - late final GeneratedColumn title = GeneratedColumn( - 'title', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn title = GeneratedColumn('title', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); static const VerificationMeta _urlMeta = const VerificationMeta('url'); @override - late final GeneratedColumn url = GeneratedColumn( - 'url', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _customThumbnailMeta = - const VerificationMeta('customThumbnail'); - @override - late final GeneratedColumn customThumbnail = GeneratedColumn( - 'custom_thumbnail', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _altTextMeta = - const VerificationMeta('altText'); - @override - late final GeneratedColumn altText = GeneratedColumn( - 'alt_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn url = GeneratedColumn('url', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _customThumbnailMeta = const VerificationMeta('customThumbnail'); + @override + late final GeneratedColumn customThumbnail = GeneratedColumn('custom_thumbnail', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _altTextMeta = const VerificationMeta('altText'); + @override + late final GeneratedColumn altText = GeneratedColumn('alt_text', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); static const VerificationMeta _bodyMeta = const VerificationMeta('body'); @override - late final GeneratedColumn body = GeneratedColumn( - 'body', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - List get $columns => [ - id, - draftType, - existingId, - replyId, - title, - url, - customThumbnail, - altText, - body - ]; + late final GeneratedColumn body = GeneratedColumn('body', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); + @override + List get $columns => [id, draftType, existingId, replyId, title, url, customThumbnail, altText, body]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'drafts'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('existing_id')) { - context.handle( - _existingIdMeta, - existingId.isAcceptableOrUnknown( - data['existing_id']!, _existingIdMeta)); + context.handle(_existingIdMeta, existingId.isAcceptableOrUnknown(data['existing_id']!, _existingIdMeta)); } if (data.containsKey('reply_id')) { - context.handle(_replyIdMeta, - replyId.isAcceptableOrUnknown(data['reply_id']!, _replyIdMeta)); + context.handle(_replyIdMeta, replyId.isAcceptableOrUnknown(data['reply_id']!, _replyIdMeta)); } if (data.containsKey('title')) { - context.handle( - _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + context.handle(_titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); } if (data.containsKey('url')) { - context.handle( - _urlMeta, url.isAcceptableOrUnknown(data['url']!, _urlMeta)); + context.handle(_urlMeta, url.isAcceptableOrUnknown(data['url']!, _urlMeta)); } if (data.containsKey('custom_thumbnail')) { - context.handle( - _customThumbnailMeta, - customThumbnail.isAcceptableOrUnknown( - data['custom_thumbnail']!, _customThumbnailMeta)); + context.handle(_customThumbnailMeta, customThumbnail.isAcceptableOrUnknown(data['custom_thumbnail']!, _customThumbnailMeta)); } if (data.containsKey('alt_text')) { - context.handle(_altTextMeta, - altText.isAcceptableOrUnknown(data['alt_text']!, _altTextMeta)); + context.handle(_altTextMeta, altText.isAcceptableOrUnknown(data['alt_text']!, _altTextMeta)); } if (data.containsKey('body')) { - context.handle( - _bodyMeta, body.isAcceptableOrUnknown(data['body']!, _bodyMeta)); + context.handle(_bodyMeta, body.isAcceptableOrUnknown(data['body']!, _bodyMeta)); } return context; } @@ -1272,25 +1053,15 @@ class $DraftsTable extends Drafts with TableInfo<$DraftsTable, Draft> { Draft map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return Draft( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - draftType: $DraftsTable.$converterdraftType.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}draft_type'])!), - existingId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}existing_id']), - replyId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}reply_id']), - title: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}title']), - url: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}url']), - customThumbnail: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}custom_thumbnail']), - altText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}alt_text']), - body: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}body']), + id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!, + draftType: $DraftsTable.$converterdraftType.fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}draft_type'])!), + existingId: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}existing_id']), + replyId: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}reply_id']), + title: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}title']), + url: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}url']), + customThumbnail: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}custom_thumbnail']), + altText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}alt_text']), + body: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}body']), ); } @@ -1299,8 +1070,7 @@ class $DraftsTable extends Drafts with TableInfo<$DraftsTable, Draft> { return $DraftsTable(attachedDatabase, alias); } - static TypeConverter $converterdraftType = - const DraftTypeConverter(); + static TypeConverter $converterdraftType = const DraftTypeConverter(); } class Draft extends DataClass implements Insertable { @@ -1313,23 +1083,13 @@ class Draft extends DataClass implements Insertable { final String? customThumbnail; final String? altText; final String? body; - const Draft( - {required this.id, - required this.draftType, - this.existingId, - this.replyId, - this.title, - this.url, - this.customThumbnail, - this.altText, - this.body}); + const Draft({required this.id, required this.draftType, this.existingId, this.replyId, this.title, this.url, this.customThumbnail, this.altText, this.body}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); { - map['draft_type'] = - Variable($DraftsTable.$converterdraftType.toSql(draftType)); + map['draft_type'] = Variable($DraftsTable.$converterdraftType.toSql(draftType)); } if (!nullToAbsent || existingId != null) { map['existing_id'] = Variable(existingId); @@ -1359,27 +1119,17 @@ class Draft extends DataClass implements Insertable { return DraftsCompanion( id: Value(id), draftType: Value(draftType), - existingId: existingId == null && nullToAbsent - ? const Value.absent() - : Value(existingId), - replyId: replyId == null && nullToAbsent - ? const Value.absent() - : Value(replyId), - title: - title == null && nullToAbsent ? const Value.absent() : Value(title), + existingId: existingId == null && nullToAbsent ? const Value.absent() : Value(existingId), + replyId: replyId == null && nullToAbsent ? const Value.absent() : Value(replyId), + title: title == null && nullToAbsent ? const Value.absent() : Value(title), url: url == null && nullToAbsent ? const Value.absent() : Value(url), - customThumbnail: customThumbnail == null && nullToAbsent - ? const Value.absent() - : Value(customThumbnail), - altText: altText == null && nullToAbsent - ? const Value.absent() - : Value(altText), + customThumbnail: customThumbnail == null && nullToAbsent ? const Value.absent() : Value(customThumbnail), + altText: altText == null && nullToAbsent ? const Value.absent() : Value(altText), body: body == null && nullToAbsent ? const Value.absent() : Value(body), ); } - factory Draft.fromJson(Map json, - {ValueSerializer? serializer}) { + factory Draft.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return Draft( id: serializer.fromJson(json['id']), @@ -1426,9 +1176,7 @@ class Draft extends DataClass implements Insertable { replyId: replyId.present ? replyId.value : this.replyId, title: title.present ? title.value : this.title, url: url.present ? url.value : this.url, - customThumbnail: customThumbnail.present - ? customThumbnail.value - : this.customThumbnail, + customThumbnail: customThumbnail.present ? customThumbnail.value : this.customThumbnail, altText: altText.present ? altText.value : this.altText, body: body.present ? body.value : this.body, ); @@ -1436,14 +1184,11 @@ class Draft extends DataClass implements Insertable { return Draft( id: data.id.present ? data.id.value : this.id, draftType: data.draftType.present ? data.draftType.value : this.draftType, - existingId: - data.existingId.present ? data.existingId.value : this.existingId, + existingId: data.existingId.present ? data.existingId.value : this.existingId, replyId: data.replyId.present ? data.replyId.value : this.replyId, title: data.title.present ? data.title.value : this.title, url: data.url.present ? data.url.value : this.url, - customThumbnail: data.customThumbnail.present - ? data.customThumbnail.value - : this.customThumbnail, + customThumbnail: data.customThumbnail.present ? data.customThumbnail.value : this.customThumbnail, altText: data.altText.present ? data.altText.value : this.altText, body: data.body.present ? data.body.value : this.body, ); @@ -1466,8 +1211,7 @@ class Draft extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(id, draftType, existingId, replyId, title, - url, customThumbnail, altText, body); + int get hashCode => Object.hash(id, draftType, existingId, replyId, title, url, customThumbnail, altText, body); @override bool operator ==(Object other) => identical(this, other) || @@ -1569,8 +1313,7 @@ class DraftsCompanion extends UpdateCompanion { map['id'] = Variable(id.value); } if (draftType.present) { - map['draft_type'] = Variable( - $DraftsTable.$converterdraftType.toSql(draftType.value)); + map['draft_type'] = Variable($DraftsTable.$converterdraftType.toSql(draftType.value)); } if (existingId.present) { map['existing_id'] = Variable(existingId.value); @@ -1618,16 +1361,13 @@ abstract class _$AppDatabase extends GeneratedDatabase { $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $AccountsTable accounts = $AccountsTable(this); late final $FavoritesTable favorites = $FavoritesTable(this); - late final $LocalSubscriptionsTable localSubscriptions = - $LocalSubscriptionsTable(this); + late final $LocalSubscriptionsTable localSubscriptions = $LocalSubscriptionsTable(this); late final $UserLabelsTable userLabels = $UserLabelsTable(this); late final $DraftsTable drafts = $DraftsTable(this); @override - Iterable> get allTables => - allSchemaEntities.whereType>(); + Iterable> get allTables => allSchemaEntities.whereType>(); @override - List get allSchemaEntities => - [accounts, favorites, localSubscriptions, userLabels, drafts]; + List get allSchemaEntities => [accounts, favorites, localSubscriptions, userLabels, drafts]; } typedef $$AccountsTableCreateCompanionBuilder = AccountsCompanion Function({ @@ -1651,8 +1391,7 @@ typedef $$AccountsTableUpdateCompanionBuilder = AccountsCompanion Function({ Value platform, }); -class $$AccountsTableFilterComposer - extends Composer<_$AppDatabase, $AccountsTable> { +class $$AccountsTableFilterComposer extends Composer<_$AppDatabase, $AccountsTable> { $$AccountsTableFilterComposer({ required super.$db, required super.$table, @@ -1660,36 +1399,25 @@ class $$AccountsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get username => $composableBuilder( - column: $table.username, builder: (column) => ColumnFilters(column)); + ColumnFilters get username => $composableBuilder(column: $table.username, builder: (column) => ColumnFilters(column)); - ColumnFilters get jwt => $composableBuilder( - column: $table.jwt, builder: (column) => ColumnFilters(column)); + ColumnFilters get jwt => $composableBuilder(column: $table.jwt, builder: (column) => ColumnFilters(column)); - ColumnFilters get instance => $composableBuilder( - column: $table.instance, builder: (column) => ColumnFilters(column)); + ColumnFilters get instance => $composableBuilder(column: $table.instance, builder: (column) => ColumnFilters(column)); - ColumnFilters get anonymous => $composableBuilder( - column: $table.anonymous, builder: (column) => ColumnFilters(column)); + ColumnFilters get anonymous => $composableBuilder(column: $table.anonymous, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get listIndex => $composableBuilder( - column: $table.listIndex, builder: (column) => ColumnFilters(column)); + ColumnFilters get listIndex => $composableBuilder(column: $table.listIndex, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters - get platform => $composableBuilder( - column: $table.platform, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters get platform => + $composableBuilder(column: $table.platform, builder: (column) => ColumnWithTypeConverterFilters(column)); } -class $$AccountsTableOrderingComposer - extends Composer<_$AppDatabase, $AccountsTable> { +class $$AccountsTableOrderingComposer extends Composer<_$AppDatabase, $AccountsTable> { $$AccountsTableOrderingComposer({ required super.$db, required super.$table, @@ -1697,33 +1425,24 @@ class $$AccountsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get username => $composableBuilder( - column: $table.username, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get username => $composableBuilder(column: $table.username, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get jwt => $composableBuilder( - column: $table.jwt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get jwt => $composableBuilder(column: $table.jwt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get instance => $composableBuilder( - column: $table.instance, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get instance => $composableBuilder(column: $table.instance, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get anonymous => $composableBuilder( - column: $table.anonymous, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get anonymous => $composableBuilder(column: $table.anonymous, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get listIndex => $composableBuilder( - column: $table.listIndex, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get listIndex => $composableBuilder(column: $table.listIndex, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get platform => $composableBuilder( - column: $table.platform, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get platform => $composableBuilder(column: $table.platform, builder: (column) => ColumnOrderings(column)); } -class $$AccountsTableAnnotationComposer - extends Composer<_$AppDatabase, $AccountsTable> { +class $$AccountsTableAnnotationComposer extends Composer<_$AppDatabase, $AccountsTable> { $$AccountsTableAnnotationComposer({ required super.$db, required super.$table, @@ -1731,54 +1450,32 @@ class $$AccountsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get username => - $composableBuilder(column: $table.username, builder: (column) => column); + GeneratedColumn get username => $composableBuilder(column: $table.username, builder: (column) => column); - GeneratedColumn get jwt => - $composableBuilder(column: $table.jwt, builder: (column) => column); + GeneratedColumn get jwt => $composableBuilder(column: $table.jwt, builder: (column) => column); - GeneratedColumn get instance => - $composableBuilder(column: $table.instance, builder: (column) => column); + GeneratedColumn get instance => $composableBuilder(column: $table.instance, builder: (column) => column); - GeneratedColumn get anonymous => - $composableBuilder(column: $table.anonymous, builder: (column) => column); + GeneratedColumn get anonymous => $composableBuilder(column: $table.anonymous, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get listIndex => - $composableBuilder(column: $table.listIndex, builder: (column) => column); + GeneratedColumn get listIndex => $composableBuilder(column: $table.listIndex, builder: (column) => column); - GeneratedColumnWithTypeConverter - get platform => $composableBuilder( - column: $table.platform, builder: (column) => column); + GeneratedColumnWithTypeConverter get platform => $composableBuilder(column: $table.platform, builder: (column) => column); } -class $$AccountsTableTableManager extends RootTableManager< - _$AppDatabase, - $AccountsTable, - Account, - $$AccountsTableFilterComposer, - $$AccountsTableOrderingComposer, - $$AccountsTableAnnotationComposer, - $$AccountsTableCreateCompanionBuilder, - $$AccountsTableUpdateCompanionBuilder, - (Account, BaseReferences<_$AppDatabase, $AccountsTable, Account>), - Account, - PrefetchHooks Function()> { +class $$AccountsTableTableManager extends RootTableManager<_$AppDatabase, $AccountsTable, Account, $$AccountsTableFilterComposer, $$AccountsTableOrderingComposer, $$AccountsTableAnnotationComposer, + $$AccountsTableCreateCompanionBuilder, $$AccountsTableUpdateCompanionBuilder, (Account, BaseReferences<_$AppDatabase, $AccountsTable, Account>), Account, PrefetchHooks Function()> { $$AccountsTableTableManager(_$AppDatabase db, $AccountsTable table) : super(TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$AccountsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$AccountsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$AccountsTableAnnotationComposer($db: db, $table: table), + createFilteringComposer: () => $$AccountsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$AccountsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$AccountsTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), Value username = const Value.absent(), @@ -1819,9 +1516,7 @@ class $$AccountsTableTableManager extends RootTableManager< listIndex: listIndex, platform: platform, ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, )); } @@ -1849,8 +1544,7 @@ typedef $$FavoritesTableUpdateCompanionBuilder = FavoritesCompanion Function({ Value communityId, }); -class $$FavoritesTableFilterComposer - extends Composer<_$AppDatabase, $FavoritesTable> { +class $$FavoritesTableFilterComposer extends Composer<_$AppDatabase, $FavoritesTable> { $$FavoritesTableFilterComposer({ required super.$db, required super.$table, @@ -1858,18 +1552,14 @@ class $$FavoritesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get accountId => $composableBuilder( - column: $table.accountId, builder: (column) => ColumnFilters(column)); + ColumnFilters get accountId => $composableBuilder(column: $table.accountId, builder: (column) => ColumnFilters(column)); - ColumnFilters get communityId => $composableBuilder( - column: $table.communityId, builder: (column) => ColumnFilters(column)); + ColumnFilters get communityId => $composableBuilder(column: $table.communityId, builder: (column) => ColumnFilters(column)); } -class $$FavoritesTableOrderingComposer - extends Composer<_$AppDatabase, $FavoritesTable> { +class $$FavoritesTableOrderingComposer extends Composer<_$AppDatabase, $FavoritesTable> { $$FavoritesTableOrderingComposer({ required super.$db, required super.$table, @@ -1877,18 +1567,14 @@ class $$FavoritesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get accountId => $composableBuilder( - column: $table.accountId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get accountId => $composableBuilder(column: $table.accountId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get communityId => $composableBuilder( - column: $table.communityId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get communityId => $composableBuilder(column: $table.communityId, builder: (column) => ColumnOrderings(column)); } -class $$FavoritesTableAnnotationComposer - extends Composer<_$AppDatabase, $FavoritesTable> { +class $$FavoritesTableAnnotationComposer extends Composer<_$AppDatabase, $FavoritesTable> { $$FavoritesTableAnnotationComposer({ required super.$db, required super.$table, @@ -1896,14 +1582,11 @@ class $$FavoritesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get accountId => - $composableBuilder(column: $table.accountId, builder: (column) => column); + GeneratedColumn get accountId => $composableBuilder(column: $table.accountId, builder: (column) => column); - GeneratedColumn get communityId => $composableBuilder( - column: $table.communityId, builder: (column) => column); + GeneratedColumn get communityId => $composableBuilder(column: $table.communityId, builder: (column) => column); } class $$FavoritesTableTableManager extends RootTableManager< @@ -1922,12 +1605,9 @@ class $$FavoritesTableTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$FavoritesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$FavoritesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$FavoritesTableAnnotationComposer($db: db, $table: table), + createFilteringComposer: () => $$FavoritesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$FavoritesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$FavoritesTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), Value accountId = const Value.absent(), @@ -1948,9 +1628,7 @@ class $$FavoritesTableTableManager extends RootTableManager< accountId: accountId, communityId: communityId, ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, )); } @@ -1967,16 +1645,14 @@ typedef $$FavoritesTableProcessedTableManager = ProcessedTableManager< (Favorite, BaseReferences<_$AppDatabase, $FavoritesTable, Favorite>), Favorite, PrefetchHooks Function()>; -typedef $$LocalSubscriptionsTableCreateCompanionBuilder - = LocalSubscriptionsCompanion Function({ +typedef $$LocalSubscriptionsTableCreateCompanionBuilder = LocalSubscriptionsCompanion Function({ Value id, required String name, required String title, required String actorId, Value icon, }); -typedef $$LocalSubscriptionsTableUpdateCompanionBuilder - = LocalSubscriptionsCompanion Function({ +typedef $$LocalSubscriptionsTableUpdateCompanionBuilder = LocalSubscriptionsCompanion Function({ Value id, Value name, Value title, @@ -1984,8 +1660,7 @@ typedef $$LocalSubscriptionsTableUpdateCompanionBuilder Value icon, }); -class $$LocalSubscriptionsTableFilterComposer - extends Composer<_$AppDatabase, $LocalSubscriptionsTable> { +class $$LocalSubscriptionsTableFilterComposer extends Composer<_$AppDatabase, $LocalSubscriptionsTable> { $$LocalSubscriptionsTableFilterComposer({ required super.$db, required super.$table, @@ -1993,24 +1668,18 @@ class $$LocalSubscriptionsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get name => $composableBuilder( - column: $table.name, builder: (column) => ColumnFilters(column)); + ColumnFilters get name => $composableBuilder(column: $table.name, builder: (column) => ColumnFilters(column)); - ColumnFilters get title => $composableBuilder( - column: $table.title, builder: (column) => ColumnFilters(column)); + ColumnFilters get title => $composableBuilder(column: $table.title, builder: (column) => ColumnFilters(column)); - ColumnFilters get actorId => $composableBuilder( - column: $table.actorId, builder: (column) => ColumnFilters(column)); + ColumnFilters get actorId => $composableBuilder(column: $table.actorId, builder: (column) => ColumnFilters(column)); - ColumnFilters get icon => $composableBuilder( - column: $table.icon, builder: (column) => ColumnFilters(column)); + ColumnFilters get icon => $composableBuilder(column: $table.icon, builder: (column) => ColumnFilters(column)); } -class $$LocalSubscriptionsTableOrderingComposer - extends Composer<_$AppDatabase, $LocalSubscriptionsTable> { +class $$LocalSubscriptionsTableOrderingComposer extends Composer<_$AppDatabase, $LocalSubscriptionsTable> { $$LocalSubscriptionsTableOrderingComposer({ required super.$db, required super.$table, @@ -2018,24 +1687,18 @@ class $$LocalSubscriptionsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get name => $composableBuilder( - column: $table.name, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get name => $composableBuilder(column: $table.name, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get title => $composableBuilder( - column: $table.title, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get title => $composableBuilder(column: $table.title, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get actorId => $composableBuilder( - column: $table.actorId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get actorId => $composableBuilder(column: $table.actorId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get icon => $composableBuilder( - column: $table.icon, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get icon => $composableBuilder(column: $table.icon, builder: (column) => ColumnOrderings(column)); } -class $$LocalSubscriptionsTableAnnotationComposer - extends Composer<_$AppDatabase, $LocalSubscriptionsTable> { +class $$LocalSubscriptionsTableAnnotationComposer extends Composer<_$AppDatabase, $LocalSubscriptionsTable> { $$LocalSubscriptionsTableAnnotationComposer({ required super.$db, required super.$table, @@ -2043,20 +1706,15 @@ class $$LocalSubscriptionsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get name => - $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - GeneratedColumn get title => - $composableBuilder(column: $table.title, builder: (column) => column); + GeneratedColumn get title => $composableBuilder(column: $table.title, builder: (column) => column); - GeneratedColumn get actorId => - $composableBuilder(column: $table.actorId, builder: (column) => column); + GeneratedColumn get actorId => $composableBuilder(column: $table.actorId, builder: (column) => column); - GeneratedColumn get icon => - $composableBuilder(column: $table.icon, builder: (column) => column); + GeneratedColumn get icon => $composableBuilder(column: $table.icon, builder: (column) => column); } class $$LocalSubscriptionsTableTableManager extends RootTableManager< @@ -2068,24 +1726,16 @@ class $$LocalSubscriptionsTableTableManager extends RootTableManager< $$LocalSubscriptionsTableAnnotationComposer, $$LocalSubscriptionsTableCreateCompanionBuilder, $$LocalSubscriptionsTableUpdateCompanionBuilder, - ( - LocalSubscription, - BaseReferences<_$AppDatabase, $LocalSubscriptionsTable, LocalSubscription> - ), + (LocalSubscription, BaseReferences<_$AppDatabase, $LocalSubscriptionsTable, LocalSubscription>), LocalSubscription, PrefetchHooks Function()> { - $$LocalSubscriptionsTableTableManager( - _$AppDatabase db, $LocalSubscriptionsTable table) + $$LocalSubscriptionsTableTableManager(_$AppDatabase db, $LocalSubscriptionsTable table) : super(TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$LocalSubscriptionsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$LocalSubscriptionsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$LocalSubscriptionsTableAnnotationComposer( - $db: db, $table: table), + createFilteringComposer: () => $$LocalSubscriptionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$LocalSubscriptionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$LocalSubscriptionsTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), Value name = const Value.absent(), @@ -2114,9 +1764,7 @@ class $$LocalSubscriptionsTableTableManager extends RootTableManager< actorId: actorId, icon: icon, ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, )); } @@ -2130,10 +1778,7 @@ typedef $$LocalSubscriptionsTableProcessedTableManager = ProcessedTableManager< $$LocalSubscriptionsTableAnnotationComposer, $$LocalSubscriptionsTableCreateCompanionBuilder, $$LocalSubscriptionsTableUpdateCompanionBuilder, - ( - LocalSubscription, - BaseReferences<_$AppDatabase, $LocalSubscriptionsTable, LocalSubscription> - ), + (LocalSubscription, BaseReferences<_$AppDatabase, $LocalSubscriptionsTable, LocalSubscription>), LocalSubscription, PrefetchHooks Function()>; typedef $$UserLabelsTableCreateCompanionBuilder = UserLabelsCompanion Function({ @@ -2147,8 +1792,7 @@ typedef $$UserLabelsTableUpdateCompanionBuilder = UserLabelsCompanion Function({ Value label, }); -class $$UserLabelsTableFilterComposer - extends Composer<_$AppDatabase, $UserLabelsTable> { +class $$UserLabelsTableFilterComposer extends Composer<_$AppDatabase, $UserLabelsTable> { $$UserLabelsTableFilterComposer({ required super.$db, required super.$table, @@ -2156,18 +1800,14 @@ class $$UserLabelsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get username => $composableBuilder( - column: $table.username, builder: (column) => ColumnFilters(column)); + ColumnFilters get username => $composableBuilder(column: $table.username, builder: (column) => ColumnFilters(column)); - ColumnFilters get label => $composableBuilder( - column: $table.label, builder: (column) => ColumnFilters(column)); + ColumnFilters get label => $composableBuilder(column: $table.label, builder: (column) => ColumnFilters(column)); } -class $$UserLabelsTableOrderingComposer - extends Composer<_$AppDatabase, $UserLabelsTable> { +class $$UserLabelsTableOrderingComposer extends Composer<_$AppDatabase, $UserLabelsTable> { $$UserLabelsTableOrderingComposer({ required super.$db, required super.$table, @@ -2175,18 +1815,14 @@ class $$UserLabelsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get username => $composableBuilder( - column: $table.username, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get username => $composableBuilder(column: $table.username, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get label => $composableBuilder( - column: $table.label, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get label => $composableBuilder(column: $table.label, builder: (column) => ColumnOrderings(column)); } -class $$UserLabelsTableAnnotationComposer - extends Composer<_$AppDatabase, $UserLabelsTable> { +class $$UserLabelsTableAnnotationComposer extends Composer<_$AppDatabase, $UserLabelsTable> { $$UserLabelsTableAnnotationComposer({ required super.$db, required super.$table, @@ -2194,14 +1830,11 @@ class $$UserLabelsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get username => - $composableBuilder(column: $table.username, builder: (column) => column); + GeneratedColumn get username => $composableBuilder(column: $table.username, builder: (column) => column); - GeneratedColumn get label => - $composableBuilder(column: $table.label, builder: (column) => column); + GeneratedColumn get label => $composableBuilder(column: $table.label, builder: (column) => column); } class $$UserLabelsTableTableManager extends RootTableManager< @@ -2220,12 +1853,9 @@ class $$UserLabelsTableTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$UserLabelsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$UserLabelsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$UserLabelsTableAnnotationComposer($db: db, $table: table), + createFilteringComposer: () => $$UserLabelsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$UserLabelsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$UserLabelsTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), Value username = const Value.absent(), @@ -2246,9 +1876,7 @@ class $$UserLabelsTableTableManager extends RootTableManager< username: username, label: label, ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, )); } @@ -2288,8 +1916,7 @@ typedef $$DraftsTableUpdateCompanionBuilder = DraftsCompanion Function({ Value body, }); -class $$DraftsTableFilterComposer - extends Composer<_$AppDatabase, $DraftsTable> { +class $$DraftsTableFilterComposer extends Composer<_$AppDatabase, $DraftsTable> { $$DraftsTableFilterComposer({ required super.$db, required super.$table, @@ -2297,39 +1924,26 @@ class $$DraftsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters get draftType => - $composableBuilder( - column: $table.draftType, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters get draftType => $composableBuilder(column: $table.draftType, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get existingId => $composableBuilder( - column: $table.existingId, builder: (column) => ColumnFilters(column)); + ColumnFilters get existingId => $composableBuilder(column: $table.existingId, builder: (column) => ColumnFilters(column)); - ColumnFilters get replyId => $composableBuilder( - column: $table.replyId, builder: (column) => ColumnFilters(column)); + ColumnFilters get replyId => $composableBuilder(column: $table.replyId, builder: (column) => ColumnFilters(column)); - ColumnFilters get title => $composableBuilder( - column: $table.title, builder: (column) => ColumnFilters(column)); + ColumnFilters get title => $composableBuilder(column: $table.title, builder: (column) => ColumnFilters(column)); - ColumnFilters get url => $composableBuilder( - column: $table.url, builder: (column) => ColumnFilters(column)); + ColumnFilters get url => $composableBuilder(column: $table.url, builder: (column) => ColumnFilters(column)); - ColumnFilters get customThumbnail => $composableBuilder( - column: $table.customThumbnail, - builder: (column) => ColumnFilters(column)); + ColumnFilters get customThumbnail => $composableBuilder(column: $table.customThumbnail, builder: (column) => ColumnFilters(column)); - ColumnFilters get altText => $composableBuilder( - column: $table.altText, builder: (column) => ColumnFilters(column)); + ColumnFilters get altText => $composableBuilder(column: $table.altText, builder: (column) => ColumnFilters(column)); - ColumnFilters get body => $composableBuilder( - column: $table.body, builder: (column) => ColumnFilters(column)); + ColumnFilters get body => $composableBuilder(column: $table.body, builder: (column) => ColumnFilters(column)); } -class $$DraftsTableOrderingComposer - extends Composer<_$AppDatabase, $DraftsTable> { +class $$DraftsTableOrderingComposer extends Composer<_$AppDatabase, $DraftsTable> { $$DraftsTableOrderingComposer({ required super.$db, required super.$table, @@ -2337,37 +1951,26 @@ class $$DraftsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get draftType => $composableBuilder( - column: $table.draftType, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get draftType => $composableBuilder(column: $table.draftType, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get existingId => $composableBuilder( - column: $table.existingId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get existingId => $composableBuilder(column: $table.existingId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get replyId => $composableBuilder( - column: $table.replyId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get replyId => $composableBuilder(column: $table.replyId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get title => $composableBuilder( - column: $table.title, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get title => $composableBuilder(column: $table.title, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get url => $composableBuilder( - column: $table.url, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get url => $composableBuilder(column: $table.url, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get customThumbnail => $composableBuilder( - column: $table.customThumbnail, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get customThumbnail => $composableBuilder(column: $table.customThumbnail, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get altText => $composableBuilder( - column: $table.altText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get altText => $composableBuilder(column: $table.altText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get body => $composableBuilder( - column: $table.body, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get body => $composableBuilder(column: $table.body, builder: (column) => ColumnOrderings(column)); } -class $$DraftsTableAnnotationComposer - extends Composer<_$AppDatabase, $DraftsTable> { +class $$DraftsTableAnnotationComposer extends Composer<_$AppDatabase, $DraftsTable> { $$DraftsTableAnnotationComposer({ required super.$db, required super.$table, @@ -2375,56 +1978,34 @@ class $$DraftsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumnWithTypeConverter get draftType => - $composableBuilder(column: $table.draftType, builder: (column) => column); + GeneratedColumnWithTypeConverter get draftType => $composableBuilder(column: $table.draftType, builder: (column) => column); - GeneratedColumn get existingId => $composableBuilder( - column: $table.existingId, builder: (column) => column); + GeneratedColumn get existingId => $composableBuilder(column: $table.existingId, builder: (column) => column); - GeneratedColumn get replyId => - $composableBuilder(column: $table.replyId, builder: (column) => column); + GeneratedColumn get replyId => $composableBuilder(column: $table.replyId, builder: (column) => column); - GeneratedColumn get title => - $composableBuilder(column: $table.title, builder: (column) => column); + GeneratedColumn get title => $composableBuilder(column: $table.title, builder: (column) => column); - GeneratedColumn get url => - $composableBuilder(column: $table.url, builder: (column) => column); + GeneratedColumn get url => $composableBuilder(column: $table.url, builder: (column) => column); - GeneratedColumn get customThumbnail => $composableBuilder( - column: $table.customThumbnail, builder: (column) => column); + GeneratedColumn get customThumbnail => $composableBuilder(column: $table.customThumbnail, builder: (column) => column); - GeneratedColumn get altText => - $composableBuilder(column: $table.altText, builder: (column) => column); + GeneratedColumn get altText => $composableBuilder(column: $table.altText, builder: (column) => column); - GeneratedColumn get body => - $composableBuilder(column: $table.body, builder: (column) => column); + GeneratedColumn get body => $composableBuilder(column: $table.body, builder: (column) => column); } -class $$DraftsTableTableManager extends RootTableManager< - _$AppDatabase, - $DraftsTable, - Draft, - $$DraftsTableFilterComposer, - $$DraftsTableOrderingComposer, - $$DraftsTableAnnotationComposer, - $$DraftsTableCreateCompanionBuilder, - $$DraftsTableUpdateCompanionBuilder, - (Draft, BaseReferences<_$AppDatabase, $DraftsTable, Draft>), - Draft, - PrefetchHooks Function()> { +class $$DraftsTableTableManager extends RootTableManager<_$AppDatabase, $DraftsTable, Draft, $$DraftsTableFilterComposer, $$DraftsTableOrderingComposer, $$DraftsTableAnnotationComposer, + $$DraftsTableCreateCompanionBuilder, $$DraftsTableUpdateCompanionBuilder, (Draft, BaseReferences<_$AppDatabase, $DraftsTable, Draft>), Draft, PrefetchHooks Function()> { $$DraftsTableTableManager(_$AppDatabase db, $DraftsTable table) : super(TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$DraftsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$DraftsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$DraftsTableAnnotationComposer($db: db, $table: table), + createFilteringComposer: () => $$DraftsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$DraftsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$DraftsTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), Value draftType = const Value.absent(), @@ -2469,37 +2050,20 @@ class $$DraftsTableTableManager extends RootTableManager< altText: altText, body: body, ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, )); } -typedef $$DraftsTableProcessedTableManager = ProcessedTableManager< - _$AppDatabase, - $DraftsTable, - Draft, - $$DraftsTableFilterComposer, - $$DraftsTableOrderingComposer, - $$DraftsTableAnnotationComposer, - $$DraftsTableCreateCompanionBuilder, - $$DraftsTableUpdateCompanionBuilder, - (Draft, BaseReferences<_$AppDatabase, $DraftsTable, Draft>), - Draft, - PrefetchHooks Function()>; +typedef $$DraftsTableProcessedTableManager = ProcessedTableManager<_$AppDatabase, $DraftsTable, Draft, $$DraftsTableFilterComposer, $$DraftsTableOrderingComposer, $$DraftsTableAnnotationComposer, + $$DraftsTableCreateCompanionBuilder, $$DraftsTableUpdateCompanionBuilder, (Draft, BaseReferences<_$AppDatabase, $DraftsTable, Draft>), Draft, PrefetchHooks Function()>; class $AppDatabaseManager { final _$AppDatabase _db; $AppDatabaseManager(this._db); - $$AccountsTableTableManager get accounts => - $$AccountsTableTableManager(_db, _db.accounts); - $$FavoritesTableTableManager get favorites => - $$FavoritesTableTableManager(_db, _db.favorites); - $$LocalSubscriptionsTableTableManager get localSubscriptions => - $$LocalSubscriptionsTableTableManager(_db, _db.localSubscriptions); - $$UserLabelsTableTableManager get userLabels => - $$UserLabelsTableTableManager(_db, _db.userLabels); - $$DraftsTableTableManager get drafts => - $$DraftsTableTableManager(_db, _db.drafts); + $$AccountsTableTableManager get accounts => $$AccountsTableTableManager(_db, _db.accounts); + $$FavoritesTableTableManager get favorites => $$FavoritesTableTableManager(_db, _db.favorites); + $$LocalSubscriptionsTableTableManager get localSubscriptions => $$LocalSubscriptionsTableTableManager(_db, _db.localSubscriptions); + $$UserLabelsTableTableManager get userLabels => $$UserLabelsTableTableManager(_db, _db.userLabels); + $$DraftsTableTableManager get drafts => $$DraftsTableTableManager(_db, _db.drafts); } diff --git a/lib/src/core/database/database.steps.dart b/lib/src/core/database/database.steps.dart index 7ac9afeff..062430158 100644 --- a/lib/src/core/database/database.steps.dart +++ b/lib/src/core/database/database.steps.dart @@ -78,105 +78,56 @@ final class Schema2 extends i0.VersionedSchema { class Shape0 extends i0.VersionedTable { Shape0({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get username => - columnsByName['username']! as i1.GeneratedColumn; - i1.GeneratedColumn get jwt => - columnsByName['jwt']! as i1.GeneratedColumn; - i1.GeneratedColumn get instance => - columnsByName['instance']! as i1.GeneratedColumn; - i1.GeneratedColumn get anonymous => - columnsByName['anonymous']! as i1.GeneratedColumn; - i1.GeneratedColumn get userId => - columnsByName['user_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get jwt => columnsByName['jwt']! as i1.GeneratedColumn; + i1.GeneratedColumn get instance => columnsByName['instance']! as i1.GeneratedColumn; + i1.GeneratedColumn get anonymous => columnsByName['anonymous']! as i1.GeneratedColumn; + i1.GeneratedColumn get userId => columnsByName['user_id']! as i1.GeneratedColumn; } i1.GeneratedColumn _column_0(String aliasedName) => - i1.GeneratedColumn('id', aliasedName, false, - hasAutoIncrement: true, - type: i1.DriftSqlType.int, - defaultConstraints: - i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); -i1.GeneratedColumn _column_1(String aliasedName) => - i1.GeneratedColumn('username', aliasedName, true, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_2(String aliasedName) => - i1.GeneratedColumn('jwt', aliasedName, true, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_3(String aliasedName) => - i1.GeneratedColumn('instance', aliasedName, true, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_4(String aliasedName) => - i1.GeneratedColumn('anonymous', aliasedName, false, - type: i1.DriftSqlType.bool, - defaultConstraints: i1.GeneratedColumn.constraintIsAlways( - 'CHECK ("anonymous" IN (0, 1))'), - defaultValue: const CustomExpression('0')); -i1.GeneratedColumn _column_5(String aliasedName) => - i1.GeneratedColumn('user_id', aliasedName, true, - type: i1.DriftSqlType.int); + i1.GeneratedColumn('id', aliasedName, false, hasAutoIncrement: true, type: i1.DriftSqlType.int, defaultConstraints: i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); +i1.GeneratedColumn _column_1(String aliasedName) => i1.GeneratedColumn('username', aliasedName, true, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_2(String aliasedName) => i1.GeneratedColumn('jwt', aliasedName, true, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_3(String aliasedName) => i1.GeneratedColumn('instance', aliasedName, true, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_4(String aliasedName) => i1.GeneratedColumn('anonymous', aliasedName, false, + type: i1.DriftSqlType.bool, defaultConstraints: i1.GeneratedColumn.constraintIsAlways('CHECK ("anonymous" IN (0, 1))'), defaultValue: const CustomExpression('0')); +i1.GeneratedColumn _column_5(String aliasedName) => i1.GeneratedColumn('user_id', aliasedName, true, type: i1.DriftSqlType.int); class Shape1 extends i0.VersionedTable { Shape1({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get accountId => - columnsByName['account_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get communityId => - columnsByName['community_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get accountId => columnsByName['account_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get communityId => columnsByName['community_id']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_6(String aliasedName) => - i1.GeneratedColumn('account_id', aliasedName, false, - type: i1.DriftSqlType.int); -i1.GeneratedColumn _column_7(String aliasedName) => - i1.GeneratedColumn('community_id', aliasedName, false, - type: i1.DriftSqlType.int); +i1.GeneratedColumn _column_6(String aliasedName) => i1.GeneratedColumn('account_id', aliasedName, false, type: i1.DriftSqlType.int); +i1.GeneratedColumn _column_7(String aliasedName) => i1.GeneratedColumn('community_id', aliasedName, false, type: i1.DriftSqlType.int); class Shape2 extends i0.VersionedTable { Shape2({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get name => - columnsByName['name']! as i1.GeneratedColumn; - i1.GeneratedColumn get title => - columnsByName['title']! as i1.GeneratedColumn; - i1.GeneratedColumn get actorId => - columnsByName['actor_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get icon => - columnsByName['icon']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get title => columnsByName['title']! as i1.GeneratedColumn; + i1.GeneratedColumn get actorId => columnsByName['actor_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get icon => columnsByName['icon']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_8(String aliasedName) => - i1.GeneratedColumn('name', aliasedName, false, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_9(String aliasedName) => - i1.GeneratedColumn('title', aliasedName, false, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_10(String aliasedName) => - i1.GeneratedColumn('actor_id', aliasedName, false, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_11(String aliasedName) => - i1.GeneratedColumn('icon', aliasedName, true, - type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_8(String aliasedName) => i1.GeneratedColumn('name', aliasedName, false, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_9(String aliasedName) => i1.GeneratedColumn('title', aliasedName, false, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_10(String aliasedName) => i1.GeneratedColumn('actor_id', aliasedName, false, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_11(String aliasedName) => i1.GeneratedColumn('icon', aliasedName, true, type: i1.DriftSqlType.string); class Shape3 extends i0.VersionedTable { Shape3({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get username => - columnsByName['username']! as i1.GeneratedColumn; - i1.GeneratedColumn get label => - columnsByName['label']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get label => columnsByName['label']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_12(String aliasedName) => - i1.GeneratedColumn('username', aliasedName, false, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_13(String aliasedName) => - i1.GeneratedColumn('label', aliasedName, false, - type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_12(String aliasedName) => i1.GeneratedColumn('username', aliasedName, false, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_13(String aliasedName) => i1.GeneratedColumn('label', aliasedName, false, type: i1.DriftSqlType.string); final class Schema3 extends i0.VersionedSchema { Schema3({required super.database}) : super(version: 3); @@ -271,40 +222,21 @@ final class Schema3 extends i0.VersionedSchema { class Shape4 extends i0.VersionedTable { Shape4({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get draftType => - columnsByName['draft_type']! as i1.GeneratedColumn; - i1.GeneratedColumn get existingId => - columnsByName['existing_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get replyId => - columnsByName['reply_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get title => - columnsByName['title']! as i1.GeneratedColumn; - i1.GeneratedColumn get url => - columnsByName['url']! as i1.GeneratedColumn; - i1.GeneratedColumn get body => - columnsByName['body']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get draftType => columnsByName['draft_type']! as i1.GeneratedColumn; + i1.GeneratedColumn get existingId => columnsByName['existing_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get replyId => columnsByName['reply_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get title => columnsByName['title']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get body => columnsByName['body']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_14(String aliasedName) => - i1.GeneratedColumn('draft_type', aliasedName, false, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_15(String aliasedName) => - i1.GeneratedColumn('existing_id', aliasedName, true, - type: i1.DriftSqlType.int); -i1.GeneratedColumn _column_16(String aliasedName) => - i1.GeneratedColumn('reply_id', aliasedName, true, - type: i1.DriftSqlType.int); -i1.GeneratedColumn _column_17(String aliasedName) => - i1.GeneratedColumn('title', aliasedName, true, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_18(String aliasedName) => - i1.GeneratedColumn('url', aliasedName, true, - type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_19(String aliasedName) => - i1.GeneratedColumn('body', aliasedName, true, - type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_14(String aliasedName) => i1.GeneratedColumn('draft_type', aliasedName, false, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_15(String aliasedName) => i1.GeneratedColumn('existing_id', aliasedName, true, type: i1.DriftSqlType.int); +i1.GeneratedColumn _column_16(String aliasedName) => i1.GeneratedColumn('reply_id', aliasedName, true, type: i1.DriftSqlType.int); +i1.GeneratedColumn _column_17(String aliasedName) => i1.GeneratedColumn('title', aliasedName, true, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_18(String aliasedName) => i1.GeneratedColumn('url', aliasedName, true, type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_19(String aliasedName) => i1.GeneratedColumn('body', aliasedName, true, type: i1.DriftSqlType.string); final class Schema4 extends i0.VersionedSchema { Schema4({required super.database}) : super(version: 4); @@ -400,27 +332,17 @@ final class Schema4 extends i0.VersionedSchema { class Shape5 extends i0.VersionedTable { Shape5({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get draftType => - columnsByName['draft_type']! as i1.GeneratedColumn; - i1.GeneratedColumn get existingId => - columnsByName['existing_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get replyId => - columnsByName['reply_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get title => - columnsByName['title']! as i1.GeneratedColumn; - i1.GeneratedColumn get url => - columnsByName['url']! as i1.GeneratedColumn; - i1.GeneratedColumn get customThumbnail => - columnsByName['custom_thumbnail']! as i1.GeneratedColumn; - i1.GeneratedColumn get body => - columnsByName['body']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get draftType => columnsByName['draft_type']! as i1.GeneratedColumn; + i1.GeneratedColumn get existingId => columnsByName['existing_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get replyId => columnsByName['reply_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get title => columnsByName['title']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get customThumbnail => columnsByName['custom_thumbnail']! as i1.GeneratedColumn; + i1.GeneratedColumn get body => columnsByName['body']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_20(String aliasedName) => - i1.GeneratedColumn('custom_thumbnail', aliasedName, true, - type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_20(String aliasedName) => i1.GeneratedColumn('custom_thumbnail', aliasedName, true, type: i1.DriftSqlType.string); final class Schema5 extends i0.VersionedSchema { Schema5({required super.database}) : super(version: 5); @@ -517,25 +439,16 @@ final class Schema5 extends i0.VersionedSchema { class Shape6 extends i0.VersionedTable { Shape6({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get username => - columnsByName['username']! as i1.GeneratedColumn; - i1.GeneratedColumn get jwt => - columnsByName['jwt']! as i1.GeneratedColumn; - i1.GeneratedColumn get instance => - columnsByName['instance']! as i1.GeneratedColumn; - i1.GeneratedColumn get anonymous => - columnsByName['anonymous']! as i1.GeneratedColumn; - i1.GeneratedColumn get userId => - columnsByName['user_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get listIndex => - columnsByName['list_index']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get jwt => columnsByName['jwt']! as i1.GeneratedColumn; + i1.GeneratedColumn get instance => columnsByName['instance']! as i1.GeneratedColumn; + i1.GeneratedColumn get anonymous => columnsByName['anonymous']! as i1.GeneratedColumn; + i1.GeneratedColumn get userId => columnsByName['user_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get listIndex => columnsByName['list_index']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_21(String aliasedName) => - i1.GeneratedColumn('list_index', aliasedName, false, - type: i1.DriftSqlType.int, defaultValue: const CustomExpression('-1')); +i1.GeneratedColumn _column_21(String aliasedName) => i1.GeneratedColumn('list_index', aliasedName, false, type: i1.DriftSqlType.int, defaultValue: const CustomExpression('-1')); final class Schema6 extends i0.VersionedSchema { Schema6({required super.database}) : super(version: 6); @@ -633,29 +546,18 @@ final class Schema6 extends i0.VersionedSchema { class Shape7 extends i0.VersionedTable { Shape7({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get draftType => - columnsByName['draft_type']! as i1.GeneratedColumn; - i1.GeneratedColumn get existingId => - columnsByName['existing_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get replyId => - columnsByName['reply_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get title => - columnsByName['title']! as i1.GeneratedColumn; - i1.GeneratedColumn get url => - columnsByName['url']! as i1.GeneratedColumn; - i1.GeneratedColumn get customThumbnail => - columnsByName['custom_thumbnail']! as i1.GeneratedColumn; - i1.GeneratedColumn get altText => - columnsByName['alt_text']! as i1.GeneratedColumn; - i1.GeneratedColumn get body => - columnsByName['body']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get draftType => columnsByName['draft_type']! as i1.GeneratedColumn; + i1.GeneratedColumn get existingId => columnsByName['existing_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get replyId => columnsByName['reply_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get title => columnsByName['title']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get customThumbnail => columnsByName['custom_thumbnail']! as i1.GeneratedColumn; + i1.GeneratedColumn get altText => columnsByName['alt_text']! as i1.GeneratedColumn; + i1.GeneratedColumn get body => columnsByName['body']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_22(String aliasedName) => - i1.GeneratedColumn('alt_text', aliasedName, true, - type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_22(String aliasedName) => i1.GeneratedColumn('alt_text', aliasedName, true, type: i1.DriftSqlType.string); final class Schema7 extends i0.VersionedSchema { Schema7({required super.database}) : super(version: 7); @@ -754,27 +656,17 @@ final class Schema7 extends i0.VersionedSchema { class Shape8 extends i0.VersionedTable { Shape8({required super.source, required super.alias}) : super.aliased(); - i1.GeneratedColumn get id => - columnsByName['id']! as i1.GeneratedColumn; - i1.GeneratedColumn get username => - columnsByName['username']! as i1.GeneratedColumn; - i1.GeneratedColumn get jwt => - columnsByName['jwt']! as i1.GeneratedColumn; - i1.GeneratedColumn get instance => - columnsByName['instance']! as i1.GeneratedColumn; - i1.GeneratedColumn get anonymous => - columnsByName['anonymous']! as i1.GeneratedColumn; - i1.GeneratedColumn get userId => - columnsByName['user_id']! as i1.GeneratedColumn; - i1.GeneratedColumn get listIndex => - columnsByName['list_index']! as i1.GeneratedColumn; - i1.GeneratedColumn get platform => - columnsByName['platform']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get jwt => columnsByName['jwt']! as i1.GeneratedColumn; + i1.GeneratedColumn get instance => columnsByName['instance']! as i1.GeneratedColumn; + i1.GeneratedColumn get anonymous => columnsByName['anonymous']! as i1.GeneratedColumn; + i1.GeneratedColumn get userId => columnsByName['user_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get listIndex => columnsByName['list_index']! as i1.GeneratedColumn; + i1.GeneratedColumn get platform => columnsByName['platform']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_23(String aliasedName) => - i1.GeneratedColumn('platform', aliasedName, true, - type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_23(String aliasedName) => i1.GeneratedColumn('platform', aliasedName, true, type: i1.DriftSqlType.string); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, diff --git a/lib/src/core/enums/fab_action.dart b/lib/src/core/enums/fab_action.dart index 4e5033636..e235ba6a7 100644 --- a/lib/src/core/enums/fab_action.dart +++ b/lib/src/core/enums/fab_action.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/features/post/post.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; enum FeedFabAction { @@ -114,9 +114,9 @@ enum PostFabAction { switch (this) { case PostFabAction.openFab: - context?.read().add(const OnFabToggle(true)); + context?.read().setPostFabOpen(true); case PostFabAction.refresh: - context?.read().add(GetPostEvent(post: post, postId: postId, highlightedCommentId: highlightedCommentId, selectedCommentPath: selectedCommentPath)); + context?.read().add(GetPostEvent(post: post, postId: postId, selectedCommentPath: selectedCommentPath)); default: break; } diff --git a/lib/src/core/enums/full_name.dart b/lib/src/core/enums/full_name.dart index 641b938ba..025099150 100644 --- a/lib/src/core/enums/full_name.dart +++ b/lib/src/core/enums/full_name.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/shared/full_name_widgets.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; enum FullNameSeparator { @@ -161,8 +161,8 @@ Widget generateSampleCommunityFullNameWidget( String generateUserFullNamePrefix(BuildContext? context, String? name, String? displayName, {FullNameSeparator? userSeparator, bool? useDisplayName}) { assert(context != null || (userSeparator != null && useDisplayName != null)); - userSeparator ??= context!.read().state.userSeparator; - useDisplayName ??= context!.read().state.useDisplayNamesForUsers; + userSeparator ??= context!.read().state.userSeparator; + useDisplayName ??= context!.read().state.useDisplayNamesForUsers; return switch (userSeparator) { FullNameSeparator.dot => (useDisplayName && displayName?.isNotEmpty == true ? displayName : name) ?? '', FullNameSeparator.at => (useDisplayName && displayName?.isNotEmpty == true ? displayName : name) ?? '', @@ -172,7 +172,7 @@ String generateUserFullNamePrefix(BuildContext? context, String? name, String? d String generateUserFullNameSuffix(BuildContext? context, String? instance, {FullNameSeparator? userSeparator}) { assert(context != null || userSeparator != null); - userSeparator ??= context!.read().state.userSeparator; + userSeparator ??= context!.read().state.userSeparator; return switch (userSeparator) { FullNameSeparator.dot => ' · $instance', FullNameSeparator.at => '@$instance', @@ -190,8 +190,8 @@ String generateUserFullName(BuildContext? context, String? name, String? display String generateCommunityFullNamePrefix(BuildContext? context, String? name, String? displayName, {FullNameSeparator? communitySeparator, bool? useDisplayName}) { assert(context != null || (communitySeparator != null && useDisplayName != null)); - communitySeparator ??= context!.read().state.communitySeparator; - useDisplayName ??= context!.read().state.useDisplayNamesForCommunities; + communitySeparator ??= context!.read().state.communitySeparator; + useDisplayName ??= context!.read().state.useDisplayNamesForCommunities; return switch (communitySeparator) { FullNameSeparator.dot => (useDisplayName && displayName?.isNotEmpty == true ? displayName : name) ?? '', FullNameSeparator.at => (useDisplayName && displayName?.isNotEmpty == true ? displayName : name) ?? '', @@ -201,7 +201,7 @@ String generateCommunityFullNamePrefix(BuildContext? context, String? name, Stri String generateCommunityFullNameSuffix(BuildContext? context, String? instance, {FullNameSeparator? communitySeparator}) { assert(context != null || communitySeparator != null); - communitySeparator ??= context!.read().state.communitySeparator; + communitySeparator ??= context!.read().state.communitySeparator; return switch (communitySeparator) { FullNameSeparator.dot => ' · $instance', FullNameSeparator.at => '@$instance', diff --git a/lib/src/core/enums/swipe_action.dart b/lib/src/core/enums/swipe_action.dart index 3964efc86..1a5e728f0 100644 --- a/lib/src/core/enums/swipe_action.dart +++ b/lib/src/core/enums/swipe_action.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; enum SwipeAction { upvote(label: 'Upvote'), @@ -50,19 +50,19 @@ enum SwipeAction { Color getColor(BuildContext context) { switch (this) { case SwipeAction.upvote: - return context.read().state.upvoteColor.color; + return context.read().state.upvoteColor.color; case SwipeAction.downvote: - return context.read().state.downvoteColor.color; + return context.read().state.downvoteColor.color; case SwipeAction.reply: - return context.read().state.replyColor.color; + return context.read().state.replyColor.color; case SwipeAction.edit: - return context.read().state.replyColor.color; + return context.read().state.replyColor.color; case SwipeAction.save: - return context.read().state.saveColor.color; + return context.read().state.saveColor.color; case SwipeAction.toggleRead: - return context.read().state.markReadColor.color; + return context.read().state.markReadColor.color; case SwipeAction.hide: - return context.read().state.hideColor.color; + return context.read().state.hideColor.color; default: return Colors.transparent; } diff --git a/lib/src/features/account/presentation/pages/account_page.dart b/lib/src/features/account/presentation/pages/account_page.dart index b918e6273..901ef0689 100644 --- a/lib/src/features/account/presentation/pages/account_page.dart +++ b/lib/src/features/account/presentation/pages/account_page.dart @@ -18,13 +18,15 @@ class _AccountPageState extends State with AutomaticKeepAliveClient Widget build(BuildContext context) { super.build(context); - return BlocBuilder( - builder: (context, state) { - if (state.isLoggedIn != true) return const AccountPlaceholder(); + return BlocSelector( + selector: (state) => state.isLoggedIn, + builder: (context, isLoggedIn) { + if (isLoggedIn != true) return const AccountPlaceholder(); + final userId = context.select((bloc) => bloc.state.account.userId); return FeedPage( feedType: FeedType.account, - userId: state.account.userId, + userId: userId, postSortType: PostSortType.new_, ); }, diff --git a/lib/src/features/account/presentation/widgets/account_placeholder.dart b/lib/src/features/account/presentation/widgets/account_placeholder.dart index 9248c5ce0..53286589b 100644 --- a/lib/src/features/account/presentation/widgets/account_placeholder.dart +++ b/lib/src/features/account/presentation/widgets/account_placeholder.dart @@ -16,7 +16,7 @@ class AccountPlaceholder extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final l10n = AppLocalizations.of(context)!; - final instance = context.watch().state.currentAnonymousInstance ?? ''; + final instance = context.select((bloc) => bloc.state.currentAnonymousInstance) ?? ''; return Center( child: Padding( diff --git a/lib/src/features/account/presentation/widgets/profile_modal_body.dart b/lib/src/features/account/presentation/widgets/profile_modal_body.dart index eeba26ac8..1cf1ebe80 100644 --- a/lib/src/features/account/presentation/widgets/profile_modal_body.dart +++ b/lib/src/features/account/presentation/widgets/profile_modal_body.dart @@ -11,9 +11,10 @@ import 'package:thunder/src/app/routing/swipeable_page_route.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/core/enums/threadiverse_platform.dart'; import 'package:thunder/src/core/models/models.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/notification/notification.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/shared/utils/instance.dart'; @@ -94,9 +95,10 @@ class _ProfileModalBodyState extends State { page = LoginPage(popRegister: popRegister, popModal: popModal, anonymous: (settings.arguments as Map)['anonymous']!); break; } + final gestureCubit = context.read(); return SwipeablePageRoute( - canSwipe: !kIsWeb && Platform.isIOS || context.read().state.enableFullScreenSwipeNavigationGesture, - canOnlySwipeFromEdge: !context.read().state.enableFullScreenSwipeNavigationGesture, + canSwipe: !kIsWeb && Platform.isIOS || gestureCubit.state.enableFullScreenSwipeNavigationGesture, + canOnlySwipeFromEdge: !gestureCubit.state.enableFullScreenSwipeNavigationGesture, builder: (context) { return page; }, @@ -153,13 +155,13 @@ class _ProfileSelectState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); final l10n = AppLocalizations.of(context)!; - final bool darkTheme = context.read().state.useDarkTheme; + final bool darkTheme = context.read().state.useDarkTheme; Color selectedColor = theme.colorScheme.primaryContainer; if (!darkTheme) { selectedColor = HSLColor.fromColor(theme.colorScheme.primaryContainer).withLightness(0.95).toColor(); } Account currentAccount = context.watch().state.account; - String? currentAnonymousInstance = context.watch().state.currentAnonymousInstance; + String? currentAnonymousInstance = context.select((bloc) => bloc.state.currentAnonymousInstance); if (accounts == null) { fetchAccounts(); diff --git a/lib/src/features/comment/presentation/widgets/comment_bottom_sheet/general_comment_action_bottom_sheet.dart b/lib/src/features/comment/presentation/widgets/comment_bottom_sheet/general_comment_action_bottom_sheet.dart index 65e4b231b..3ed34e138 100644 --- a/lib/src/features/comment/presentation/widgets/comment_bottom_sheet/general_comment_action_bottom_sheet.dart +++ b/lib/src/features/comment/presentation/widgets/comment_bottom_sheet/general_comment_action_bottom_sheet.dart @@ -8,7 +8,7 @@ import 'package:thunder/src/core/enums/full_name.dart'; import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/shared/bottom_sheet_action.dart'; import 'package:thunder/src/shared/multi_picker_item.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/shared/utils/instance.dart'; @@ -214,33 +214,33 @@ class _GeneralCommentActionBottomSheetPageState extends State().state; + final themeState = context.read().state; switch (action) { case GeneralQuickCommentAction.upvote: - return state.upvoteColor.color; + return themeState.upvoteColor.color; case GeneralQuickCommentAction.downvote: - return state.downvoteColor.color; + return themeState.downvoteColor.color; case GeneralQuickCommentAction.save: - return state.saveColor.color; + return themeState.saveColor.color; case GeneralQuickCommentAction.reply: - return state.replyColor.color; + return themeState.replyColor.color; case GeneralQuickCommentAction.edit: - return state.replyColor.color; + return themeState.replyColor.color; } } Color? getForegroundColor(GeneralQuickCommentAction action) { - final state = context.read().state; + final themeState = context.read().state; final comment = widget.comment; switch (action) { case GeneralQuickCommentAction.upvote: - return comment.myVote == 1 ? state.upvoteColor.color : null; + return comment.myVote == 1 ? themeState.upvoteColor.color : null; case GeneralQuickCommentAction.downvote: - return comment.myVote == -1 ? state.downvoteColor.color : null; + return comment.myVote == -1 ? themeState.downvoteColor.color : null; case GeneralQuickCommentAction.save: - return comment.saved == true ? state.saveColor.color : null; + return comment.saved == true ? themeState.saveColor.color : null; default: return null; } diff --git a/lib/src/features/comment/presentation/widgets/comment_card/additional_comment_card.dart b/lib/src/features/comment/presentation/widgets/comment_card/additional_comment_card.dart index 771767a6a..428917db5 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/additional_comment_card.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/additional_comment_card.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/core/enums/nested_comment_indicator.dart'; import 'package:thunder/src/features/comment/comment.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; @@ -37,19 +39,15 @@ class _AdditionalCommentCardState extends State { final l10n = GlobalContext.l10n; final theme = Theme.of(context); - final state = context.select( - (ThunderBloc bloc) => ( - style: bloc.state.nestedCommentIndicatorStyle, - scheme: bloc.state.nestedCommentIndicatorColor, - commentFontSizeScale: bloc.state.commentFontSizeScale, - ), - ); + final nestedCommentIndicatorStyle = context.select((cubit) => cubit.state.nestedCommentIndicatorStyle); + final nestedCommentIndicatorColor = context.select((cubit) => cubit.state.nestedCommentIndicatorColor); + final commentFontSizeScale = context.select((cubit) => cubit.state.commentFontSizeScale); - final padding = (state.style == NestedCommentIndicatorStyle.thick ? widget.depth + 1 : widget.depth) * 4.0; + final padding = (nestedCommentIndicatorStyle == NestedCommentIndicatorStyle.thick ? widget.depth + 1 : widget.depth) * 4.0; final reply = widget.replies == 1 ? l10n.loadMoreSingular(widget.replies) : l10n.loadMorePlural(widget.replies); return Container( - decoration: CommentDepthIndicatorDecoration(context, level: widget.depth + 1, style: state.style, scheme: state.scheme), + decoration: CommentDepthIndicatorDecoration(context, level: widget.depth + 1, style: nestedCommentIndicatorStyle, scheme: nestedCommentIndicatorColor), child: Padding( padding: EdgeInsets.only(left: padding), child: InkWell( @@ -68,7 +66,7 @@ class _AdditionalCommentCardState extends State { padding: const EdgeInsets.fromLTRB(12.0, 12.0, 0.0, 12.0), child: ScalableText( reply, - fontScale: state.commentFontSizeScale, + fontScale: commentFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith( color: theme.textTheme.bodyMedium?.color?.withValues(alpha: 0.5), ), diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_card.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_card.dart index e60754f0b..cb27e912b 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_card.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_card.dart @@ -9,6 +9,8 @@ import 'package:thunder/src/core/enums/nested_comment_indicator.dart'; import 'package:thunder/src/core/enums/swipe_action.dart'; import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; import 'package:thunder/src/shared/widgets/multi_action_dismissible.dart'; import 'package:thunder/src/shared/utils/swipe.dart'; @@ -170,15 +172,15 @@ class _CommentCardState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); - final nestedCommentIndicatorStyle = context.select((bloc) => bloc.state.nestedCommentIndicatorStyle); - final nestedCommentIndicatorColor = context.select((bloc) => bloc.state.nestedCommentIndicatorColor); + final nestedCommentIndicatorStyle = context.select((cubit) => cubit.state.nestedCommentIndicatorStyle); + final nestedCommentIndicatorColor = context.select((cubit) => cubit.state.nestedCommentIndicatorColor); - final showCommentButtonActions = context.select((bloc) => bloc.state.showCommentButtonActions); - final enableCommentGestures = context.select((bloc) => bloc.state.enableCommentGestures); - final leftPrimaryCommentGesture = context.select((bloc) => bloc.state.leftPrimaryCommentGesture); - final leftSecondaryCommentGesture = context.select((bloc) => bloc.state.leftSecondaryCommentGesture); - final rightPrimaryCommentGesture = context.select((bloc) => bloc.state.rightPrimaryCommentGesture); - final rightSecondaryCommentGesture = context.select((bloc) => bloc.state.rightSecondaryCommentGesture); + final showCommentButtonActions = context.select((cubit) => cubit.state.showCommentButtonActions); + final enableCommentGestures = context.select((cubit) => cubit.state.enableCommentGestures); + final leftPrimaryCommentGesture = context.select((cubit) => cubit.state.leftPrimaryCommentGesture); + final leftSecondaryCommentGesture = context.select((cubit) => cubit.state.leftSecondaryCommentGesture); + final rightPrimaryCommentGesture = context.select((cubit) => cubit.state.rightPrimaryCommentGesture); + final rightSecondaryCommentGesture = context.select((cubit) => cubit.state.rightSecondaryCommentGesture); final actionThresholds = [0.15, 0.35]; final leftActions = [leftPrimaryCommentGesture, leftSecondaryCommentGesture].where((action) => action != SwipeAction.none).toList(); diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_background.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_background.dart index ba17d4b06..4bc07f9e0 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_background.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_background.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; import 'package:thunder/src/core/enums/swipe_action.dart'; /// A widget that displays the proper background when a swipe action is performed on a comment. @@ -29,8 +29,8 @@ class CommentCardBackground extends StatelessWidget { @override Widget build(BuildContext context) { - final leftPrimaryCommentGesture = context.select((bloc) => bloc.state.leftPrimaryCommentGesture); - final rightPrimaryCommentGesture = context.select((bloc) => bloc.state.rightPrimaryCommentGesture); + final leftPrimaryCommentGesture = context.select((cubit) => cubit.state.leftPrimaryCommentGesture); + final rightPrimaryCommentGesture = context.select((cubit) => cubit.state.rightPrimaryCommentGesture); final alignment = dismissDirection == DismissDirection.startToEnd ? Alignment.centerLeft : Alignment.centerRight; final defaultColor = dismissDirection == DismissDirection.startToEnd ? leftPrimaryCommentGesture.getColor(context) : rightPrimaryCommentGesture.getColor(context); diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_button_actions.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_button_actions.dart index dbd3ce3fd..36fea6228 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_button_actions.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_button_actions.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/core/enums/swipe_action.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/features/comment/comment.dart'; -import 'package:thunder/src/app/thunder.dart'; import 'package:thunder/src/app/utils/global_context.dart'; /// Displays a row of actions that can be performed on a comment. @@ -40,8 +40,8 @@ class CommentCardButtonActions extends StatelessWidget { final downvotesEnabled = context.select((bloc) => bloc.state.downvotesEnabled); final voteType = comment.myVote ?? 0; - final upvoteColor = context.select((bloc) => bloc.state.upvoteColor.color); - final downvoteColor = context.select((bloc) => bloc.state.downvoteColor.color); + final upvoteColor = context.select((cubit) => cubit.state.upvoteColor.color); + final downvoteColor = context.select((cubit) => cubit.state.downvoteColor.color); final widgets = [ _CommentCardButtonAction( diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header.dart index ede8d0f9b..d9e9f57b4 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header.dart @@ -10,7 +10,9 @@ import 'package:thunder/src/features/comment/presentation/widgets/comment_card/c import 'package:thunder/src/core/enums/user_type.dart'; import 'package:thunder/src/shared/widgets/avatars/user_avatar.dart'; import 'package:thunder/src/shared/widgets/chips/user_chip.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/core/enums/action_color.dart'; import 'package:thunder/src/features/user/user.dart'; /// A widget that displays the header of a comment, including user information, score, and metadata @@ -37,13 +39,9 @@ class CommentCardHeader extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final state = context.select( - (ThunderBloc bloc) => ( - collapseParentCommentOnGesture: bloc.state.collapseParentCommentOnGesture, - commentShowUserInstance: bloc.state.commentShowUserInstance, - saveColor: bloc.state.saveColor, - ), - ); + final collapseParentCommentOnGesture = context.select((cubit) => cubit.state.collapseParentCommentOnGesture); + final commentShowUserInstance = context.select((cubit) => cubit.state.commentShowUserInstance); + final saveColor = context.select((cubit) => cubit.state.saveColor); return LayoutBuilder( builder: (context, constraints) => Padding( @@ -62,8 +60,8 @@ class CommentCardHeader extends StatelessWidget { user: comment.creator!, personAvatar: UserAvatar(user: comment.creator!, radius: 10, thumbnailSize: 20, format: 'png'), userGroups: userGroups, - includeInstance: state.commentShowUserInstance, - ignorePointerEvents: hidden && state.collapseParentCommentOnGesture, + includeInstance: commentShowUserInstance, + ignorePointerEvents: hidden && collapseParentCommentOnGesture, opacity: 1.0, constraints: constraints, ), @@ -75,7 +73,7 @@ class CommentCardHeader extends StatelessWidget { children: hidden && (comment.childCount ?? 0) > 0 ? [CommentCardHeaderReplyCount(replies: comment.childCount!, hidden: hidden)] : [ - if (comment.saved == true) Icon(Icons.star_rounded, color: state.saveColor.color, size: 19.0), + if (comment.saved == true) Icon(Icons.star_rounded, color: saveColor.color, size: 19.0), if (comment.updated != null) Icon(Icons.create_rounded, color: theme.colorScheme.onSurface.withValues(alpha: 0.75), size: 16.0), CommentCardHeaderDate(created: comment.published, updated: comment.updated), ], diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_date.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_date.dart index 2414e8a41..3151cd2b4 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_date.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_date.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/shared/utils/date_time.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; @@ -31,15 +31,11 @@ class CommentCardHeaderDate extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final formattedDate = BlocSelector( - selector: (state) => state.metadataFontSizeScale, - builder: (context, metadataFontSizeScale) { - return ScalableText( - date, - fontScale: metadataFontSizeScale, - style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurface), - ); - }, + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); + final formattedDate = ScalableText( + date, + fontScale: metadataFontSizeScale, + style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurface), ); if (!recent) return formattedDate; diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_reply_count.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_reply_count.dart index 522231180..91cd56f72 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_reply_count.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_reply_count.dart @@ -2,7 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; @@ -22,7 +23,8 @@ class CommentCardHeaderReplyCount extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final collapseParentCommentOnGesture = context.select((ThunderBloc bloc) => bloc.state.collapseParentCommentOnGesture); + final collapseParentCommentOnGesture = context.select((cubit) => cubit.state.collapseParentCommentOnGesture); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); return AnimatedOpacity( opacity: (hidden && (collapseParentCommentOnGesture || replies > 0)) ? 1.0 : 0.0, @@ -33,12 +35,7 @@ class CommentCardHeaderReplyCount extends StatelessWidget { color: theme.colorScheme.primaryContainer, borderRadius: const BorderRadius.all(Radius.elliptical(5.0, 5.0)), ), - child: BlocSelector( - selector: (state) => state.metadataFontSizeScale, - builder: (context, metadataFontSizeScale) { - return ScalableText('+$replies', fontScale: metadataFontSizeScale); - }, - ), + child: ScalableText('+$replies', fontScale: metadataFontSizeScale), ), ); } diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_score.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_score.dart index c7563e60a..03438f485 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_score.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_card_header/comment_card_header_score.dart @@ -2,7 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/features/account/account.dart'; @@ -41,17 +42,15 @@ class CommentCardHeaderScore extends StatelessWidget { final showScores = context.select((ProfileBloc bloc) => bloc.state.siteResponse?.myUser?.localUserView.localUser.showScores) ?? true; - final state = context.select((ThunderBloc bloc) => ( - metadataFontSizeScale: bloc.state.metadataFontSizeScale, - combineCommentScores: bloc.state.combineCommentScores, - upvoteColor: bloc.state.upvoteColor.color, - downvoteColor: bloc.state.downvoteColor.color, - )); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); + final combineCommentScores = context.select((cubit) => cubit.state.combineCommentScores); + final upvoteColor = context.select((cubit) => cubit.state.upvoteColor.color); + final downvoteColor = context.select((cubit) => cubit.state.downvoteColor.color); // Show only vote indicator if scores are hidden if (!showScores) { - if (voteType == 1) return VoteIcon(type: voteType!, voteType: voteType, color: state.upvoteColor, fontScale: state.metadataFontSizeScale); - if (voteType == -1) return VoteIcon(type: voteType!, voteType: voteType, color: state.downvoteColor, fontScale: state.metadataFontSizeScale); + if (voteType == 1) return VoteIcon(type: voteType!, voteType: voteType, color: upvoteColor, fontScale: metadataFontSizeScale); + if (voteType == -1) return VoteIcon(type: voteType!, voteType: voteType, color: downvoteColor, fontScale: metadataFontSizeScale); return SizedBox.shrink(); } @@ -60,24 +59,24 @@ class CommentCardHeaderScore extends StatelessWidget { final downvotesLabel = formatNumberToK(downvotes); // Show the combined score - if (state.combineCommentScores) { + if (combineCommentScores) { return Row( spacing: 2.0, children: [ - VoteIcon(type: 1, voteType: voteType, color: state.upvoteColor, fontScale: state.metadataFontSizeScale), + VoteIcon(type: 1, voteType: voteType, color: upvoteColor, fontScale: metadataFontSizeScale), ScalableText( scoreLabel, semanticsLabel: l10n.xScore(scoreLabel), - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith( color: (voteType != null && voteType != 0) ? voteType == 1 - ? state.upvoteColor - : state.downvoteColor + ? upvoteColor + : downvoteColor : theme.colorScheme.onSurface, ), ), - VoteIcon(type: -1, voteType: voteType, color: state.downvoteColor, fontScale: state.metadataFontSizeScale), + VoteIcon(type: -1, voteType: voteType, color: downvoteColor, fontScale: metadataFontSizeScale), ], ); } @@ -86,26 +85,26 @@ class CommentCardHeaderScore extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - VoteIcon(type: 1, voteType: voteType, color: state.upvoteColor, fontScale: state.metadataFontSizeScale), + VoteIcon(type: 1, voteType: voteType, color: upvoteColor, fontScale: metadataFontSizeScale), const SizedBox(width: 2.0), ScalableText( upvotesLabel, semanticsLabel: l10n.xUpvotes(upvotesLabel), - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith( - color: (voteType == 1) ? state.upvoteColor : theme.colorScheme.onSurface, + color: (voteType == 1) ? upvoteColor : theme.colorScheme.onSurface, ), ), const SizedBox(width: 10.0), if (downvotes != 0) ...[ - VoteIcon(type: -1, voteType: voteType, color: state.downvoteColor, fontScale: state.metadataFontSizeScale), + VoteIcon(type: -1, voteType: voteType, color: downvoteColor, fontScale: metadataFontSizeScale), const SizedBox(width: 2.0), ScalableText( downvotesLabel, semanticsLabel: l10n.xDownvotes(downvotesLabel), - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith( - color: (voteType == -1) ? state.downvoteColor : theme.colorScheme.onSurface, + color: (voteType == -1) ? downvoteColor : theme.colorScheme.onSurface, ), ), ], diff --git a/lib/src/features/comment/presentation/widgets/comment_card/comment_content.dart b/lib/src/features/comment/presentation/widgets/comment_card/comment_content.dart index f8fcdfea2..34631f523 100644 --- a/lib/src/features/comment/presentation/widgets/comment_card/comment_content.dart +++ b/lib/src/features/comment/presentation/widgets/comment_card/comment_content.dart @@ -13,7 +13,8 @@ import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; import 'package:thunder/src/shared/conditional_parent_widget.dart'; import 'package:thunder/src/shared/reply_to_preview_actions.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; /// A widget that displays the content of a comment. class CommentContent extends StatefulWidget { @@ -87,9 +88,9 @@ class _CommentContentState extends State with SingleTickerProvid final content = cleanCommentContent(widget.comment); - final collapseParentCommentOnGesture = context.select((bloc) => bloc.state.collapseParentCommentOnGesture); - final nestedCommentIndicatorStyle = context.select((bloc) => bloc.state.nestedCommentIndicatorStyle); - final contentFontSizeScale = context.select((bloc) => bloc.state.contentFontSizeScale); + final collapseParentCommentOnGesture = context.select((cubit) => cubit.state.collapseParentCommentOnGesture); + final nestedCommentIndicatorStyle = context.select((cubit) => cubit.state.nestedCommentIndicatorStyle); + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); return ExcludeSemantics( excluding: widget.excludeSemantics, diff --git a/lib/src/features/community/presentation/widgets/community_drawer.dart b/lib/src/features/community/presentation/widgets/community_drawer.dart index 68ffa4da6..465a97686 100644 --- a/lib/src/features/community/presentation/widgets/community_drawer.dart +++ b/lib/src/features/community/presentation/widgets/community_drawer.dart @@ -13,6 +13,7 @@ import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/shared/widgets/avatars/community_avatar.dart'; import 'package:thunder/src/shared/widgets/avatars/user_avatar.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/instance.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/core/enums/full_name.dart'; @@ -44,7 +45,7 @@ class _CommunityDrawerState extends State { ProfileState profileState = context.watch().state; FeedState feedState = context.watch().state; - ThunderState thunderState = context.read().state; + final feedCubit = context.read(); AnonymousSubscriptionsBloc subscriptionsBloc = context.watch(); subscriptionsBloc.add(GetSubscribedCommunitiesEvent()); @@ -94,7 +95,7 @@ class _CommunityDrawerState extends State { onPressed: () async { Navigator.of(context).pop(); - final postSortType = profileState.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? thunderState.postSortTypeForInstance; + final postSortType = profileState.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? feedCubit.state.defaultPostSortType; context.read().add( FeedFetchedEvent( @@ -103,7 +104,7 @@ class _CommunityDrawerState extends State { communityId: isLoggedIn ? community.id : null, communityName: !isLoggedIn ? await getLemmyCommunity(community.actorId) : null, reset: true, - showHidden: thunderState.showHiddenPosts, + showHidden: feedCubit.state.showHiddenPosts, ), ); }, @@ -142,7 +143,7 @@ class UserDrawerItem extends StatelessWidget { ProfileState profileState = context.watch().state; bool isLoggedIn = context.watch().state.isLoggedIn; - String? anonymousInstance = context.watch().state.currentAnonymousInstance; + String? anonymousInstance = context.select((bloc) => bloc.state.currentAnonymousInstance); return Container( color: theme.colorScheme.surfaceContainerLow, @@ -266,7 +267,7 @@ class FavoriteCommunities extends StatelessWidget { ProfileState profileState = context.watch().state; FeedState feedState = context.watch().state; - ThunderState thunderState = context.read().state; + final feedCubit = context.read(); bool isLoggedIn = context.watch().state.isLoggedIn; @@ -298,7 +299,7 @@ class FavoriteCommunities extends StatelessWidget { onPressed: () { Navigator.of(context).pop(); - final postSortType = profileState.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? thunderState.postSortTypeForInstance; + final postSortType = profileState.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? feedCubit.state.defaultPostSortType; context.read().add( FeedFetchedEvent( @@ -306,7 +307,7 @@ class FavoriteCommunities extends StatelessWidget { postSortType: postSortType, communityId: community.id, reset: true, - showHidden: thunderState.showHiddenPosts, + showHidden: feedCubit.state.showHiddenPosts, ), ); }, @@ -330,7 +331,7 @@ class ModeratedCommunities extends StatelessWidget { ProfileState profileState = context.watch().state; FeedState feedState = context.watch().state; - ThunderState thunderState = context.read().state; + final feedCubit = context.read(); List moderatedCommunities = profileState.moderates; @@ -361,7 +362,7 @@ class ModeratedCommunities extends StatelessWidget { onPressed: () { Navigator.of(context).pop(); - final postSortType = profileState.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? thunderState.postSortTypeForInstance; + final postSortType = profileState.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? feedCubit.state.defaultPostSortType; context.read().add( FeedFetchedEvent( @@ -369,7 +370,7 @@ class ModeratedCommunities extends StatelessWidget { postSortType: postSortType, communityId: community.id, reset: true, - showHidden: thunderState.showHiddenPosts, + showHidden: feedCubit.state.showHiddenPosts, ), ); }, diff --git a/lib/src/features/community/presentation/widgets/post_card.dart b/lib/src/features/community/presentation/widgets/post_card.dart index 8fcfd5c5b..ba935b0c2 100644 --- a/lib/src/features/community/presentation/widgets/post_card.dart +++ b/lib/src/features/community/presentation/widgets/post_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/features/community/community.dart'; @@ -14,6 +15,8 @@ import 'package:thunder/src/app/utils/navigation.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/widgets/multi_action_dismissible.dart'; import 'package:thunder/src/shared/utils/swipe.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; class PostCard extends StatefulWidget { /// The associated post information to display in the card. @@ -146,19 +149,43 @@ class _PostCardState extends State { @override Widget build(BuildContext context) { - final state = context.read().state; - final currentSwipeDirection = determinePostSwipeDirection(isUserLoggedIn, state, disableSwiping: widget.disableSwiping); - final feedType = context.read().state.feedType; + // Select only the specific properties we need from GesturePreferencesCubit + final enablePostGestures = context.select((cubit) => cubit.state.enablePostGestures); + final leftPrimaryPostGesture = context.select((cubit) => cubit.state.leftPrimaryPostGesture); + final leftSecondaryPostGesture = context.select((cubit) => cubit.state.leftSecondaryPostGesture); + final rightPrimaryPostGesture = context.select((cubit) => cubit.state.rightPrimaryPostGesture); + final rightSecondaryPostGesture = context.select((cubit) => cubit.state.rightSecondaryPostGesture); + + // Select only the specific properties we need from FeedPreferencesCubit + final useCompactView = context.select((cubit) => cubit.state.useCompactView); + final hideThumbnails = context.select((cubit) => cubit.state.hideThumbnails); + final hideNsfwPreviews = context.select((cubit) => cubit.state.hideNsfwPreviews); + final markPostReadOnMediaView = context.select((cubit) => cubit.state.markPostReadOnMediaView); + final showFullHeightImages = context.select((cubit) => cubit.state.showFullHeightImages); + final showEdgeToEdgeImages = context.select((cubit) => cubit.state.showEdgeToEdgeImages); + final showTitleFirst = context.select((cubit) => cubit.state.showTitleFirst); + final showTextContent = context.select((cubit) => cubit.state.showTextContent); + + final currentSwipeDirection = determinePostSwipeDirection( + isUserLoggedIn: isUserLoggedIn, + enablePostGestures: enablePostGestures, + leftPrimaryPostGesture: leftPrimaryPostGesture, + leftSecondaryPostGesture: leftSecondaryPostGesture, + rightPrimaryPostGesture: rightPrimaryPostGesture, + rightSecondaryPostGesture: rightSecondaryPostGesture, + disableSwiping: widget.disableSwiping, + ); + final feedType = context.select((bloc) => bloc.state.feedType); // Determine which post card view to use based on the settings - Widget child = state.useCompactView || widget.post.featuredLocal || (feedType == FeedType.community && widget.post.featuredCommunity) + Widget child = useCompactView || widget.post.featuredLocal || (feedType == FeedType.community && widget.post.featuredCommunity) ? PostCardViewCompact( post: widget.post, creator: widget.post.creator!, community: widget.post.community!, indicateRead: widget.indicateRead, isLastTapped: widget.isLastTapped, - showMedia: !state.hideThumbnails, + showMedia: !hideThumbnails, navigateToPost: ({ThunderPost? post}) async { widget.onTap(); await navigateToPost(context, post: widget.post); @@ -166,13 +193,13 @@ class _PostCardState extends State { ) : PostCardViewComfortable( post: widget.post, - hideThumbnails: state.hideThumbnails, - hideNsfwPreviews: state.hideNsfwPreviews, - markPostReadOnMediaView: state.markPostReadOnMediaView, - showFullHeightImages: state.showFullHeightImages, - edgeToEdgeImages: state.showEdgeToEdgeImages, - showTitleFirst: state.showTitleFirst, - showTextContent: state.showTextContent, + hideThumbnails: hideThumbnails, + hideNsfwPreviews: hideNsfwPreviews, + markPostReadOnMediaView: markPostReadOnMediaView, + showFullHeightImages: showFullHeightImages, + edgeToEdgeImages: showEdgeToEdgeImages, + showTitleFirst: showTitleFirst, + showTextContent: showTextContent, isUserLoggedIn: isUserLoggedIn, indicateRead: widget.indicateRead, isLastTapped: widget.isLastTapped, @@ -203,11 +230,11 @@ class _PostCardState extends State { } if (userAction == UserAction.block) { - context.read().add(FeedDismissBlockedEvent(userId: post!.creator!.id)); + context.read().dismissBlocked(userId: post!.creator!.id); } if (communityAction == CommunityAction.block) { - context.read().add(FeedDismissBlockedEvent(communityId: post!.community!.id)); + context.read().dismissBlocked(communityId: post!.community!.id); } }, ), @@ -220,8 +247,8 @@ class _PostCardState extends State { final hidden = widget.post.hidden ?? false; final actionThresholds = [0.15, 0.35]; - final leftActions = [state.leftPrimaryPostGesture, state.leftSecondaryPostGesture].where((action) => action != SwipeAction.none).toList(); - final rightActions = [state.rightPrimaryPostGesture, state.rightSecondaryPostGesture].where((action) => action != SwipeAction.none).toList(); + final leftActions = [leftPrimaryPostGesture, leftSecondaryPostGesture].where((action) => action != SwipeAction.none).toList(); + final rightActions = [rightPrimaryPostGesture, rightSecondaryPostGesture].where((action) => action != SwipeAction.none).toList(); child = MultiActionDismissible( key: ObjectKey(widget.post.id), @@ -295,8 +322,8 @@ class PostCardActionBackground extends StatelessWidget { Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; final tabletMode = context.select((bloc) => bloc.state.tabletMode); - final leftPrimaryPostGesture = context.select((bloc) => bloc.state.leftPrimaryPostGesture); - final rightPrimaryPostGesture = context.select((bloc) => bloc.state.rightPrimaryPostGesture); + final leftPrimaryPostGesture = context.select((cubit) => cubit.state.leftPrimaryPostGesture); + final rightPrimaryPostGesture = context.select((cubit) => cubit.state.rightPrimaryPostGesture); final alignment = dismissDirection == DismissDirection.startToEnd ? Alignment.centerLeft : Alignment.centerRight; final defaultColor = dismissDirection == DismissDirection.startToEnd ? leftPrimaryPostGesture.getColor(context) : rightPrimaryPostGesture.getColor(context); diff --git a/lib/src/features/community/presentation/widgets/post_card_actions.dart b/lib/src/features/community/presentation/widgets/post_card_actions.dart index 55897cadd..5f2354f36 100644 --- a/lib/src/features/community/presentation/widgets/post_card_actions.dart +++ b/lib/src/features/community/presentation/widgets/post_card_actions.dart @@ -5,7 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/features/account/account.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; /// Represents the actions that can be performed on a post when using the card view. class PostCardActions extends StatelessWidget { @@ -33,18 +34,11 @@ class PostCardActions extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; - final state = context.select((ThunderBloc bloc) => ( - bloc.state.upvoteColor.color, - bloc.state.downvoteColor.color, - bloc.state.saveColor.color, - bloc.state.showVoteActions, - bloc.state.showSaveAction, - )); - final upvoteColor = state.$1; - final downvoteColor = state.$2; - final saveColor = state.$3; - final showVoteActions = state.$4; - final showSaveAction = state.$5; + final upvoteColor = context.select((cubit) => cubit.state.upvoteColor.color); + final downvoteColor = context.select((cubit) => cubit.state.downvoteColor.color); + final saveColor = context.select((cubit) => cubit.state.saveColor.color); + final showVoteActions = context.select((cubit) => cubit.state.showVoteActions); + final showSaveAction = context.select((cubit) => cubit.state.showSaveAction); final downvotesEnabled = context.select((ProfileBloc bloc) => bloc.state.downvotesEnabled); diff --git a/lib/src/features/community/presentation/widgets/post_card_metadata.dart b/lib/src/features/community/presentation/widgets/post_card_metadata.dart index b3aa8b1ef..2d8272ac3 100644 --- a/lib/src/features/community/presentation/widgets/post_card_metadata.dart +++ b/lib/src/features/community/presentation/widgets/post_card_metadata.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/features/community/community.dart'; @@ -8,6 +9,7 @@ import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/core/enums/enums.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/core/enums/subscription_status.dart'; import 'package:thunder/src/core/enums/view_mode.dart'; import 'package:thunder/src/features/feed/feed.dart'; @@ -16,7 +18,8 @@ import 'package:thunder/src/shared/widgets/avatars/community_avatar.dart'; import 'package:thunder/src/shared/full_name_widgets.dart'; import 'package:thunder/src/shared/icon_text.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/utils/date_time.dart'; import 'package:thunder/src/shared/utils/instance.dart'; @@ -83,7 +86,8 @@ class PostCardMetadata extends StatelessWidget { @override Widget build(BuildContext context) { - final postCardMetadataItems = context.select((ThunderBloc bloc) => postCardViewType == ViewMode.compact ? bloc.state.compactPostCardMetadataItems : bloc.state.cardPostCardMetadataItems); + final postCardMetadataItems = context + .select>((cubit) => postCardViewType == ViewMode.compact ? cubit.state.compactPostCardMetadataItems : cubit.state.cardPostCardMetadataItems); final showScores = context.select((ProfileBloc bloc) => bloc.state.siteResponse?.myUser?.localUserView.localUser.showScores) ?? true; final dim = this.dim ?? false; @@ -169,10 +173,9 @@ class ScorePostCardMetaData extends StatelessWidget { final theme = Theme.of(context); final l10n = AppLocalizations.of(context)!; - final state = context.select((ThunderBloc bloc) => (bloc.state.metadataFontSizeScale, bloc.state.upvoteColor.color, bloc.state.downvoteColor.color)); - final metadataFontScale = state.$1; - final upvoteColor = state.$2; - final downvoteColor = state.$3; + final metadataFontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); + final upvoteColor = context.select((cubit) => cubit.state.upvoteColor.color); + final downvoteColor = context.select((cubit) => cubit.state.downvoteColor.color); final baseTextColor = theme.textTheme.bodyMedium?.color; final dimColor = baseTextColor?.withValues(alpha: 0.45); @@ -253,9 +256,8 @@ class UpvotePostCardMetaData extends StatelessWidget { final theme = Theme.of(context); - final state = context.select((ThunderBloc bloc) => (bloc.state.metadataFontSizeScale, bloc.state.upvoteColor.color)); - final metadataFontScale = state.$1; - final upvoteColor = state.$2; + final metadataFontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); + final upvoteColor = context.select((cubit) => cubit.state.upvoteColor.color); final baseTextColor = theme.textTheme.bodyMedium?.color; final dimColor = baseTextColor?.withValues(alpha: 0.45); @@ -306,9 +308,8 @@ class DownvotePostCardMetaData extends StatelessWidget { final theme = Theme.of(context); - final state = context.select((ThunderBloc bloc) => (bloc.state.metadataFontSizeScale, bloc.state.downvoteColor.color)); - final metadataFontScale = state.$1; - final downvoteColor = state.$2; + final metadataFontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); + final downvoteColor = context.select((cubit) => cubit.state.downvoteColor.color); final baseTextColor = theme.textTheme.bodyMedium?.color; final dimColor = baseTextColor?.withValues(alpha: 0.45); @@ -352,7 +353,7 @@ class CommentCountPostCardMetaData extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final fontScale = context.select((ThunderBloc bloc) => bloc.state.metadataFontSizeScale); + final fontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); final baseTextColor = theme.textTheme.bodyMedium?.color; final primaryColor = theme.primaryColor; @@ -394,10 +395,9 @@ class DateTimePostCardMetaData extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final state = context.select((ThunderBloc bloc) => (bloc.state.showFullPostDate, bloc.state.dateFormat, bloc.state.metadataFontSizeScale)); - final showFullPostDate = state.$1; - final dateFormat = state.$2; - final fontScale = state.$3; + final showFullPostDate = context.select((cubit) => cubit.state.showFullPostDate); + final dateFormat = context.select((cubit) => cubit.state.dateFormat); + final fontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); String formattedDate; @@ -441,7 +441,7 @@ class UrlPostCardMetaData extends StatelessWidget { if (url?.isEmpty ?? true) return const SizedBox.shrink(); final theme = Theme.of(context); - final fontScale = context.select((ThunderBloc bloc) => bloc.state.metadataFontSizeScale); + final fontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); String? host; @@ -504,7 +504,7 @@ class LanguagePostCardMetaData extends StatelessWidget { if (languageName == null) return const SizedBox.shrink(); final theme = Theme.of(context); - final fontScale = context.select((ThunderBloc bloc) => bloc.state.metadataFontSizeScale); + final fontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); final readColor = theme.textTheme.bodyMedium?.color?.withValues(alpha: 0.45); final color = hasBeenRead ? readColor : theme.textTheme.bodyMedium?.color; @@ -561,9 +561,8 @@ class PostCommunityAndAuthor extends StatelessWidget { @override Widget build(BuildContext context) { - final state = context.select((ThunderBloc bloc) => (bloc.state.showPostAuthor, bloc.state.showCommunityIcons)); - final showPostAuthor = state.$1; - final showCommunityIcons = state.$2; + final showPostAuthor = context.select((cubit) => cubit.state.showPostAuthor); + final showCommunityIcons = context.select((cubit) => cubit.state.showCommunityIcons); final feedType = context.select((FeedBloc bloc) => bloc.state.feedType); final showUsername = (showPostAuthor || feedType == FeedType.community) && feedType != FeedType.user; @@ -649,7 +648,7 @@ class CommunityPostCardMetadata extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final fontScale = context.select((ThunderBloc bloc) => (bloc.state.metadataFontSizeScale)); + final fontScale = context.select((cubit) => cubit.state.metadataFontSizeScale); final feedListType = context.select((FeedBloc bloc) => bloc.state.feedListType); final instanceName = actorId != null ? fetchInstanceNameFromUrl(actorId) : null; @@ -701,9 +700,8 @@ class UserPostCardMetadata extends StatelessWidget { @override Widget build(BuildContext context) { - final state = context.select((ThunderBloc bloc) => (bloc.state.postShowUserInstance, bloc.state.metadataFontSizeScale)); - final postShowUserInstance = state.$1; - final metadataFontSizeScale = state.$2; + final postShowUserInstance = context.select((cubit) => cubit.state.postShowUserInstance); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); final instanceName = actorId != null ? fetchInstanceNameFromUrl(actorId) : null; diff --git a/lib/src/features/community/presentation/widgets/post_card_view_comfortable.dart b/lib/src/features/community/presentation/widgets/post_card_view_comfortable.dart index 1d58aa551..915ed034e 100644 --- a/lib/src/features/community/presentation/widgets/post_card_view_comfortable.dart +++ b/lib/src/features/community/presentation/widgets/post_card_view_comfortable.dart @@ -4,16 +4,18 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:html/parser.dart'; import 'package:markdown/markdown.dart' hide Text; +import 'package:thunder/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart'; import 'package:thunder/src/features/community/community.dart'; import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/core/enums/media_type.dart'; import 'package:thunder/src/core/enums/view_mode.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/shared/widgets/media/media_view.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -104,7 +106,7 @@ class PostCardViewComfortable extends StatelessWidget { switch (postAction) { case PostAction.hide: - context.read().add(FeedDismissHiddenPostEvent(postId: post!.id)); + context.read().dismissHiddenPost(post!.id); break; default: break; @@ -112,7 +114,7 @@ class PostCardViewComfortable extends StatelessWidget { switch (userAction) { case UserAction.block: - context.read().add(FeedDismissBlockedEvent(userId: post!.creator!.id)); + context.read().dismissBlocked(userId: post!.creator!.id); break; default: break; @@ -120,7 +122,7 @@ class PostCardViewComfortable extends StatelessWidget { switch (communityAction) { case CommunityAction.block: - context.read().add(FeedDismissBlockedEvent(communityId: post!.community!.id)); + context.read().dismissBlocked(communityId: post!.community!.id); break; default: break; @@ -134,11 +136,10 @@ class PostCardViewComfortable extends StatelessWidget { final theme = Theme.of(context); final l10n = GlobalContext.l10n; - final state = context.select((ThunderBloc bloc) => (bloc.state.dimReadPosts, bloc.state.contentFontSizeScale)); - final dimReadPosts = state.$1; - final contentFontSizeScale = state.$2; + final dimReadPosts = context.select((cubit) => cubit.state.dimReadPosts); + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); - final useDarkTheme = context.select((ThemeBloc bloc) => bloc.state.useDarkTheme); + final useDarkTheme = context.select((ThemePreferencesCubit cubit) => cubit.state.useDarkTheme); final media = post.media.firstOrNull; final indicateRead = this.indicateRead ?? dimReadPosts; diff --git a/lib/src/features/community/presentation/widgets/post_card_view_compact.dart b/lib/src/features/community/presentation/widgets/post_card_view_compact.dart index e968c544a..228cd0bed 100644 --- a/lib/src/features/community/presentation/widgets/post_card_view_compact.dart +++ b/lib/src/features/community/presentation/widgets/post_card_view_compact.dart @@ -5,10 +5,10 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/features/community/community.dart'; import 'package:thunder/src/core/enums/media_type.dart'; import 'package:thunder/src/core/enums/view_mode.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/shared/widgets/media/compact_thumbnail_preview.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; /// Displays a compact view of a post card. This view is used in the feed related pages. @@ -62,12 +62,11 @@ class PostCardViewCompact extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final state = context.select((ThunderBloc bloc) => (bloc.state.showThumbnailPreviewOnRight, bloc.state.showTextPostIndicator, bloc.state.dimReadPosts)); - final showThumbnailPreviewOnRight = state.$1; - final showTextPostIndicator = state.$2; - final dimReadPostsSetting = state.$3; + final showThumbnailPreviewOnRight = context.select((cubit) => cubit.state.showThumbnailPreviewOnRight); + final showTextPostIndicator = context.select((cubit) => cubit.state.showTextPostIndicator); + final dimReadPostsSetting = context.select((cubit) => cubit.state.dimReadPosts); - final useDarkTheme = context.select((ThemeBloc bloc) => bloc.state.useDarkTheme); + final useDarkTheme = context.select((ThemePreferencesCubit cubit) => cubit.state.useDarkTheme); final indicateRead = this.indicateRead ?? dimReadPostsSetting; final dim = indicateRead && post.read == true; diff --git a/lib/src/features/feed/presentation/bloc/feed_bloc.dart b/lib/src/features/feed/presentation/bloc/feed_bloc.dart index 90895e92a..7964bf603 100644 --- a/lib/src/features/feed/presentation/bloc/feed_bloc.dart +++ b/lib/src/features/feed/presentation/bloc/feed_bloc.dart @@ -81,30 +81,6 @@ class FeedBloc extends Bloc { transformer: throttleDroppable(Duration.zero), ); - /// Handles scrolling to top of the feed - on( - _onFeedScrollToTop, - transformer: throttleDroppable(Duration.zero), - ); - - /// Handles dismissing read posts from the feed - on( - _onFeedDismissRead, - transformer: throttleDroppable(Duration.zero), - ); - - /// Handles dismissing posts from blocked users/communities - on( - _onFeedDismissBlocked, - transformer: throttleDroppable(Duration.zero), - ); - - /// Handles dismissing posts that have been hidden by the user - on( - _onFeedDismissHiddenPost, - transformer: throttleDroppable(Duration.zero), - ); - /// Handles hiding posts from the feed on( _onFeedHidePostsFromView, @@ -133,26 +109,6 @@ class FeedBloc extends Bloc { emit(state.copyWith(status: FeedStatus.success, posts: posts)); } - /// Handles dismissing read posts from the feed - Future _onFeedDismissRead(FeedDismissReadEvent event, Emitter emit) async { - emit(state.copyWith(status: FeedStatus.success, dismissReadId: state.dismissReadId + 1)); - } - - /// Handles dismissing read posts from the feed - Future _onFeedDismissBlocked(FeedDismissBlockedEvent event, Emitter emit) async { - emit(state.copyWith(status: FeedStatus.success, dismissBlockedUserId: event.userId, dismissBlockedCommunityId: event.communityId)); - } - - /// Handles dismissing read posts from the feed - Future _onFeedDismissHiddenPost(FeedDismissHiddenPostEvent event, Emitter emit) async { - emit(state.copyWith(status: FeedStatus.success, dismissHiddenPostId: event.postId)); - } - - /// Handles scrolling to top of the feed - Future _onFeedScrollToTop(ScrollToTopEvent event, Emitter emit) async { - emit(state.copyWith(status: FeedStatus.success, scrollId: state.scrollId + 1)); - } - /// Handles clearing any messages from the state Future _onFeedClearMessage(FeedClearMessageEvent event, Emitter emit) async { emit(state.copyWith(status: state.status == FeedStatus.failureLoadingCommunity || state.status == FeedStatus.failureLoadingUser ? state.status : FeedStatus.success, message: null)); diff --git a/lib/src/features/feed/presentation/bloc/feed_state.dart b/lib/src/features/feed/presentation/bloc/feed_state.dart index 56dcc9ddf..904d581ef 100644 --- a/lib/src/features/feed/presentation/bloc/feed_state.dart +++ b/lib/src/features/feed/presentation/bloc/feed_state.dart @@ -23,11 +23,6 @@ final class FeedState extends Equatable { this.username, this.cursor, this.message, - this.scrollId = 0, - this.dismissReadId = 0, - this.dismissBlockedUserId, - this.dismissBlockedCommunityId, - this.dismissHiddenPostId, this.insertedPostIds = const [], this.showHidden = false, this.showSaved = false, @@ -91,21 +86,6 @@ final class FeedState extends Equatable { /// The message to display on failure final String? message; - /// This id is used for scrolling back to the top - final int scrollId; - - /// This id is used for dismissing already read posts in the feed - final int dismissReadId; - - /// This id is used for dismissing posts from blocked users - final int? dismissBlockedUserId; - - /// This id is used for dismissing posts from blocked communities - final int? dismissBlockedCommunityId; - - /// This id is used for dismissing posts that have been hidden by the user - final int? dismissHiddenPostId; - /// The inserted post ids. This is used to prevent duplicate posts final List insertedPostIds; @@ -138,11 +118,6 @@ final class FeedState extends Equatable { String? username, String? cursor, String? message, - int? scrollId, - int? dismissReadId, - int? dismissBlockedUserId, - int? dismissBlockedCommunityId, - int? dismissHiddenPostId, List? insertedPostIds, bool? showHidden, bool? showSaved, @@ -168,11 +143,6 @@ final class FeedState extends Equatable { username: username ?? this.username, cursor: cursor ?? this.cursor, message: message, - scrollId: scrollId ?? this.scrollId, - dismissReadId: dismissReadId ?? this.dismissReadId, - dismissBlockedUserId: dismissBlockedUserId, - dismissBlockedCommunityId: dismissBlockedCommunityId, - dismissHiddenPostId: dismissHiddenPostId, insertedPostIds: insertedPostIds ?? this.insertedPostIds, showHidden: showHidden ?? this.showHidden, showSaved: showSaved ?? this.showSaved, @@ -206,11 +176,6 @@ final class FeedState extends Equatable { username, cursor, message, - scrollId, - dismissReadId, - dismissBlockedUserId, - dismissBlockedCommunityId, - dismissHiddenPostId, insertedPostIds, showHidden, showSaved, diff --git a/lib/src/features/feed/presentation/pages/feed_page.dart b/lib/src/features/feed/presentation/pages/feed_page.dart index 085a1eee1..ddfa17992 100644 --- a/lib/src/features/feed/presentation/pages/feed_page.dart +++ b/lib/src/features/feed/presentation/pages/feed_page.dart @@ -19,6 +19,12 @@ import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/nav_bar_state_cubit/nav_bar_state_cubit.dart'; +import 'package:thunder/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/navigation.dart'; @@ -201,19 +207,18 @@ class _FeedViewState extends State { final delta = currentScrollPosition - _previousScrollPosition; if (delta.abs() > _scrollThreshold) { - _cachedHideBottomBarOnScroll ??= context.read().state.hideBottomBarOnScroll; + _cachedHideBottomBarOnScroll ??= context.read().state.hideBottomBarOnScroll; // Still in ThunderBloc as it's a global setting if (_cachedHideBottomBarOnScroll == true) { - final bloc = context.read(); final isScrollingDown = delta > 0; - final isBottomNavBarVisible = bloc.state.isBottomNavBarVisible; + final isBottomNavBarVisible = context.read().state.isBottomNavBarVisible; // Only dispatch if the visibility state needs to change // Show nav bar when scrolling up, hide when scrolling down if (isScrollingDown && isBottomNavBarVisible) { - bloc.add(const OnBottomNavBarVisibilityChange(false)); + context.read().setBottomNavBarVisible(false); } else if (!isScrollingDown && !isBottomNavBarVisible) { - bloc.add(const OnBottomNavBarVisibilityChange(true)); + context.read().setBottomNavBarVisible(true); } } _previousScrollPosition = currentScrollPosition; @@ -233,7 +238,7 @@ class _FeedViewState extends State { /// /// Once those posts are fully added, an event is triggered which filters those posts from the feed bloc state Future dismissRead() async { - ThunderState state = context.read().state; + final useCompactView = context.read().state.useCompactView; FeedBloc feedBloc = context.read(); List posts = feedBloc.state.posts; @@ -242,7 +247,7 @@ class _FeedViewState extends State { for (ThunderPost post in posts) { if (post.read == true) { setState(() => queuedForRemoval.add(post.id)); - await Future.delayed(Duration(milliseconds: state.useCompactView ? 60 : 100)); + await Future.delayed(Duration(milliseconds: useCompactView ? 60 : 100)); } } @@ -254,7 +259,7 @@ class _FeedViewState extends State { } Future dismissBlockedUsersAndCommunities(int? userId, int? communityId) async { - ThunderState state = context.read().state; + final useCompactView = context.read().state.useCompactView; FeedBloc feedBloc = context.read(); List posts = feedBloc.state.posts; @@ -263,7 +268,7 @@ class _FeedViewState extends State { for (ThunderPost post in posts) { if (post.creator?.id == userId || post.community?.id == communityId) { setState(() => queuedForRemoval.add(post.id)); - await Future.delayed(Duration(milliseconds: state.useCompactView ? 60 : 100)); + await Future.delayed(Duration(milliseconds: useCompactView ? 60 : 100)); } } @@ -275,7 +280,7 @@ class _FeedViewState extends State { } Future dismissHiddenPost(int postId) async { - ThunderState state = context.read().state; + final useCompactView = context.read().state.useCompactView; FeedBloc feedBloc = context.read(); List posts = feedBloc.state.posts; @@ -284,7 +289,7 @@ class _FeedViewState extends State { for (ThunderPost post in posts) { if (post.id == postId) { setState(() => queuedForRemoval.add(post.id)); - await Future.delayed(Duration(milliseconds: state.useCompactView ? 60 : 100)); + await Future.delayed(Duration(milliseconds: useCompactView ? 60 : 100)); } } @@ -300,181 +305,207 @@ class _FeedViewState extends State { final l10n = GlobalContext.l10n; final tabletMode = context.select((bloc) => bloc.state.tabletMode); - final markPostReadOnScroll = context.select((bloc) => bloc.state.markPostReadOnScroll); + final markPostReadOnScroll = context.select((cubit) => cubit.state.markPostReadOnScroll); final hideTopBarOnScroll = context.select((bloc) => bloc.state.hideTopBarOnScroll); - final isFabOpen = context.select((bloc) => bloc.state.isFabOpen); - final enableFeedsFab = context.select((bloc) => bloc.state.enableFeedsFab); - final showHiddenPosts = context.select((bloc) => bloc.state.showHiddenPosts); - - return Scaffold( - body: SafeArea( - top: false, - child: BlocConsumer( - listenWhen: (previous, current) { - if (previous.scrollId != current.scrollId) _scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); - if (previous.dismissReadId != current.dismissReadId) dismissRead(); - if (current.dismissBlockedUserId != null || current.dismissBlockedCommunityId != null) dismissBlockedUsersAndCommunities(current.dismissBlockedUserId, current.dismissBlockedCommunityId); - if (current.dismissHiddenPostId != null && !showHiddenPosts) dismissHiddenPost(current.dismissHiddenPostId!); - if (current.excessiveApiCalls) { - showSnackbar( - l10n.excessiveApiCallsWarning, - trailingIcon: Icons.settings_rounded, - trailingAction: () => navigateToSettingPage(context, LocalSettings.settingsPageFilters, settingToHighlight: LocalSettings.keywordFilters), - ); - } - return true; - }, - listener: (context, state) { - // Continue to fetch more items as long as the device view is not scrollable. - // This is to avoid cases where more items cannot be fetched because the conditions are not met - if (state.status == FeedStatus.success && ((selectedUserOption[0] && state.hasReachedPostsEnd == false) || (selectedUserOption[1] && state.hasReachedCommentsEnd == false))) { - Future.delayed(const Duration(milliseconds: 1000), () { - if (!mounted) return; - bool isScrollable = _scrollController.position.maxScrollExtent > _scrollController.position.viewportDimension; - if (!isScrollable) context.read().add(const FeedFetchedEvent()); - }); - } - - if ((state.status == FeedStatus.failure || state.status == FeedStatus.failureLoadingCommunity || state.status == FeedStatus.failureLoadingUser) && state.message != null) { - showSnackbar(state.message!); - context.read().add(FeedClearMessageEvent()); // Clear the message so that it does not spam - } - }, - builder: (context, state) { - final theme = Theme.of(context); - List posts = state.posts; - List comments = state.comments; - - return RefreshIndicator( - onRefresh: () async { - HapticFeedback.mediumImpact(); - triggerRefresh(context); - }, - edgeOffset: MediaQuery.of(context).padding.top + APP_BAR_HEIGHT, // This offset is placed to allow the correct positioning of the refresh indicator - child: Stack( - children: [ - CustomScrollView( - controller: _scrollController, - slivers: [ - widget.feedType == FeedType.account - ? AccountPageAppBar(scrollController: _scrollController) - : FeedPageAppBar(scrollController: _scrollController, scaffoldStateKey: widget.scaffoldStateKey), - // Display loading indicator until the feed is fetched - if (state.status == FeedStatus.initial) - const SliverFillRemaining( - hasScrollBody: false, - child: Center(child: CircularProgressIndicator()), - ), - if (state.status == FeedStatus.failureLoadingCommunity || state.status == FeedStatus.failureLoadingUser) - SliverToBoxAdapter( - child: Container(), - ), - // Display tagline and list of posts once they are fetched - if (state.status != FeedStatus.initial && (state.status != FeedStatus.failureLoadingCommunity || state.status != FeedStatus.failureLoadingUser)) ...[ - SliverToBoxAdapter( - child: Visibility( - visible: state.feedType == FeedType.general && state.status != FeedStatus.initial, - child: TagLine(), + final isFabOpen = context.select((cubit) => cubit.state.isFeedFabOpen); + final enableFeedsFab = context.select((cubit) => cubit.state.enableFeedsFab); + final showHiddenPosts = context.select((cubit) => cubit.state.showHiddenPosts); + + return BlocListener( + listenWhen: (previous, current) { + if (previous.scrollId != current.scrollId) { + _scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); + } + if (previous.dismissReadId != current.dismissReadId) { + dismissRead(); + } + if (current.dismissBlockedUserId != null || current.dismissBlockedCommunityId != null) { + dismissBlockedUsersAndCommunities(current.dismissBlockedUserId, current.dismissBlockedCommunityId); + } + if (current.dismissHiddenPostId != null && !showHiddenPosts) { + dismissHiddenPost(current.dismissHiddenPostId!); + } + return true; + }, + listener: (context, state) {}, + child: Scaffold( + body: SafeArea( + top: false, + child: BlocConsumer( + listenWhen: (previous, current) { + if (current.excessiveApiCalls) { + showSnackbar( + l10n.excessiveApiCallsWarning, + trailingIcon: Icons.settings_rounded, + trailingAction: () => navigateToSettingPage(context, LocalSettings.settingsPageFilters, settingToHighlight: LocalSettings.keywordFilters), + ); + } + return true; + }, + buildWhen: (previous, current) => + previous.status != current.status || + previous.posts != current.posts || + previous.comments != current.comments || + previous.hasReachedPostsEnd != current.hasReachedPostsEnd || + previous.hasReachedCommentsEnd != current.hasReachedCommentsEnd || + previous.feedType != current.feedType || + previous.community != current.community || + previous.communityInstance != current.communityInstance || + previous.communityModerators != current.communityModerators || + previous.user != current.user || + previous.userModerates != current.userModerates, + listener: (context, state) { + // Continue to fetch more items as long as the device view is not scrollable. + // This is to avoid cases where more items cannot be fetched because the conditions are not met + if (state.status == FeedStatus.success && ((selectedUserOption[0] && state.hasReachedPostsEnd == false) || (selectedUserOption[1] && state.hasReachedCommentsEnd == false))) { + Future.delayed(const Duration(milliseconds: 1000), () { + if (!mounted) return; + bool isScrollable = _scrollController.position.maxScrollExtent > _scrollController.position.viewportDimension; + if (!isScrollable) context.read().add(const FeedFetchedEvent()); + }); + } + + if ((state.status == FeedStatus.failure || state.status == FeedStatus.failureLoadingCommunity || state.status == FeedStatus.failureLoadingUser) && state.message != null) { + showSnackbar(state.message!); + context.read().add(FeedClearMessageEvent()); // Clear the message so that it does not spam + } + }, + builder: (context, state) { + final theme = Theme.of(context); + List posts = state.posts; + List comments = state.comments; + + return RefreshIndicator( + onRefresh: () async { + HapticFeedback.mediumImpact(); + triggerRefresh(context); + }, + edgeOffset: MediaQuery.of(context).padding.top + APP_BAR_HEIGHT, // This offset is placed to allow the correct positioning of the refresh indicator + child: Stack( + children: [ + CustomScrollView( + controller: _scrollController, + slivers: [ + widget.feedType == FeedType.account + ? AccountPageAppBar(scrollController: _scrollController) + : FeedPageAppBar(scrollController: _scrollController, scaffoldStateKey: widget.scaffoldStateKey), + // Display loading indicator until the feed is fetched + if (state.status == FeedStatus.initial) + const SliverFillRemaining( + hasScrollBody: false, + child: Center(child: CircularProgressIndicator()), ), - ), - if (state.community != null && state.feedType == FeedType.community) + if (state.status == FeedStatus.failureLoadingCommunity || state.status == FeedStatus.failureLoadingUser) SliverToBoxAdapter( - child: CommunityHeader( - community: state.community!, - instance: state.communityInstance, - moderators: state.communityModerators, - condensed: false, - ), + child: Container(), ), - if (state.user != null && (state.feedType == FeedType.user || state.feedType == FeedType.account)) + // Display tagline and list of posts once they are fetched + if (state.status != FeedStatus.initial && (state.status != FeedStatus.failureLoadingCommunity || state.status != FeedStatus.failureLoadingUser)) ...[ SliverToBoxAdapter( - child: UserHeader( - user: state.user!, - moderates: state.userModerates, - feedType: selectedUserOption[0] ? FeedTypeSubview.post : FeedTypeSubview.comment, - onChangeFeedType: (feedType) { - setState(() { - selectedUserOption[0] = feedType == FeedTypeSubview.post; - selectedUserOption[1] = feedType == FeedTypeSubview.comment; - }); - }, - condensed: false, + child: Visibility( + visible: state.feedType == FeedType.general && state.status != FeedStatus.initial, + child: TagLine(), ), ), - selectedUserOption[1] - // Widget representing the list of user comments on the feed - ? FeedCommentCardList( - comments: comments, - tabletMode: tabletMode, - ) - : - // Widget representing the list of posts on the feed - FeedPostCardList( - posts: posts, - tabletMode: tabletMode, - markPostReadOnScroll: markPostReadOnScroll, - queuedForRemoval: queuedForRemoval, - dimReadPosts: state.feedType == FeedType.account ? false : null, + if (state.community != null && state.feedType == FeedType.community) + SliverToBoxAdapter( + child: CommunityHeader( + community: state.community!, + instance: state.communityInstance, + moderators: state.communityModerators, + condensed: false, ), - // Widget representing the bottom of the feed (reached end or loading more posts indicators) - if (state.status != FeedStatus.failureLoadingCommunity && state.status != FeedStatus.failureLoadingUser) - SliverToBoxAdapter( - child: ((selectedUserOption[0] && state.hasReachedPostsEnd) || (selectedUserOption[1] && state.hasReachedCommentsEnd)) - ? const FeedReachedEnd() - : Container( - height: state.status == FeedStatus.initial ? MediaQuery.of(context).size.height * 0.5 : null, // Might have to adjust this to be more robust - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: const CircularProgressIndicator(), - ), - ), - ], - ], - ), - // Widget to host the feed FAB when navigating to new page - AnimatedOpacity( - opacity: isFabOpen ? 1.0 : 0.0, - curve: Curves.easeInOut, - duration: const Duration(milliseconds: 250), - child: Stack( - children: [ - IgnorePointer( - child: Container( - color: theme.colorScheme.surface.withValues(alpha: 0.95), - )), - if (isFabOpen) - ModalBarrier( - color: null, - dismissible: true, - onDismiss: () => context.read().add(const OnFabToggle(false)), - ), + ), + if (state.user != null && (state.feedType == FeedType.user || state.feedType == FeedType.account)) + SliverToBoxAdapter( + child: UserHeader( + user: state.user!, + moderates: state.userModerates, + feedType: selectedUserOption[0] ? FeedTypeSubview.post : FeedTypeSubview.comment, + onChangeFeedType: (feedType) { + setState(() { + selectedUserOption[0] = feedType == FeedTypeSubview.post; + selectedUserOption[1] = feedType == FeedTypeSubview.comment; + }); + }, + condensed: false, + ), + ), + selectedUserOption[1] + // Widget representing the list of user comments on the feed + ? FeedCommentCardList( + comments: comments, + tabletMode: tabletMode, + ) + : + // Widget representing the list of posts on the feed + FeedPostCardList( + posts: posts, + tabletMode: tabletMode, + markPostReadOnScroll: markPostReadOnScroll, + queuedForRemoval: queuedForRemoval, + dimReadPosts: state.feedType == FeedType.account ? false : null, + ), + // Widget representing the bottom of the feed (reached end or loading more posts indicators) + if (state.status != FeedStatus.failureLoadingCommunity && state.status != FeedStatus.failureLoadingUser) + SliverToBoxAdapter( + child: ((selectedUserOption[0] && state.hasReachedPostsEnd) || (selectedUserOption[1] && state.hasReachedCommentsEnd)) + ? const FeedReachedEnd() + : Container( + height: state.status == FeedStatus.initial ? MediaQuery.of(context).size.height * 0.5 : null, // Might have to adjust this to be more robust + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: const CircularProgressIndicator(), + ), + ), + ], ], ), - ), - if (Navigator.of(context).canPop() && - (state.communityId != null || state.communityName != null || state.userId != null || state.username != null) && - enableFeedsFab && - state.feedType != FeedType.account) + // Widget to host the feed FAB when navigating to new page AnimatedOpacity( - opacity: enableFeedsFab ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - curve: Curves.easeIn, - child: Container( - margin: const EdgeInsets.all(16), - child: FeedFAB(heroTag: state.communityName ?? state.username), + opacity: isFabOpen ? 1.0 : 0.0, + curve: Curves.easeInOut, + duration: const Duration(milliseconds: 250), + child: Stack( + children: [ + IgnorePointer( + child: Container( + color: theme.colorScheme.surface.withValues(alpha: 0.95), + )), + if (isFabOpen) + ModalBarrier( + color: null, + dismissible: true, + onDismiss: () => context.read().setFeedFabOpen(false), + ), + ], ), ), - if (hideTopBarOnScroll) - Positioned( - child: Container( - height: MediaQuery.of(context).padding.top, - color: theme.colorScheme.surface, + if (Navigator.of(context).canPop() && + (state.communityId != null || state.communityName != null || state.userId != null || state.username != null) && + enableFeedsFab && + state.feedType != FeedType.account) + AnimatedOpacity( + opacity: enableFeedsFab ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + curve: Curves.easeIn, + child: Container( + margin: const EdgeInsets.all(16), + child: FeedFAB(heroTag: state.communityName ?? state.username), + ), ), - ) - ], - ), - ); - }, + if (hideTopBarOnScroll) + Positioned( + child: Container( + height: MediaQuery.of(context).padding.top, + color: theme.colorScheme.surface, + ), + ) + ], + ), + ); + }, + ), ), ), ); @@ -483,7 +514,7 @@ class _FeedViewState extends State { FutureOr _handleBack(bool stopDefaultButtonEvent, RouteInfo info) async { ProfileBloc authBloc = context.read(); FeedBloc feedBloc = context.read(); - ThunderBloc thunderBloc = context.read(); + final feedCubit = context.read(); // See if we're at the top level of navigation final canPop = Navigator.of(context).canPop(); @@ -493,7 +524,7 @@ class _FeedViewState extends State { } // Get the desired post listing so we can check against current - final desiredFeedListType = authBloc.state.siteResponse?.myUser?.localUserView.localUser.defaultListingType ?? thunderBloc.state.defaultFeedListType; + final desiredFeedListType = authBloc.state.siteResponse?.myUser?.localUserView.localUser.defaultListingType ?? feedCubit.state.defaultFeedListType; final currentFeedListType = feedBloc.state.feedListType; // See if we're in a community @@ -505,7 +536,7 @@ class _FeedViewState extends State { // - We're on a community // THEN navigate to the desired listing type if (!canPop && (desiredFeedListType != currentFeedListType || communityMode)) { - final postSortType = authBloc.state.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? thunderBloc.state.postSortTypeForInstance; + final postSortType = authBloc.state.siteResponse?.myUser?.localUserView.localUser.defaultSortType ?? feedCubit.state.defaultPostSortType; feedBloc.add( FeedFetchedEvent( @@ -514,7 +545,7 @@ class _FeedViewState extends State { feedListType: desiredFeedListType, feedType: FeedType.general, communityId: null, - showHidden: thunderBloc.state.showHiddenPosts, + showHidden: feedCubit.state.showHiddenPosts, ), ); @@ -577,7 +608,7 @@ class FeedReachedEnd extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final theme = Theme.of(context); - final state = context.read().state; + final metadataFontSizeScale = context.read().state.metadataFontSizeScale; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -589,7 +620,7 @@ class FeedReachedEnd extends StatelessWidget { l10n.reachedTheBottom, textAlign: TextAlign.center, style: theme.textTheme.titleSmall, - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, ), ), const SizedBox(height: 160) diff --git a/lib/src/features/feed/presentation/widgets/feed_card_divider.dart b/lib/src/features/feed/presentation/widgets/feed_card_divider.dart index a834d0d9a..087d4c9db 100644 --- a/lib/src/features/feed/presentation/widgets/feed_card_divider.dart +++ b/lib/src/features/feed/presentation/widgets/feed_card_divider.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; /// A user-customizable divider used between items (posts/comments) in the feed page. /// @@ -13,8 +13,8 @@ class FeedCardDivider extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final thickness = context.select((bloc) => bloc.state.feedCardDividerThickness.value); - final dividerColor = context.select((bloc) => bloc.state.feedCardDividerColor); + final thickness = context.select((cubit) => cubit.state.feedCardDividerThickness.value); + final dividerColor = context.select((cubit) => cubit.state.feedCardDividerColor); Color color = ElevationOverlay.applySurfaceTint(theme.colorScheme.surface, theme.colorScheme.surfaceTint, 10); diff --git a/lib/src/features/feed/presentation/widgets/feed_fab.dart b/lib/src/features/feed/presentation/widgets/feed_fab.dart index 6e8447809..f36b0e40f 100644 --- a/lib/src/features/feed/presentation/widgets/feed_fab.dart +++ b/lib/src/features/feed/presentation/widgets/feed_fab.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; +import 'package:thunder/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/core/enums/fab_action.dart'; @@ -14,7 +15,8 @@ import 'package:thunder/src/app/utils/navigation.dart'; import 'package:thunder/src/shared/gesture_fab.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/shared/sort_picker.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart'; class FeedFAB extends StatelessWidget { const FeedFAB({super.key, this.heroTag}); @@ -24,7 +26,9 @@ class FeedFAB extends StatelessWidget { @override build(BuildContext context) { final theme = Theme.of(context); - final ThunderState state = context.watch().state; + final feedFabSinglePressAction = context.select((cubit) => cubit.state.feedFabSinglePressAction); + final feedFabLongPressAction = context.select((cubit) => cubit.state.feedFabLongPressAction); + final isFabSummoned = context.select((cubit) => cubit.state.isFeedFabSummoned); final FeedState feedState = context.watch().state; final ProfileState profileState = context.read().state; @@ -43,8 +47,8 @@ class FeedFAB extends StatelessWidget { FeedFabAction.dismissRead, ]; - FeedFabAction singlePressAction = state.feedFabSinglePressAction; - FeedFabAction longPressAction = state.feedFabLongPressAction; + FeedFabAction singlePressAction = feedFabSinglePressAction; + FeedFabAction longPressAction = feedFabLongPressAction; // Check to see if we are in the general feeds bool isGeneralFeed = feedState.status != FeedStatus.initial && feedState.feedType == FeedType.general; @@ -100,7 +104,7 @@ class FeedFAB extends StatelessWidget { child: child, ); }, - child: state.isFabSummoned + child: isFabSummoned ? GestureFab( heroTag: heroTag, distance: 60, @@ -164,6 +168,7 @@ class FeedFAB extends StatelessWidget { break; } }, + fabType: FabType.feed, children: getEnabledActions(context, isPostingLocked: isPostLocked, disabledActions: disabledActions), ) : Stack( @@ -176,7 +181,7 @@ class FeedFAB extends StatelessWidget { child: GestureDetector( onVerticalDragEnd: (DragEndDetails details) { if (details.primaryVelocity! < 0) { - context.read().add(const OnFabSummonToggle(true)); + context.read().setFeedFabSummoned(true); } }, ), @@ -188,14 +193,12 @@ class FeedFAB extends StatelessWidget { List getEnabledActions(BuildContext context, {bool isPostingLocked = false, List disabledActions = const []}) { final theme = Theme.of(context); - final ThunderState state = context.watch().state; - - bool enableBackToTop = state.enableBackToTop && !disabledActions.contains(FeedFabAction.backToTop); - bool enableSubscriptions = state.enableSubscriptions && !disabledActions.contains(FeedFabAction.subscriptions); - bool enableChangeSort = state.enableChangeSort && !disabledActions.contains(FeedFabAction.changeSort); - bool enableRefresh = state.enableRefresh && !disabledActions.contains(FeedFabAction.refresh); - bool enableDismissRead = state.enableDismissRead && !disabledActions.contains(FeedFabAction.dismissRead); - bool enableNewPost = state.enableNewPost && !disabledActions.contains(FeedFabAction.newPost); + final enableBackToTop = context.select((cubit) => cubit.state.enableBackToTop) && !disabledActions.contains(FeedFabAction.backToTop); + final enableSubscriptions = context.select((cubit) => cubit.state.enableSubscriptions) && !disabledActions.contains(FeedFabAction.subscriptions); + final enableChangeSort = context.select((cubit) => cubit.state.enableChangeSort) && !disabledActions.contains(FeedFabAction.changeSort); + final enableRefresh = context.select((cubit) => cubit.state.enableRefresh) && !disabledActions.contains(FeedFabAction.refresh); + final enableDismissRead = context.select((cubit) => cubit.state.enableDismissRead) && !disabledActions.contains(FeedFabAction.dismissRead); + final enableNewPost = context.select((cubit) => cubit.state.enableNewPost) && !disabledActions.contains(FeedFabAction.newPost); List actions = [ if (enableDismissRead) @@ -259,11 +262,11 @@ class FeedFAB extends StatelessWidget { } Future triggerOpenFab(BuildContext context) async { - context.read().add(const OnFabToggle(true)); + context.read().setFeedFabOpen(true); } Future triggerDismissRead(BuildContext context) async { - context.read().add(FeedDismissReadEvent()); + context.read().dismissRead(); } Future triggerChangeSort(BuildContext context) async { @@ -288,7 +291,7 @@ class FeedFAB extends StatelessWidget { } Future triggerScrollToTop(BuildContext context) async { - context.read().add(ScrollToTopEvent()); + context.read().scrollToTop(); } Future triggerNewPost(BuildContext context, {bool isPostingLocked = false}) async { diff --git a/lib/src/features/feed/presentation/widgets/feed_post_card_list.dart b/lib/src/features/feed/presentation/widgets/feed_post_card_list.dart index 64285262a..8e748acd7 100644 --- a/lib/src/features/feed/presentation/widgets/feed_post_card_list.dart +++ b/lib/src/features/feed/presentation/widgets/feed_post_card_list.dart @@ -12,6 +12,7 @@ import 'package:thunder/src/core/enums/enums.dart'; import 'package:thunder/src/features/community/community.dart'; import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; /// Widget representing the list of posts on the feed. class FeedPostCardList extends StatefulWidget { @@ -160,27 +161,24 @@ class _FeedPostCardListState extends State { final isQueuedForRemoval = widget.queuedForRemoval?.contains(post.id) == true; if (isQueuedForRemoval) { - return AnimatedSwitcher( - switchOutCurve: Curves.ease, - duration: Duration.zero, - reverseDuration: const Duration(milliseconds: 400), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: animation, curve: const Interval(0.5, 1.0)), - ), - child: SlideTransition( - position: Tween(begin: const Offset(1.2, 0.0), end: const Offset(0.0, 0.0)).animate(animation), - child: SizeTransition( - sizeFactor: Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: animation, curve: const Interval(0.0, 0.25)), + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 300), + tween: Tween(begin: 1.0, end: 0.0), + builder: (context, value, animatedChild) { + return ClipRect( + child: Align( + alignment: Alignment.topCenter, + heightFactor: value, + child: Opacity( + opacity: value, + child: Transform.translate( + offset: Offset((1 - value) * 100, 0), + child: child, ), - child: child, ), ), ); }, - child: null, // Post is being removed, animate out ); } @@ -189,10 +187,11 @@ class _FeedPostCardListState extends State { @override Widget build(BuildContext context) { - final state = context.read().state; - final isUserLoggedIn = context.read().state.isLoggedIn; + final feedType = context.select((bloc) => bloc.state.feedType); + final feedListType = context.select((bloc) => bloc.state.feedListType); + final isUserLoggedIn = context.select((bloc) => bloc.state.isLoggedIn); - bool dimReadPosts = widget.dimReadPosts ?? (isUserLoggedIn && context.read().state.dimReadPosts); + bool dimReadPosts = widget.dimReadPosts ?? (isUserLoggedIn && context.select((cubit) => cubit.state.dimReadPosts)); if (widget.tabletMode) { return SliverMasonryGrid.count( @@ -204,8 +203,8 @@ class _FeedPostCardListState extends State { post: widget.posts[index], index: index, dim: widget.indicateRead ?? dimReadPosts, - feedType: state.feedType, - feedListType: state.feedListType, + feedType: feedType, + feedListType: feedListType, isUserLoggedIn: isUserLoggedIn, ); }, @@ -219,8 +218,8 @@ class _FeedPostCardListState extends State { post: widget.posts[index], index: index, dim: widget.indicateRead ?? dimReadPosts, - feedType: state.feedType, - feedListType: state.feedListType, + feedType: feedType, + feedListType: feedListType, isUserLoggedIn: isUserLoggedIn, ); }, diff --git a/lib/src/features/feed/presentation/widgets/tagline.dart b/lib/src/features/feed/presentation/widgets/tagline.dart index 775f84378..c9b56a179 100644 --- a/lib/src/features/feed/presentation/widgets/tagline.dart +++ b/lib/src/features/feed/presentation/widgets/tagline.dart @@ -8,7 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/colors.dart'; /// Displays a random tagline from the site whenever the feed is refreshed. @@ -53,7 +53,7 @@ class _TagLineState extends State { final theme = Theme.of(context); final l10n = AppLocalizations.of(context)!; - final showExpandedTaglines = context.select((bloc) => bloc.state.showExpandedTaglines); + final showExpandedTaglines = context.select((cubit) => cubit.state.showExpandedTaglines); if (tagline == null || tagline?.isEmpty == true) return const SizedBox.shrink(); diff --git a/lib/src/features/moderator/presentation/pages/report_page.dart b/lib/src/features/moderator/presentation/pages/report_page.dart index ad4c4259f..2c0ff9dca 100644 --- a/lib/src/features/moderator/presentation/pages/report_page.dart +++ b/lib/src/features/moderator/presentation/pages/report_page.dart @@ -15,7 +15,8 @@ import 'package:thunder/src/shared/comment_reference.dart'; import 'package:thunder/src/shared/full_name_widgets.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/shared/utils/instance.dart'; @@ -77,7 +78,7 @@ class _ReportFeedViewState extends State { @override Widget build(BuildContext context) { - final thunderState = context.watch().state; + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); final l10n = AppLocalizations.of(context)!; final theme = Theme.of(context); @@ -249,7 +250,7 @@ class _ReportFeedViewState extends State { l10n.detailedReason(state.postReports[index].reason), maxLines: 4, overflow: TextOverflow.ellipsis, - fontScale: thunderState.contentFontSizeScale, + fontScale: contentFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.error, fontWeight: FontWeight.w600, @@ -352,7 +353,7 @@ class _ReportFeedViewState extends State { l10n.detailedReason(state.commentReports[index].reason), maxLines: 4, overflow: TextOverflow.ellipsis, - fontScale: thunderState.contentFontSizeScale, + fontScale: contentFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.error, fontWeight: FontWeight.w600, diff --git a/lib/src/features/modlog/presentation/bloc/modlog_cubit.freezed.dart b/lib/src/features/modlog/presentation/bloc/modlog_cubit.freezed.dart index edd450b46..fe59261da 100644 --- a/lib/src/features/modlog/presentation/bloc/modlog_cubit.freezed.dart +++ b/lib/src/features/modlog/presentation/bloc/modlog_cubit.freezed.dart @@ -35,8 +35,7 @@ mixin _$ModlogState { int? get commentId => throw _privateConstructorUsedError; /// The list of modlog events. - List get modlogEventItems => - throw _privateConstructorUsedError; + List get modlogEventItems => throw _privateConstructorUsedError; /// Whether the end of the modlog has been reached. bool get hasReachedEnd => throw _privateConstructorUsedError; @@ -50,15 +49,12 @@ mixin _$ModlogState { /// Create a copy of ModlogState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $ModlogStateCopyWith get copyWith => - throw _privateConstructorUsedError; + $ModlogStateCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc abstract class $ModlogStateCopyWith<$Res> { - factory $ModlogStateCopyWith( - ModlogState value, $Res Function(ModlogState) then) = - _$ModlogStateCopyWithImpl<$Res, ModlogState>; + factory $ModlogStateCopyWith(ModlogState value, $Res Function(ModlogState) then) = _$ModlogStateCopyWithImpl<$Res, ModlogState>; @useResult $Res call( {ModlogStatus status, @@ -74,8 +70,7 @@ abstract class $ModlogStateCopyWith<$Res> { } /// @nodoc -class _$ModlogStateCopyWithImpl<$Res, $Val extends ModlogState> - implements $ModlogStateCopyWith<$Res> { +class _$ModlogStateCopyWithImpl<$Res, $Val extends ModlogState> implements $ModlogStateCopyWith<$Res> { _$ModlogStateCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -145,11 +140,8 @@ class _$ModlogStateCopyWithImpl<$Res, $Val extends ModlogState> } /// @nodoc -abstract class _$$ModlogStateImplCopyWith<$Res> - implements $ModlogStateCopyWith<$Res> { - factory _$$ModlogStateImplCopyWith( - _$ModlogStateImpl value, $Res Function(_$ModlogStateImpl) then) = - __$$ModlogStateImplCopyWithImpl<$Res>; +abstract class _$$ModlogStateImplCopyWith<$Res> implements $ModlogStateCopyWith<$Res> { + factory _$$ModlogStateImplCopyWith(_$ModlogStateImpl value, $Res Function(_$ModlogStateImpl) then) = __$$ModlogStateImplCopyWithImpl<$Res>; @override @useResult $Res call( @@ -166,12 +158,8 @@ abstract class _$$ModlogStateImplCopyWith<$Res> } /// @nodoc -class __$$ModlogStateImplCopyWithImpl<$Res> - extends _$ModlogStateCopyWithImpl<$Res, _$ModlogStateImpl> - implements _$$ModlogStateImplCopyWith<$Res> { - __$$ModlogStateImplCopyWithImpl( - _$ModlogStateImpl _value, $Res Function(_$ModlogStateImpl) _then) - : super(_value, _then); +class __$$ModlogStateImplCopyWithImpl<$Res> extends _$ModlogStateCopyWithImpl<$Res, _$ModlogStateImpl> implements _$$ModlogStateImplCopyWith<$Res> { + __$$ModlogStateImplCopyWithImpl(_$ModlogStateImpl _value, $Res Function(_$ModlogStateImpl) _then) : super(_value, _then); /// Create a copy of ModlogState /// with the given fields replaced by the non-null parameter values. @@ -284,8 +272,7 @@ class _$ModlogStateImpl extends _ModlogState { @override @JsonKey() List get modlogEventItems { - if (_modlogEventItems is EqualUnmodifiableListView) - return _modlogEventItems; + if (_modlogEventItems is EqualUnmodifiableListView) return _modlogEventItems; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_modlogEventItems); } @@ -315,45 +302,27 @@ class _$ModlogStateImpl extends _ModlogState { (other.runtimeType == runtimeType && other is _$ModlogStateImpl && (identical(other.status, status) || other.status == status) && - (identical(other.modlogActionType, modlogActionType) || - other.modlogActionType == modlogActionType) && - (identical(other.communityId, communityId) || - other.communityId == communityId) && + (identical(other.modlogActionType, modlogActionType) || other.modlogActionType == modlogActionType) && + (identical(other.communityId, communityId) || other.communityId == communityId) && (identical(other.userId, userId) || other.userId == userId) && - (identical(other.moderatorId, moderatorId) || - other.moderatorId == moderatorId) && - (identical(other.commentId, commentId) || - other.commentId == commentId) && - const DeepCollectionEquality() - .equals(other._modlogEventItems, _modlogEventItems) && - (identical(other.hasReachedEnd, hasReachedEnd) || - other.hasReachedEnd == hasReachedEnd) && - (identical(other.currentPage, currentPage) || - other.currentPage == currentPage) && + (identical(other.moderatorId, moderatorId) || other.moderatorId == moderatorId) && + (identical(other.commentId, commentId) || other.commentId == commentId) && + const DeepCollectionEquality().equals(other._modlogEventItems, _modlogEventItems) && + (identical(other.hasReachedEnd, hasReachedEnd) || other.hasReachedEnd == hasReachedEnd) && + (identical(other.currentPage, currentPage) || other.currentPage == currentPage) && (identical(other.message, message) || other.message == message)); } @override - int get hashCode => Object.hash( - runtimeType, - status, - modlogActionType, - communityId, - userId, - moderatorId, - commentId, - const DeepCollectionEquality().hash(_modlogEventItems), - hasReachedEnd, - currentPage, - message); + int get hashCode => + Object.hash(runtimeType, status, modlogActionType, communityId, userId, moderatorId, commentId, const DeepCollectionEquality().hash(_modlogEventItems), hasReachedEnd, currentPage, message); /// Create a copy of ModlogState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ModlogStateImplCopyWith<_$ModlogStateImpl> get copyWith => - __$$ModlogStateImplCopyWithImpl<_$ModlogStateImpl>(this, _$identity); + _$$ModlogStateImplCopyWith<_$ModlogStateImpl> get copyWith => __$$ModlogStateImplCopyWithImpl<_$ModlogStateImpl>(this, _$identity); } abstract class _ModlogState extends ModlogState { @@ -414,6 +383,5 @@ abstract class _ModlogState extends ModlogState { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ModlogStateImplCopyWith<_$ModlogStateImpl> get copyWith => - throw _privateConstructorUsedError; + _$$ModlogStateImplCopyWith<_$ModlogStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/features/modlog/presentation/widgets/modlog_item_card.dart b/lib/src/features/modlog/presentation/widgets/modlog_item_card.dart index 479593b3a..57e792208 100644 --- a/lib/src/features/modlog/presentation/widgets/modlog_item_card.dart +++ b/lib/src/features/modlog/presentation/widgets/modlog_item_card.dart @@ -7,7 +7,7 @@ import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/features/modlog/modlog.dart'; import 'package:thunder/src/shared/divider.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; /// Widget to display a single modlog event item @@ -46,9 +46,9 @@ class ModlogItemCard extends StatelessWidget { final l10n = GlobalContext.l10n; final modName = getModeratorName(event); - final metadataFontSizeScale = context.select((state) => state.state.metadataFontSizeScale); - final titleFontSizeScale = context.select((state) => state.state.titleFontSizeScale); - final contentFontSizeScale = context.select((state) => state.state.contentFontSizeScale); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); + final titleFontSizeScale = context.select((cubit) => cubit.state.titleFontSizeScale); + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); return Column( children: [ diff --git a/lib/src/features/modlog/presentation/widgets/modlog_item_context_card.dart b/lib/src/features/modlog/presentation/widgets/modlog_item_context_card.dart index d3ed5c8be..71504d6c5 100644 --- a/lib/src/features/modlog/presentation/widgets/modlog_item_context_card.dart +++ b/lib/src/features/modlog/presentation/widgets/modlog_item_context_card.dart @@ -18,7 +18,7 @@ import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; import 'package:thunder/src/shared/full_name_widgets.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/instance.dart'; /// Provides some additional context for a [ModlogEventItem] @@ -95,7 +95,8 @@ class ModlogPostItemContextCard extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final l10n = AppLocalizations.of(context)!; - final state = context.watch().state; + final titleFontSizeScale = context.select((cubit) => cubit.state.titleFontSizeScale); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); return InkWell( onTap: () { @@ -117,7 +118,7 @@ class ModlogPostItemContextCard extends StatelessWidget { ScalableText( HtmlUnescape().convert(post.name), style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), - fontScale: state.titleFontSizeScale, + fontScale: titleFontSizeScale, ), Padding( padding: const EdgeInsets.only(right: 6.0, top: 6.0), @@ -129,7 +130,7 @@ class ModlogPostItemContextCard extends StatelessWidget { community?.name, community?.title, fetchInstanceNameFromUrl(community?.actorId), - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, transformColor: (color) => color?.withValues(alpha: 0.75), ), ), @@ -181,7 +182,8 @@ class _ModlogCommentItemContextCardState extends State().state; + final titleFontSizeScale = context.select((cubit) => cubit.state.titleFontSizeScale); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); Color? textStyleCommunityAndAuthor(Color? color) => color?.withValues(alpha: 0.75); @@ -213,7 +215,7 @@ class _ModlogCommentItemContextCardState extends State navigateToFeedPage(context, feedType: FeedType.user, userId: widget.user?.id), child: ScalableText( '${widget.user?.displayName ?? widget.user?.displayNameOrName}', - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith(color: textStyleCommunityAndAuthor(theme.textTheme.bodyMedium?.color)), ), ), ScalableText( ' in ', - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: theme.textTheme.bodyMedium?.color?.withValues(alpha: 0.4), @@ -285,7 +287,7 @@ class _ModlogCommentItemContextCardState extends State().state; + final titleFontSizeScale = context.select((cubit) => cubit.state.titleFontSizeScale); return InkWell( onTap: () { @@ -339,7 +341,7 @@ class ModlogUserItemContextCard extends StatelessWidget { ScalableText( HtmlUnescape().convert(user?.displayName ?? user?.displayNameOrName ?? l10n.user), style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), - fontScale: state.titleFontSizeScale, + fontScale: titleFontSizeScale, ), UserFullNameWidget( context, @@ -370,7 +372,8 @@ class ModlogCommunityItemContextCard extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final l10n = AppLocalizations.of(context)!; - final state = context.watch().state; + final titleFontSizeScale = context.select((cubit) => cubit.state.titleFontSizeScale); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); return InkWell( onTap: () { @@ -396,14 +399,14 @@ class ModlogCommunityItemContextCard extends StatelessWidget { ScalableText( HtmlUnescape().convert(community?.title ?? community?.name ?? l10n.community), style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), - fontScale: state.titleFontSizeScale, + fontScale: titleFontSizeScale, ), CommunityFullNameWidget( context, community?.name, community?.title, fetchInstanceNameFromUrl(community?.actorId), - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, transformColor: (color) => color?.withValues(alpha: 0.75), ), ], diff --git a/lib/src/features/post/domain/enums/post_status.dart b/lib/src/features/post/domain/enums/post_status.dart index 6d43c32f7..4443f2879 100644 --- a/lib/src/features/post/domain/enums/post_status.dart +++ b/lib/src/features/post/domain/enums/post_status.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; enum PostStatusType { @@ -22,13 +22,14 @@ enum PostStatusType { double getScaledSize(double textScaleFactor) => size * textScaleFactor; Color getColor(BuildContext context) { + final themeState = context.read().state; switch (this) { case PostStatusType.hidden: - return context.read().state.hideColor.color; + return themeState.hideColor.color; case PostStatusType.locked: - return context.read().state.upvoteColor.color; + return themeState.upvoteColor.color; case PostStatusType.saved: - return context.read().state.saveColor.color; + return themeState.saveColor.color; case PostStatusType.pinned: return color!; case PostStatusType.deleted: diff --git a/lib/src/features/post/post.dart b/lib/src/features/post/post.dart index 21b650f99..fab80ea20 100644 --- a/lib/src/features/post/post.dart +++ b/lib/src/features/post/post.dart @@ -1,5 +1,6 @@ export 'presentation/bloc/post_bloc.dart'; export 'presentation/cubit/create_post_cubit.dart'; +export 'presentation/cubits/post_navigation_cubit/post_navigation_cubit.dart'; export 'domain/enums/enums.dart'; export 'presentation/pages/pages.dart'; export 'presentation/utils/utils.dart'; diff --git a/lib/src/features/post/presentation/bloc/post_bloc.dart b/lib/src/features/post/presentation/bloc/post_bloc.dart index 1905e2279..0837db266 100644 --- a/lib/src/features/post/presentation/bloc/post_bloc.dart +++ b/lib/src/features/post/presentation/bloc/post_bloc.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -40,11 +38,6 @@ class PostBloc extends Bloc { on(_commentActionEvent); on(_commentItemUpdatedEvent); on(_commentItemInsertedEvent); - on(_navigateCommentEvent); - on(_startCommentSearchEvent); - on(_continueCommentSearchEvent); - on(_endCommentSearchEvent); - on(_onUpdateScrollPosition); on(_onUpdateCollapsedComment); on(_onPostUpdated); } @@ -192,7 +185,7 @@ class PostBloc extends Bloc { return emit( state.copyWith( - status: state.status == PostStatus.searchInProgress ? PostStatus.searchInProgress : PostStatus.success, + status: PostStatus.success, comments: commentNode.flatten(), commentNodes: commentNode, commentResponseMap: comments, @@ -251,7 +244,7 @@ class PostBloc extends Bloc { // We'll add in a edge case here to stop fetching comments after theres no more comments to be fetched return emit( state.copyWith( - status: state.status == PostStatus.searchInProgress ? PostStatus.searchInProgress : PostStatus.success, + status: PostStatus.success, commentSortType: commentSortType, comments: commentNode.flatten(), commentNodes: commentNode, @@ -345,7 +338,6 @@ class PostBloc extends Bloc { return emit(state.copyWith( status: PostStatus.success, - highlightedCommentId: null, comments: state.commentNodes!.flatten(), moddingCommentId: -1, )); @@ -368,7 +360,6 @@ class PostBloc extends Bloc { return emit(state.copyWith( status: PostStatus.success, - highlightedCommentId: event.comment.id, comments: state.commentNodes!.flatten(), moddingCommentId: -1, )); @@ -384,65 +375,6 @@ class PostBloc extends Bloc { } } - Future _navigateCommentEvent(NavigateCommentEvent event, Emitter emit) async { - if (event.direction == NavigateCommentDirection.up) { - return emit(state.copyWith(status: PostStatus.success, navigateCommentIndex: max(0, event.targetIndex))); - } else { - return emit(state.copyWith(status: PostStatus.success, navigateCommentIndex: event.targetIndex)); - } - } - - /// Comment search - - Future _startCommentSearchEvent(StartCommentSearchEvent event, Emitter emit) async { - if (event.commentSearchResults.isEmpty) return; - - int firstMatchIndex = event.commentSearchResults.keys.first; - int firstMatchCommentId = event.commentSearchResults[firstMatchIndex]!; - - return emit(state.copyWith( - status: PostStatus.searchInProgress, - commentSearchResults: event.commentSearchResults, - highlightedCommentId: firstMatchCommentId, - navigateCommentIndex: firstMatchIndex, - )); - } - - Future _continueCommentSearchEvent(ContinueCommentSearchEvent event, Emitter emit) async { - if (state.commentSearchResults?.isEmpty ?? true) return; - - final commentSearchResults = state.commentSearchResults!; - final commentSearchResultIndexes = commentSearchResults.keys.toList(); - - // Find the current match position in our sorted list - int currentMatchPosition = -1; - int currentCommentId = state.highlightedCommentId ?? commentSearchResults.values.first; - - for (int i = 0; i < commentSearchResultIndexes.length; i++) { - if (commentSearchResults[commentSearchResultIndexes[i]] == currentCommentId) { - currentMatchPosition = i; - break; - } - } - - // Move to the next match, wrapping around to the beginning if at the end - int nextMatchPosition = (currentMatchPosition + 1) % commentSearchResultIndexes.length; - int nextFlattenedIndex = commentSearchResultIndexes[nextMatchPosition]; - int nextCommentId = commentSearchResults[nextFlattenedIndex]!; - - return emit(state.copyWith(status: PostStatus.searchInProgress, highlightedCommentId: nextCommentId, navigateCommentIndex: nextFlattenedIndex)); - } - - Future _endCommentSearchEvent(EndCommentSearchEvent event, Emitter emit) async { - return emit(state.copyWith(status: PostStatus.success, highlightedCommentId: null, commentSearchResults: null)); - } - - /// Scroll position - - void _onUpdateScrollPosition(UpdateScrollPosition event, Emitter emit) { - return emit(state.copyWith(status: state.status, scrollPosition: event.scrollPosition, didScrollPositionChange: true)); - } - void _onUpdateCollapsedComment(UpdateCollapsedComment event, Emitter emit) { List collapsedComments = event.collapsed ? (state.collapsedComments.toList()..add(event.commentId)) : (state.collapsedComments.toList()..remove(event.commentId)); return emit(state.copyWith(status: state.status, collapsedComments: collapsedComments)); diff --git a/lib/src/features/post/presentation/bloc/post_event.dart b/lib/src/features/post/presentation/bloc/post_event.dart index 42db763b0..8c0b564c4 100644 --- a/lib/src/features/post/presentation/bloc/post_event.dart +++ b/lib/src/features/post/presentation/bloc/post_event.dart @@ -12,9 +12,8 @@ class GetPostEvent extends PostEvent { final ThunderPost? post; final CommentSortType? commentSortType; final String? selectedCommentPath; - final int? highlightedCommentId; - const GetPostEvent({this.commentSortType, this.post, this.postId, this.selectedCommentPath, this.highlightedCommentId}); + const GetPostEvent({this.commentSortType, this.post, this.postId, this.selectedCommentPath}); } class GetPostCommentsEvent extends PostEvent { @@ -62,29 +61,6 @@ final class CommentItemInsertedEvent extends PostEvent { const CommentItemInsertedEvent({required this.comment}); } -enum NavigateCommentDirection { up, down } - -class NavigateCommentEvent extends PostEvent { - final NavigateCommentDirection direction; - final int targetIndex; - - const NavigateCommentEvent({required this.targetIndex, required this.direction}); -} - -class StartCommentSearchEvent extends PostEvent { - final Map commentSearchResults; - - const StartCommentSearchEvent({required this.commentSearchResults}); -} - -class ContinueCommentSearchEvent extends PostEvent { - const ContinueCommentSearchEvent(); -} - -class EndCommentSearchEvent extends PostEvent { - const EndCommentSearchEvent(); -} - class ReportCommentEvent extends PostEvent { final int commentId; final String message; @@ -95,12 +71,6 @@ class ReportCommentEvent extends PostEvent { }); } -class UpdateScrollPosition extends PostEvent { - final double scrollPosition; - - const UpdateScrollPosition({required this.scrollPosition}); -} - class UpdateCollapsedComment extends PostEvent { final int commentId; final bool collapsed; diff --git a/lib/src/features/post/presentation/bloc/post_state.dart b/lib/src/features/post/presentation/bloc/post_state.dart index ced7ab505..20712e5ef 100644 --- a/lib/src/features/post/presentation/bloc/post_state.dart +++ b/lib/src/features/post/presentation/bloc/post_state.dart @@ -25,13 +25,8 @@ class PostState extends Equatable { this.hasReachedCommentEnd = false, this.errorMessage, this.commentSortType, - this.highlightedCommentId, this.selectedCommentPath, this.moddingCommentId = -1, - this.navigateCommentIndex = 0, - this.commentSearchResults, - this.scrollPosition, - this.didScrollPositionChange = false, this.collapsedComments = const [], }); @@ -57,7 +52,6 @@ class PostState extends Equatable { final String? commentCursor; final int commentCount; final bool hasReachedCommentEnd; - final int? highlightedCommentId; final String? selectedCommentPath; // This is to track what comment is being restored or deleted so we can @@ -66,16 +60,6 @@ class PostState extends Equatable { final String? errorMessage; - final int navigateCommentIndex; - final Map? commentSearchResults; - - /// Saves the position of the user's scrolling while viewing a post - final double? scrollPosition; - - /// Whether the scroll position changed. If it did not, we don't want to rebuild. - /// This flag just makes it easier to check without having to access both the old and new [scrollPosition]. - final bool didScrollPositionChange; - /// Keeps track of which comments should be collapsed. When a comment is collapsed, its child comments are hidden. final List collapsedComments; @@ -94,13 +78,8 @@ class PostState extends Equatable { List? crossPosts, String? errorMessage, CommentSortType? commentSortType, - int? highlightedCommentId, String? selectedCommentPath, int? moddingCommentId, - int? navigateCommentIndex, - Map? commentSearchResults, - double? scrollPosition, - bool? didScrollPositionChange, List? collapsedComments, }) { return PostState( @@ -117,13 +96,8 @@ class PostState extends Equatable { crossPosts: crossPosts ?? this.crossPosts, errorMessage: errorMessage ?? this.errorMessage, commentSortType: commentSortType ?? this.commentSortType, - highlightedCommentId: highlightedCommentId, selectedCommentPath: selectedCommentPath, moddingCommentId: moddingCommentId ?? this.moddingCommentId, - navigateCommentIndex: navigateCommentIndex ?? 0, - commentSearchResults: commentSearchResults ?? this.commentSearchResults, - scrollPosition: scrollPosition ?? this.scrollPosition, - didScrollPositionChange: didScrollPositionChange ?? false, collapsedComments: collapsedComments ?? this.collapsedComments, ); } @@ -142,13 +116,8 @@ class PostState extends Equatable { errorMessage, hasReachedCommentEnd, commentSortType, - highlightedCommentId, selectedCommentPath, moddingCommentId, - navigateCommentIndex, - commentSearchResults, - scrollPosition, - didScrollPositionChange, collapsedComments, ]; } diff --git a/lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_cubit.dart b/lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_cubit.dart new file mode 100644 index 000000000..587fef842 --- /dev/null +++ b/lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_cubit.dart @@ -0,0 +1,135 @@ +import 'dart:math'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'post_navigation_state.dart'; + +enum NavigateCommentDirection { up, down } + +abstract class PostNavigationEvent extends Equatable { + const PostNavigationEvent(); + + @override + List get props => []; +} + +class NavigateCommentEvent extends PostNavigationEvent { + final NavigateCommentDirection direction; + final int targetIndex; + + const NavigateCommentEvent({required this.targetIndex, required this.direction}); + + @override + List get props => [direction, targetIndex]; +} + +class StartCommentSearchEvent extends PostNavigationEvent { + final Map commentSearchResults; + + const StartCommentSearchEvent({required this.commentSearchResults}); + + @override + List get props => [commentSearchResults]; +} + +class ContinueCommentSearchEvent extends PostNavigationEvent { + const ContinueCommentSearchEvent(); +} + +class EndCommentSearchEvent extends PostNavigationEvent { + const EndCommentSearchEvent(); +} + +class UpdateScrollPositionEvent extends PostNavigationEvent { + final double scrollPosition; + + const UpdateScrollPositionEvent({required this.scrollPosition}); + + @override + List get props => [scrollPosition]; +} + +class SetHighlightedCommentIdEvent extends PostNavigationEvent { + final int? highlightedCommentId; + + const SetHighlightedCommentIdEvent({required this.highlightedCommentId}); + + @override + List get props => [highlightedCommentId ?? -1]; +} + +class PostNavigationCubit extends Cubit { + PostNavigationCubit() : super(const PostNavigationState()); + + void navigateComment(NavigateCommentDirection direction, int targetIndex) { + if (direction == NavigateCommentDirection.up) { + emit(state.copyWith(navigateCommentIndex: max(0, targetIndex))); + } else { + emit(state.copyWith(navigateCommentIndex: targetIndex)); + } + } + + void startCommentSearch(Map commentSearchResults) { + if (commentSearchResults.isEmpty) return; + + int firstMatchIndex = commentSearchResults.keys.first; + int firstMatchCommentId = commentSearchResults[firstMatchIndex]!; + + emit(state.copyWith( + commentSearchResults: commentSearchResults, + highlightedCommentId: firstMatchCommentId, + navigateCommentIndex: firstMatchIndex, + )); + } + + void continueCommentSearch() { + if (state.commentSearchResults?.isEmpty ?? true) return; + + final commentSearchResults = state.commentSearchResults!; + final commentSearchResultIndexes = commentSearchResults.keys.toList(); + + // Find the current match position in our sorted list + int currentMatchPosition = -1; + int currentCommentId = state.highlightedCommentId ?? commentSearchResults.values.first; + + for (int i = 0; i < commentSearchResultIndexes.length; i++) { + if (commentSearchResults[commentSearchResultIndexes[i]] == currentCommentId) { + currentMatchPosition = i; + break; + } + } + + // Move to the next match, wrapping around to the beginning if at the end + int nextMatchPosition = (currentMatchPosition + 1) % commentSearchResultIndexes.length; + int nextFlattenedIndex = commentSearchResultIndexes[nextMatchPosition]; + int nextCommentId = commentSearchResults[nextFlattenedIndex]!; + + emit(state.copyWith( + highlightedCommentId: nextCommentId, + navigateCommentIndex: nextFlattenedIndex, + )); + } + + void endCommentSearch() { + emit(state.copyWith( + highlightedCommentId: null, + commentSearchResults: null, + )); + } + + void updateScrollPosition(double scrollPosition) { + emit(state.copyWith( + scrollPosition: scrollPosition, + didScrollPositionChange: true, + )); + } + + void setHighlightedCommentId(int? highlightedCommentId) { + emit(state.copyWith(highlightedCommentId: highlightedCommentId)); + } + + void clearScrollPositionChange() { + emit(state.copyWith(didScrollPositionChange: false)); + } +} diff --git a/lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_state.dart b/lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_state.dart new file mode 100644 index 000000000..aecc59ec8 --- /dev/null +++ b/lib/src/features/post/presentation/cubits/post_navigation_cubit/post_navigation_state.dart @@ -0,0 +1,52 @@ +part of 'post_navigation_cubit.dart'; + +class PostNavigationState extends Equatable { + const PostNavigationState({ + this.navigateCommentIndex = 0, + this.highlightedCommentId, + this.commentSearchResults, + this.scrollPosition, + this.didScrollPositionChange = false, + }); + + /// The index of the comment to navigate to + final int navigateCommentIndex; + + /// The ID of the comment that should be highlighted + final int? highlightedCommentId; + + /// The search results for comment search (maps comment index to comment ID) + final Map? commentSearchResults; + + /// Saves the position of the user's scrolling while viewing a post + final double? scrollPosition; + + /// Whether the scroll position changed. If it did not, we don't want to rebuild. + /// This flag just makes it easier to check without having to access both the old and new [scrollPosition]. + final bool didScrollPositionChange; + + PostNavigationState copyWith({ + int? navigateCommentIndex, + int? highlightedCommentId, + Map? commentSearchResults, + double? scrollPosition, + bool? didScrollPositionChange, + }) { + return PostNavigationState( + navigateCommentIndex: navigateCommentIndex ?? this.navigateCommentIndex, + highlightedCommentId: highlightedCommentId, + commentSearchResults: commentSearchResults ?? this.commentSearchResults, + scrollPosition: scrollPosition ?? this.scrollPosition, + didScrollPositionChange: didScrollPositionChange ?? this.didScrollPositionChange, + ); + } + + @override + List get props => [ + navigateCommentIndex, + highlightedCommentId, + commentSearchResults, + scrollPosition, + didScrollPositionChange, + ]; +} diff --git a/lib/src/features/post/presentation/pages/create_post_page.dart b/lib/src/features/post/presentation/pages/create_post_page.dart index fce1b33d5..b2dde9d50 100644 --- a/lib/src/features/post/presentation/pages/create_post_page.dart +++ b/lib/src/features/post/presentation/pages/create_post_page.dart @@ -13,7 +13,7 @@ import 'package:link_preview_generator/link_preview_generator.dart'; import 'package:markdown_editor/markdown_editor.dart'; // Project imports -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/features/community/community.dart'; import 'package:thunder/src/core/enums/meta_search_type.dart'; import 'package:thunder/src/core/enums/post_sort_type.dart'; @@ -392,7 +392,7 @@ class _CreatePostPageState extends State { final l10n = GlobalContext.l10n; final theme = Theme.of(context); - final hideNsfwPreviews = context.select((ThunderBloc bloc) => bloc.state.hideNsfwPreviews); + final hideNsfwPreviews = context.select((cubit) => cubit.state.hideNsfwPreviews); return PopScope( onPopInvokedWithResult: (didPop, result) {}, diff --git a/lib/src/features/post/presentation/pages/post_page.dart b/lib/src/features/post/presentation/pages/post_page.dart index 047b821e4..47e9ff4b5 100644 --- a/lib/src/features/post/presentation/pages/post_page.dart +++ b/lib/src/features/post/presentation/pages/post_page.dart @@ -20,6 +20,8 @@ import 'package:thunder/src/shared/cross_posts.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; import 'package:thunder/src/shared/widgets/text/selectable_text_modal.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; /// A page that displays the post details and comments associated with a post. class PostPage extends StatefulWidget { @@ -93,7 +95,7 @@ class _PostPageState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { if (!hasSetInitialScroll) { hasSetInitialScroll = true; - scrollController.jumpTo(context.read().state.scrollPosition ?? 0.0); + scrollController.jumpTo(context.read().state.scrollPosition ?? 0.0); } }); } @@ -106,12 +108,12 @@ class _PostPageState extends State { super.dispose(); } - /// Updates the scroll position in the bloc after scrolling has stopped + /// Updates the scroll position in the cubit after scrolling has stopped void _updateScrollPosition() { _updateScrollPositionTimer?.cancel(); _updateScrollPositionTimer = Timer(const Duration(milliseconds: 150), () { - context.read().add(UpdateScrollPosition(scrollPosition: scrollController.position.pixels)); + context.read().updateScrollPosition(scrollController.position.pixels); }); } @@ -131,8 +133,12 @@ class _PostPageState extends State { void listener(BuildContext context, PostState state) { final l10n = GlobalContext.l10n; + final navigationState = context.read().state; - if (state.didScrollPositionChange) return; + if (navigationState.didScrollPositionChange) { + context.read().clearScrollPositionChange(); + return; + } if (state.status == PostStatus.success && state.post != null) { widget.onPostUpdated?.call(state.post!); @@ -155,7 +161,19 @@ class _PostPageState extends State { return BlocConsumer( listenWhen: listenWhen, listener: listener, - buildWhen: (previous, current) => !current.didScrollPositionChange, + buildWhen: (previous, current) { + final navigationState = context.read().state; + // Don't rebuild if only scroll position changed + if (navigationState.didScrollPositionChange) { + return false; + } + // Rebuild if relevant state properties changed + return previous.status != current.status || + previous.post != current.post || + previous.comments != current.comments || + previous.crossPosts != current.crossPosts || + previous.errorMessage != current.errorMessage; + }, builder: (context, state) { if (state.status == PostStatus.initial) { // This is required because listener does not get called on initial build @@ -163,7 +181,6 @@ class _PostPageState extends State { GetPostEvent( post: widget.initialPost, selectedCommentPath: widget.commentPath, - highlightedCommentId: widget.highlightedCommentId, ), ); } @@ -172,7 +189,8 @@ class _PostPageState extends State { // Check to see if there is a highlighted comment. If there is, check to see if it is visible. // If it is not visible, scroll to it. - final highlightedCommentId = state.highlightedCommentId; + final navigationState = context.read().state; + final highlightedCommentId = navigationState.highlightedCommentId; final highlightedCommentIndex = state.comments.indexWhere((element) => element.comment!.id == highlightedCommentId); if (widget.highlightedCommentId != null && listController.isAttached && highlightedCommentIndex != -1) { @@ -193,9 +211,10 @@ class _PostPageState extends State { onRefresh: () async { HapticFeedback.mediumImpact(); - if (this.highlightedCommentId != null) { + final navigationState = context.read().state; + if (navigationState.highlightedCommentId != null) { // If we're viewing a specific comment thread, refresh with that context unless "View All Comments" is pressed - context.read().add(GetPostEvent(postId: widget.initialPost.id, selectedCommentPath: widget.commentPath, highlightedCommentId: widget.highlightedCommentId)); + context.read().add(GetPostEvent(postId: widget.initialPost.id, selectedCommentPath: widget.commentPath)); } else { context.read().add(GetPostEvent(postId: widget.initialPost.id)); } @@ -248,9 +267,10 @@ class _PostPageState extends State { hasScrollBody: false, child: _PostPageError( onRetry: () { - if (this.highlightedCommentId != null) { + final navigationState = context.read().state; + if (navigationState.highlightedCommentId != null) { // If we're viewing a specific comment thread, retry with that context unless "View All Comments" is pressed - context.read().add(GetPostEvent(postId: widget.initialPost.id, selectedCommentPath: widget.commentPath, highlightedCommentId: widget.highlightedCommentId)); + context.read().add(GetPostEvent(postId: widget.initialPost.id, selectedCommentPath: widget.commentPath)); } else { context.read().add(GetPostEvent(postId: widget.initialPost.id)); } @@ -266,7 +286,7 @@ class _PostPageState extends State { viewSource: viewSource, showCompactPostBody: widget.highlightedCommentId != null, ), - if (state.status != PostStatus.loading && this.highlightedCommentId != null) + if (state.status != PostStatus.loading && navigationState.highlightedCommentId != null) InkWell( child: Container( height: 60.0, @@ -282,7 +302,7 @@ class _PostPageState extends State { ), onTap: () { context.read().add(const GetPostCommentsEvent(reset: true, commentParentId: null)); - setState(() => this.highlightedCommentId = null); + context.read().setHighlightedCommentId(null); }, ), ], @@ -309,13 +329,19 @@ class _PostPageState extends State { key: ValueKey(comment.id), account: account, comment: comment, - onCommentUpdated: (comment) => context.read().add(CommentItemUpdatedEvent(comment: comment)), - onCommentInserted: (comment) => context.read().add(CommentItemInsertedEvent(comment: comment)), + onCommentUpdated: (comment) { + context.read().add(CommentItemUpdatedEvent(comment: comment)); + context.read().setHighlightedCommentId(null); + }, + onCommentInserted: (comment) { + context.read().add(CommentItemInsertedEvent(comment: comment)); + context.read().setHighlightedCommentId(comment.id); + }, level: commentNode.depth, replies: commentNode.replies.length, collapsed: isCollapsed, hidden: isHidden, - highlight: comment.id == state.highlightedCommentId, + highlight: comment.id == navigationState.highlightedCommentId, onCollapse: (int commentId, bool collapsed) { context.read().add(UpdateCollapsedComment(commentId: commentId, collapsed: collapsed)); setState(() {}); @@ -335,9 +361,9 @@ class _PostPageState extends State { if (thunderState.hideTopBarOnScroll) Positioned(child: Container(height: MediaQuery.of(context).padding.top, color: theme.colorScheme.surface)), AnimatedSwitcher( duration: const Duration(milliseconds: 200), - child: thunderState.isFabOpen + child: context.select((cubit) => cubit.state.isPostFabOpen) ? Listener( - onPointerUp: (details) => context.read().add(const OnFabToggle(false)), + onPointerUp: (details) => context.read().setPostFabOpen(false), child: Container(color: theme.colorScheme.surface.withValues(alpha: 0.95)), ) : null, @@ -411,7 +437,7 @@ class _PostPageFeedEndState extends State<_PostPageFeedEnd> { final comments = context.select>((bloc) => bloc.state.comments); final hasReachedCommentEnd = context.select((bloc) => bloc.state.hasReachedCommentEnd); - final metadataFontSizeScale = context.select((bloc) => bloc.state.metadataFontSizeScale); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); if (bottomSpacerHeight == null) { if (_calculateBottomSpacerTimer != null) _calculateBottomSpacerTimer!.cancel(); diff --git a/lib/src/features/post/presentation/widgets/post_body/post_body.dart b/lib/src/features/post/presentation/widgets/post_body/post_body.dart index 83891c812..d1d511cca 100644 --- a/lib/src/features/post/presentation/widgets/post_body/post_body.dart +++ b/lib/src/features/post/presentation/widgets/post_body/post_body.dart @@ -16,6 +16,8 @@ import 'package:thunder/src/shared/utils/colors.dart'; import 'package:thunder/src/app/utils/navigation.dart'; import 'package:thunder/src/features/community/community.dart'; import 'package:thunder/src/features/feed/feed.dart'; +import 'package:thunder/src/app/cubits/feed_ui_cubit/feed_ui_cubit.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/core/enums/media_type.dart'; import 'package:thunder/src/core/enums/post_body_view_type.dart'; import 'package:thunder/src/core/enums/view_mode.dart'; @@ -25,7 +27,8 @@ import 'package:thunder/src/shared/cross_posts.dart'; import 'package:thunder/src/shared/widgets/media/media_view.dart'; import 'package:thunder/src/shared/reply_to_preview_actions.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; /// A widget that displays the body of a post. This includes the title, body, media, and metadata. @@ -123,10 +126,10 @@ class _PostBodyState extends State with SingleTickerProviderStateMixin Widget build(BuildContext context) { final theme = Theme.of(context); - final hideNsfwPreviews = context.select((ThunderBloc bloc) => bloc.state.hideNsfwPreviews); - final showCrossPosts = context.select((ThunderBloc bloc) => bloc.state.showCrossPosts); - final postBodyViewType = context.select((ThunderBloc bloc) => bloc.state.postBodyViewType); - final contentFontSizeScale = context.select((ThunderBloc bloc) => bloc.state.contentFontSizeScale); + final hideNsfwPreviews = context.select((cubit) => cubit.state.hideNsfwPreviews); + final showCrossPosts = context.select((cubit) => cubit.state.showCrossPosts); + final postBodyViewType = context.select((cubit) => cubit.state.postBodyViewType); + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); final post = widget.post; final media = post.media.first; @@ -252,7 +255,7 @@ class _PostBodyState extends State with SingleTickerProviderStateMixin switch (postAction) { case PostAction.hide: - context.read().add(FeedDismissHiddenPostEvent(postId: post!.id)); + context.read().dismissHiddenPost(post!.id); break; default: break; @@ -260,7 +263,7 @@ class _PostBodyState extends State with SingleTickerProviderStateMixin switch (userAction) { case UserAction.block: - context.read().add(FeedDismissBlockedEvent(userId: post!.creator!.id)); + context.read().dismissBlocked(userId: post!.creator!.id); break; default: break; @@ -268,7 +271,7 @@ class _PostBodyState extends State with SingleTickerProviderStateMixin switch (communityAction) { case CommunityAction.block: - context.read().add(FeedDismissBlockedEvent(communityId: post!.community!.id)); + context.read().dismissBlocked(communityId: post!.community!.id); break; default: break; diff --git a/lib/src/features/post/presentation/widgets/post_body/post_body_action_bar.dart b/lib/src/features/post/presentation/widgets/post_body/post_body_action_bar.dart index f9027fc4a..73f11d1e0 100644 --- a/lib/src/features/post/presentation/widgets/post_body/post_body_action_bar.dart +++ b/lib/src/features/post/presentation/widgets/post_body/post_body_action_bar.dart @@ -4,7 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/shared/snackbar.dart'; -import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/core/enums/action_color.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/shared/utils/numbers.dart'; @@ -60,14 +61,16 @@ class PostBodyActionsBar extends StatelessWidget { @override Widget build(BuildContext context) { + final upvoteColor = context.select((cubit) => cubit.state.upvoteColor); + final downvoteColor = context.select((cubit) => cubit.state.downvoteColor); + final saveColor = context.select((cubit) => cubit.state.saveColor); final theme = Theme.of(context); final l10n = GlobalContext.l10n; - return BlocBuilder( - buildWhen: (previous, current) => previous.isLoggedIn != current.isLoggedIn, - builder: (context, state) { - bool isUserLoggedIn = state.isLoggedIn; - bool downvotesEnabled = state.downvotesEnabled; + return BlocSelector( + selector: (state) => state.isLoggedIn, + builder: (context, isUserLoggedIn) { + final downvotesEnabled = context.select((bloc) => bloc.state.downvotesEnabled); final showScores = context.select((ProfileBloc bloc) => bloc.state.siteResponse?.myUser?.localUserView.localUser.showScores) ?? true; return Padding( @@ -80,7 +83,7 @@ class PostBodyActionsBar extends StatelessWidget { onPressed: isUserLoggedIn ? () => onVote?.call(vote == 1 ? 0 : 1) : null, style: TextButton.styleFrom( fixedSize: const Size.fromHeight(40), - foregroundColor: vote == 1 ? theme.textTheme.bodyMedium?.color : context.read().state.upvoteColor.color, + foregroundColor: vote == 1 ? theme.textTheme.bodyMedium?.color : context.read().state.upvoteColor.color, padding: EdgeInsets.zero, ), child: Wrap( @@ -90,14 +93,14 @@ class PostBodyActionsBar extends StatelessWidget { Icon( Icons.arrow_upward_rounded, semanticLabel: vote == 1 ? l10n.upvoted : l10n.upvote, - color: isUserLoggedIn ? (vote == 1 ? context.read().state.upvoteColor.color : theme.textTheme.bodyMedium?.color) : null, + color: isUserLoggedIn ? (vote == 1 ? upvoteColor.color : theme.textTheme.bodyMedium?.color) : null, size: 24.0, ), if (showScores) Text( formatNumberToK(upvotes ?? 0), style: TextStyle( - color: isUserLoggedIn ? (vote == 1 ? context.read().state.upvoteColor.color : theme.textTheme.bodyMedium?.color) : null, + color: isUserLoggedIn ? (vote == 1 ? upvoteColor.color : theme.textTheme.bodyMedium?.color) : null, ), ), ], @@ -110,7 +113,7 @@ class PostBodyActionsBar extends StatelessWidget { onPressed: isUserLoggedIn ? () => onVote?.call(vote == -1 ? 0 : -1) : null, style: TextButton.styleFrom( fixedSize: const Size.fromHeight(40), - foregroundColor: vote == -1 ? theme.textTheme.bodyMedium?.color : context.read().state.downvoteColor.color, + foregroundColor: vote == -1 ? theme.textTheme.bodyMedium?.color : downvoteColor.color, padding: EdgeInsets.zero, ), child: Wrap( @@ -120,14 +123,14 @@ class PostBodyActionsBar extends StatelessWidget { Icon( Icons.arrow_downward_rounded, semanticLabel: vote == -1 ? l10n.downvoted : l10n.downvote, - color: isUserLoggedIn ? (vote == -1 ? context.read().state.downvoteColor.color : theme.textTheme.bodyMedium?.color) : null, + color: isUserLoggedIn ? (vote == -1 ? downvoteColor.color : theme.textTheme.bodyMedium?.color) : null, size: 24.0, ), if (showScores) Text( formatNumberToK(downvotes ?? 0), style: TextStyle( - color: isUserLoggedIn ? (vote == -1 ? context.read().state.downvoteColor.color : theme.textTheme.bodyMedium?.color) : null, + color: isUserLoggedIn ? (vote == -1 ? downvoteColor.color : theme.textTheme.bodyMedium?.color) : null, ), ), ], @@ -137,11 +140,11 @@ class PostBodyActionsBar extends StatelessWidget { Expanded( child: IconButton( onPressed: isUserLoggedIn ? () => onSave?.call(!saved) : null, - style: IconButton.styleFrom(foregroundColor: saved ? null : context.read().state.saveColor.color), + style: IconButton.styleFrom(foregroundColor: saved ? null : saveColor.color), icon: Icon( saved ? Icons.star_rounded : Icons.star_border_rounded, semanticLabel: saved ? l10n.saved : l10n.save, - color: isUserLoggedIn ? (saved ? context.read().state.saveColor.color : theme.textTheme.bodyMedium?.color) : null, + color: isUserLoggedIn ? (saved ? saveColor.color : theme.textTheme.bodyMedium?.color) : null, ), ), ), diff --git a/lib/src/features/post/presentation/widgets/post_body/post_body_preview.dart b/lib/src/features/post/presentation/widgets/post_body/post_body_preview.dart index b9eb17dcd..7e0481f7e 100644 --- a/lib/src/features/post/presentation/widgets/post_body/post_body_preview.dart +++ b/lib/src/features/post/presentation/widgets/post_body/post_body_preview.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; @@ -40,8 +42,8 @@ class PostBodyPreview extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final contentFontSizeScale = context.select((bloc) => bloc.state.contentFontSizeScale); - final hideNsfwPreviews = context.select((ThunderBloc bloc) => bloc.state.hideNsfwPreviews); + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); + final hideNsfwPreviews = context.select((cubit) => cubit.state.hideNsfwPreviews); final color = gradientBackgroundColor ?? theme.scaffoldBackgroundColor; diff --git a/lib/src/features/post/presentation/widgets/post_body/post_body_title.dart b/lib/src/features/post/presentation/widgets/post_body/post_body_title.dart index fae6bdabb..c7d5a96cf 100644 --- a/lib/src/features/post/presentation/widgets/post_body/post_body_title.dart +++ b/lib/src/features/post/presentation/widgets/post_body/post_body_title.dart @@ -15,6 +15,8 @@ import 'package:thunder/src/shared/widgets/chips/user_chip.dart'; import 'package:thunder/src/shared/widgets/media/compact_thumbnail_preview.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; /// Displays the title and related information for a given post. @@ -51,7 +53,7 @@ class PostBodyTitle extends StatelessWidget { /// Builds the title section for condensed view Widget _buildCondensedTitle(BuildContext context) { - final showThumbnailPreviewOnRight = context.select((bloc) => bloc.state.showThumbnailPreviewOnRight); + final showThumbnailPreviewOnRight = context.select((cubit) => cubit.state.showThumbnailPreviewOnRight); final media = post.media.first; @@ -110,7 +112,7 @@ class PostBodyTitle extends StatelessWidget { /// Builds the title text widget Widget _buildTitleText(BuildContext context) { final theme = Theme.of(context); - final titleFontSizeScale = context.select((bloc) => bloc.state.titleFontSizeScale); + final titleFontSizeScale = context.select((cubit) => cubit.state.titleFontSizeScale); return ScalableText( post.name, @@ -187,8 +189,8 @@ class _PostBodyAuthorCommunityMetadataState extends State((bloc) => bloc.state.postBodyShowUserInstance); - final postBodyShowCommunityInstance = context.select((bloc) => bloc.state.postBodyShowCommunityInstance); + final postBodyShowUserInstance = context.select((cubit) => cubit.state.postBodyShowUserInstance); + final postBodyShowCommunityInstance = context.select((cubit) => cubit.state.postBodyShowCommunityInstance); return Wrap( spacing: 6.0, diff --git a/lib/src/features/post/presentation/widgets/post_bottom_sheet/general_post_action_bottom_sheet.dart b/lib/src/features/post/presentation/widgets/post_bottom_sheet/general_post_action_bottom_sheet.dart index 61be421ad..914a8b237 100644 --- a/lib/src/features/post/presentation/widgets/post_bottom_sheet/general_post_action_bottom_sheet.dart +++ b/lib/src/features/post/presentation/widgets/post_bottom_sheet/general_post_action_bottom_sheet.dart @@ -8,7 +8,7 @@ import 'package:thunder/src/core/enums/threadiverse_platform.dart'; import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/shared/bottom_sheet_action.dart'; import 'package:thunder/src/shared/multi_picker_item.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/shared/utils/instance.dart'; @@ -222,37 +222,37 @@ class _GeneralPostActionBottomSheetPageState extends State().state; + final themeState = context.read().state; switch (action) { case GeneralQuickPostAction.upvote: - return state.upvoteColor.color; + return themeState.upvoteColor.color; case GeneralQuickPostAction.downvote: - return state.downvoteColor.color; + return themeState.downvoteColor.color; case GeneralQuickPostAction.save: - return state.saveColor.color; + return themeState.saveColor.color; case GeneralQuickPostAction.read: - return state.markReadColor.color; + return themeState.markReadColor.color; case GeneralQuickPostAction.hide: - return state.hideColor.color; + return themeState.hideColor.color; } } Color? getForegroundColor(GeneralQuickPostAction action) { - final state = context.read().state; + final themeState = context.read().state; final post = widget.post; switch (action) { case GeneralQuickPostAction.upvote: - return post.myVote == 1 ? state.upvoteColor.color : null; + return post.myVote == 1 ? themeState.upvoteColor.color : null; case GeneralQuickPostAction.downvote: - return post.myVote == -1 ? state.downvoteColor.color : null; + return post.myVote == -1 ? themeState.downvoteColor.color : null; case GeneralQuickPostAction.save: - return post.saved == true ? state.saveColor.color : null; + return post.saved == true ? themeState.saveColor.color : null; case GeneralQuickPostAction.read: - return post.read == true ? state.markReadColor.color : null; + return post.read == true ? themeState.markReadColor.color : null; case GeneralQuickPostAction.hide: - return post.hidden == true ? state.hideColor.color : null; + return post.hidden == true ? themeState.hideColor.color : null; } } diff --git a/lib/src/features/post/presentation/widgets/post_card_title.dart b/lib/src/features/post/presentation/widgets/post_card_title.dart index f26e4ce99..6459cbfe0 100644 --- a/lib/src/features/post/presentation/widgets/post_card_title.dart +++ b/lib/src/features/post/presentation/widgets/post_card_title.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/features/post/post.dart'; -import 'package:thunder/src/app/thunder.dart'; /// Creates the title of a post card. This includes the post title and any status icons. class PostCardTitle extends StatelessWidget { @@ -65,7 +65,7 @@ class PostCardTitle extends StatelessWidget { final theme = Theme.of(context); final textStyle = theme.textTheme.bodyMedium; - final textScaleFactor = context.select((ThunderBloc bloc) => bloc.state.titleFontSizeScale.textScaleFactor); + final textScaleFactor = context.select((cubit) => cubit.state.titleFontSizeScale.textScaleFactor); final fontSize = _calculateFontSize(context, textStyle, textScaleFactor); final statuses = PostStatusIcon(hidden: hidden, locked: locked, saved: saved, pinned: pinned, deleted: deleted, removed: removed, dim: dim); diff --git a/lib/src/features/post/presentation/widgets/post_page_app_bar.dart b/lib/src/features/post/presentation/widgets/post_page_app_bar.dart index 05ccefb0f..2961207b9 100644 --- a/lib/src/features/post/presentation/widgets/post_page_app_bar.dart +++ b/lib/src/features/post/presentation/widgets/post_page_app_bar.dart @@ -191,9 +191,10 @@ class PostAppBarActions extends StatelessWidget { HapticFeedback.mediumImpact(); await onReset?.call(); if (context.mounted) { - if (highlightedCommentId != null) { + final navigationState = context.read().state; + if (navigationState.highlightedCommentId != null) { // If we're viewing a specific comment thread, refresh with that context unless "View All Comments" is pressed - context.read().add(GetPostEvent(postId: state.post?.id, selectedCommentPath: commentPath, highlightedCommentId: highlightedCommentId)); + context.read().add(GetPostEvent(postId: state.post?.id, selectedCommentPath: commentPath)); } else { context.read().add(GetPostEvent(postId: state.post?.id)); } diff --git a/lib/src/features/post/presentation/widgets/post_page_fab.dart b/lib/src/features/post/presentation/widgets/post_page_fab.dart index 002990df7..0279b09fe 100644 --- a/lib/src/features/post/presentation/widgets/post_page_fab.dart +++ b/lib/src/features/post/presentation/widgets/post_page_fab.dart @@ -7,6 +7,8 @@ import 'package:super_sliver_list/super_sliver_list.dart'; import 'package:thunder/src/app/thunder.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/app/utils/navigation.dart'; +import 'package:thunder/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; import 'package:thunder/src/core/enums/fab_action.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/features/comment/comment.dart'; @@ -88,10 +90,11 @@ class _PostPageFABState extends State { PostFabAction.search.execute( override: () { final l10n = GlobalContext.l10n; - final status = context.read().state.status; + final navigationCubit = context.read(); + final isSearchInProgress = navigationCubit.state.commentSearchResults != null; - if (status == PostStatus.searchInProgress) { - context.read().add(const EndCommentSearchEvent()); + if (isSearchInProgress) { + navigationCubit.endCommentSearch(); return; } @@ -119,7 +122,7 @@ class _PostPageFABState extends State { if (commentSearchResults.isEmpty) { showSnackbar(l10n.noResultsFound); } else { - context.read().add(StartCommentSearchEvent(commentSearchResults: commentSearchResults)); + context.read().startCommentSearch(commentSearchResults); } return Future.value(null); @@ -135,22 +138,29 @@ class _PostPageFABState extends State { Widget build(BuildContext context) { final l10n = GlobalContext.l10n; - final thunderState = context.read().state; - final combineNavAndFab = thunderState.combineNavAndFab; - final isFabSummoned = thunderState.isFabSummoned; - final singlePressAction = thunderState.postFabSinglePressAction; - final longPressAction = thunderState.postFabLongPressAction; + final combineNavAndFab = context.select((cubit) => cubit.state.combineNavAndFab); + final isFabSummoned = context.select((cubit) => cubit.state.isPostFabSummoned); + final singlePressAction = context.select((cubit) => cubit.state.postFabSinglePressAction); + final longPressAction = context.select((cubit) => cubit.state.postFabLongPressAction); + final hideTopBarOnScroll = context.select((bloc) => bloc.state.hideTopBarOnScroll); + final enableCommentNavigation = context.select((cubit) => cubit.state.enableCommentNavigation); + final enablePostsFab = context.select((cubit) => cubit.state.enablePostsFab); + final postFabEnableRefresh = context.select((cubit) => cubit.state.postFabEnableRefresh); + final postFabEnableReplyToPost = context.select((cubit) => cubit.state.postFabEnableReplyToPost); + final postFabEnableChangeSort = context.select((cubit) => cubit.state.postFabEnableChangeSort); + final postFabEnableBackToTop = context.select((cubit) => cubit.state.postFabEnableBackToTop); + final postFabEnableSearch = context.select((cubit) => cubit.state.postFabEnableSearch); final double statusBarHeight = MediaQuery.of(context).padding.top; - final status = context.select((bloc) => bloc.state.status); - final highlightedCommentId = context.select((bloc) => bloc.state.highlightedCommentId); + final highlightedCommentId = context.select((cubit) => cubit.state.highlightedCommentId); final selectedCommentPath = context.select((bloc) => bloc.state.selectedCommentPath); + final isSearchInProgress = context.select((cubit) => cubit.state.commentSearchResults != null); return Stack( alignment: Alignment.center, children: [ - if (thunderState.enableCommentNavigation) + if (enableCommentNavigation) Positioned.fill( child: Padding( padding: const EdgeInsets.only(bottom: 5), @@ -162,12 +172,12 @@ class _PostPageFABState extends State { scrollController: widget.scrollController, listController: widget.listController, comments: widget.comments, - statusBarHeight: thunderState.hideTopBarOnScroll ? statusBarHeight : 0, + statusBarHeight: hideTopBarOnScroll ? statusBarHeight : 0, ), ), ), ), - if (thunderState.enablePostsFab) + if (enablePostsFab) Padding( padding: EdgeInsets.only(right: combineNavAndFab ? 0 : 16, bottom: combineNavAndFab ? 5 : 0), child: AnimatedSwitcher( @@ -177,13 +187,13 @@ class _PostPageFABState extends State { centered: combineNavAndFab, distance: combineNavAndFab ? 45 : 60, icon: Icon( - status == PostStatus.searchInProgress ? Icons.youtube_searched_for_rounded : singlePressAction.getIcon(postLocked: widget.post.locked), - semanticLabel: status == PostStatus.searchInProgress ? l10n.search : singlePressAction.getTitle(context, postLocked: widget.post.locked), + isSearchInProgress ? Icons.youtube_searched_for_rounded : singlePressAction.getIcon(postLocked: widget.post.locked), + semanticLabel: isSearchInProgress ? l10n.search : singlePressAction.getTitle(context, postLocked: widget.post.locked), size: 35, ), - onPressed: status == PostStatus.searchInProgress + onPressed: isSearchInProgress ? () { - context.read().add(const ContinueCommentSearchEvent()); + context.read().continueCommentSearch(); } : () => singlePressAction.execute( context: context, @@ -212,7 +222,6 @@ class _PostPageFABState extends State { context: context, post: widget.post, postId: widget.post.id, - highlightedCommentId: highlightedCommentId, selectedCommentPath: selectedCommentPath, override: longPressAction == PostFabAction.backToTop ? () => { @@ -229,19 +238,21 @@ class _PostPageFABState extends State { : longPressAction == PostFabAction.replyToPost ? () => replyToPost(context, widget.post, postLocked: widget.post.locked) : null), + fabType: FabType.post, children: [ - if (thunderState.postFabEnableRefresh) + if (postFabEnableRefresh) ActionButton( + fabType: FabType.post, centered: combineNavAndFab, onPressed: () { HapticFeedback.mediumImpact(); - if (highlightedCommentId != null) { + final navigationState = context.read().state; + if (navigationState.highlightedCommentId != null) { // If we're viewing a specific comment thread, refresh with that context unless "View All Comments" is pressed PostFabAction.refresh.execute( context: context, postId: widget.post.id, - highlightedCommentId: highlightedCommentId, selectedCommentPath: selectedCommentPath, ); } else { @@ -254,8 +265,9 @@ class _PostPageFABState extends State { title: PostFabAction.refresh.getTitle(context), icon: Icon(PostFabAction.refresh.getIcon()), ), - if (thunderState.postFabEnableReplyToPost) + if (postFabEnableReplyToPost) ActionButton( + fabType: FabType.post, centered: combineNavAndFab, onPressed: () { HapticFeedback.mediumImpact(); @@ -266,8 +278,9 @@ class _PostPageFABState extends State { title: PostFabAction.replyToPost.getTitle(context), icon: Icon(widget.post.locked ? Icons.lock : PostFabAction.replyToPost.getIcon()), ), - if (thunderState.enableChangeSort) + if (postFabEnableChangeSort) ActionButton( + fabType: FabType.post, centered: combineNavAndFab, onPressed: () { HapticFeedback.mediumImpact(); @@ -276,8 +289,9 @@ class _PostPageFABState extends State { title: PostFabAction.changeSort.getTitle(context), icon: Icon(PostFabAction.changeSort.getIcon()), ), - if (thunderState.enableBackToTop) + if (postFabEnableBackToTop) ActionButton( + fabType: FabType.post, centered: combineNavAndFab, onPressed: () { PostFabAction.backToTop @@ -286,12 +300,13 @@ class _PostPageFABState extends State { title: PostFabAction.backToTop.getTitle(context), icon: Icon(PostFabAction.backToTop.getIcon()), ), - if (thunderState.postFabEnableSearch) + if (postFabEnableSearch) ActionButton( + fabType: FabType.post, centered: combineNavAndFab, onPressed: () => startCommentSearch(context), - title: status == PostStatus.searchInProgress ? l10n.endSearch : PostFabAction.search.getTitle(context), - icon: Icon(status == PostStatus.searchInProgress ? Icons.search_off_rounded : PostFabAction.search.getIcon()), + title: isSearchInProgress ? l10n.endSearch : PostFabAction.search.getTitle(context), + icon: Icon(isSearchInProgress ? Icons.search_off_rounded : PostFabAction.search.getIcon()), ), ], ) diff --git a/lib/src/features/post/presentation/widgets/post_status_icon.dart b/lib/src/features/post/presentation/widgets/post_status_icon.dart index 117adf6c6..6957b3bc6 100644 --- a/lib/src/features/post/presentation/widgets/post_status_icon.dart +++ b/lib/src/features/post/presentation/widgets/post_status_icon.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/features/post/post.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; /// Given a list of statuses, returns a list of icons representing the statuses. class PostStatusIcon extends StatelessWidget { @@ -70,7 +70,7 @@ class PostStatusIcon extends StatelessWidget { @override Widget build(BuildContext context) { - final textScaleFactor = context.select((ThunderBloc bloc) => bloc.state.titleFontSizeScale.textScaleFactor); + final textScaleFactor = context.select((cubit) => cubit.state.titleFontSizeScale.textScaleFactor); final statuses = _buildStatusIcons(context, textScaleFactor); if (statuses.isEmpty) return SizedBox.shrink(); diff --git a/lib/src/features/search/presentation/pages/search_page.dart b/lib/src/features/search/presentation/pages/search_page.dart index 9a0314561..e09529737 100644 --- a/lib/src/features/search/presentation/pages/search_page.dart +++ b/lib/src/features/search/presentation/pages/search_page.dart @@ -207,11 +207,12 @@ class _SearchPageState extends State with AutomaticKeepAliveClientMi }, ), ], - child: BlocBuilder( - builder: (context, state) { - if (state.focusSearchId > _previousFocusSearchId) { + child: BlocSelector( + selector: (state) => state.focusSearchId, + builder: (context, focusSearchId) { + if (focusSearchId > _previousFocusSearchId) { searchTextFieldFocus.requestFocus(); - _previousFocusSearchId = state.focusSearchId; + _previousFocusSearchId = focusSearchId; } return Scaffold( @@ -281,7 +282,7 @@ class _SearchPageState extends State with AutomaticKeepAliveClientMi controller: _searchFiltersScrollController, child: Row( children: [ - if (state.viewingAll) ...[ + if (context.read().state.viewingAll) ...[ ThunderActionChip( backgroundColor: theme.colorScheme.primaryContainer.withValues(alpha: 0.25), trailingIcon: Icons.close_rounded, @@ -458,7 +459,7 @@ class _SearchPageState extends State with AutomaticKeepAliveClientMi ), Padding( padding: const EdgeInsets.only(top: 60), - child: _getSearchBody(context, state, account.instance), + child: _getSearchBody(context, context.read().state, account.instance), ), ], ), @@ -472,8 +473,7 @@ class _SearchPageState extends State with AutomaticKeepAliveClientMi Widget _getSearchBody(BuildContext context, SearchState state, String accountInstance) { final ThemeData theme = Theme.of(context); final AppLocalizations l10n = AppLocalizations.of(context)!; - final ThunderBloc thunderBloc = context.watch(); - final bool tabletMode = thunderBloc.state.tabletMode; + final bool tabletMode = context.select((bloc) => bloc.state.tabletMode); switch (state.status) { case SearchStatus.initial: diff --git a/lib/src/features/settings/presentation/pages/accessibility_settings_page.dart b/lib/src/features/settings/presentation/pages/accessibility_settings_page.dart index d9c609bc9..78404b839 100644 --- a/lib/src/features/settings/presentation/pages/accessibility_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/accessibility_settings_page.dart @@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/core/enums/local_settings.dart'; import 'package:thunder/src/core/singletons/preferences.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; import 'package:thunder/src/shared/utils/constants.dart'; @@ -35,7 +35,7 @@ class _AccessibilitySettingsPageState extends State w case LocalSettings.reduceAnimations: await prefs.setBool(LocalSettings.reduceAnimations.name, value); setState(() => reduceAnimations = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) context.read().reload(); break; default: break; diff --git a/lib/src/features/settings/presentation/pages/comment_appearance_settings_page.dart b/lib/src/features/settings/presentation/pages/comment_appearance_settings_page.dart index 838500916..516147290 100644 --- a/lib/src/features/settings/presentation/pages/comment_appearance_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/comment_appearance_settings_page.dart @@ -13,6 +13,7 @@ import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/shared/dialogs.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/bottom_sheet_list_picker.dart'; import 'package:thunder/src/features/comment/comment.dart'; import 'package:thunder/src/shared/utils/constants.dart'; @@ -105,6 +106,7 @@ class _CommentAppearanceSettingsPageState extends State().add(UserPreferencesChangeEvent()); + context.read().reload(); } } @@ -123,6 +125,7 @@ class _CommentAppearanceSettingsPageState extends State().add(UserPreferencesChangeEvent()); + context.read().reload(); } } diff --git a/lib/src/features/settings/presentation/pages/fab_settings_page.dart b/lib/src/features/settings/presentation/pages/fab_settings_page.dart index b85ae9f00..f9c816bc9 100644 --- a/lib/src/features/settings/presentation/pages/fab_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/fab_settings_page.dart @@ -11,6 +11,7 @@ import 'package:thunder/src/core/singletons/preferences.dart'; import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/fab_preferences_cubit/fab_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/bottom_sheet_list_picker.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -163,6 +164,7 @@ class _FabSettingsPage extends State with TickerProviderStateMi if (context.mounted) { context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } diff --git a/lib/src/features/settings/presentation/pages/filter_settings_page.dart b/lib/src/features/settings/presentation/pages/filter_settings_page.dart index 75ed688a4..a971a0aaf 100644 --- a/lib/src/features/settings/presentation/pages/filter_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/filter_settings_page.dart @@ -12,6 +12,7 @@ import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/shared/dialogs.dart'; import 'package:thunder/src/shared/input_dialogs.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/app/utils/navigation.dart'; @@ -46,6 +47,7 @@ class _FilterSettingsPageState extends State with SingleTick if (context.mounted) { context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } diff --git a/lib/src/features/settings/presentation/pages/general_settings_page.dart b/lib/src/features/settings/presentation/pages/general_settings_page.dart index c0449450b..488e224cb 100644 --- a/lib/src/features/settings/presentation/pages/general_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/general_settings_page.dart @@ -30,6 +30,7 @@ import 'package:thunder/src/shared/divider.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/shared/sort_picker.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/bottom_sheet_list_picker.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -267,6 +268,7 @@ class _GeneralSettingsPageState extends State with SingleTi if (context.mounted) { context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } @@ -1033,6 +1035,7 @@ class _GeneralSettingsPageState extends State with SingleTi if (context.mounted) { _initPreferences(); context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } else { showSnackbar(l10n.settingsNotImportedSuccessfully); } diff --git a/lib/src/features/settings/presentation/pages/gesture_settings_page.dart b/lib/src/features/settings/presentation/pages/gesture_settings_page.dart index b97ba6e82..58ed9dd1b 100644 --- a/lib/src/features/settings/presentation/pages/gesture_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/gesture_settings_page.dart @@ -9,6 +9,7 @@ import 'package:thunder/src/core/enums/swipe_action.dart'; import 'package:thunder/src/core/singletons/preferences.dart'; import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/bottom_sheet_list_picker.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -144,6 +145,7 @@ class _GestureSettingsPageState extends State with TickerPr if (context.mounted) { context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } diff --git a/lib/src/features/settings/presentation/pages/post_appearance_settings_page.dart b/lib/src/features/settings/presentation/pages/post_appearance_settings_page.dart index fb4abefef..7bf1a8a58 100644 --- a/lib/src/features/settings/presentation/pages/post_appearance_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/post_appearance_settings_page.dart @@ -22,6 +22,7 @@ import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/shared/dialogs.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/bottom_sheet_list_picker.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -286,6 +287,7 @@ class _PostAppearanceSettingsPageState extends State if (context.mounted) { context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } @@ -324,6 +326,7 @@ class _PostAppearanceSettingsPageState extends State if (context.mounted) { context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } diff --git a/lib/src/features/settings/presentation/pages/theme_settings_page.dart b/lib/src/features/settings/presentation/pages/theme_settings_page.dart index d40f8d063..5fa6f871c 100644 --- a/lib/src/features/settings/presentation/pages/theme_settings_page.dart +++ b/lib/src/features/settings/presentation/pages/theme_settings_page.dart @@ -14,9 +14,9 @@ import 'package:thunder/src/core/enums/full_name.dart'; import 'package:thunder/src/core/enums/local_settings.dart'; import 'package:thunder/src/core/enums/theme_type.dart'; import 'package:thunder/src/core/singletons/preferences.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; import 'package:thunder/src/features/settings/settings.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/bottom_sheet_list_picker.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -111,23 +111,31 @@ class _ThemeSettingsPageState extends State { case LocalSettings.appTheme: await prefs.setInt(LocalSettings.appTheme.name, value); setState(() => themeType = ThemeType.values[value]); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } 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.usePureBlackTheme: await prefs.setBool(LocalSettings.usePureBlackTheme.name, value); setState(() => usePureBlackTheme = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } break; case LocalSettings.appThemeAccentColor: await prefs.setString(LocalSettings.appThemeAccentColor.name, (value as CustomThemeType).name); setState(() => selectedTheme = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } break; case LocalSettings.useMaterialYouTheme: await prefs.setBool(LocalSettings.useMaterialYouTheme.name, value); setState(() => useMaterialYouTheme = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } break; // Color settings @@ -160,22 +168,30 @@ class _ThemeSettingsPageState extends State { case LocalSettings.titleFontSizeScale: await prefs.setString(LocalSettings.titleFontSizeScale.name, (value as FontScale).name); setState(() => titleFontSizeScale = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } break; case LocalSettings.contentFontSizeScale: await prefs.setString(LocalSettings.contentFontSizeScale.name, (value as FontScale).name); setState(() => contentFontSizeScale = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } break; case LocalSettings.commentFontSizeScale: await prefs.setString(LocalSettings.commentFontSizeScale.name, (value as FontScale).name); setState(() => commentFontSizeScale = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } break; case LocalSettings.metadataFontSizeScale: await prefs.setString(LocalSettings.metadataFontSizeScale.name, (value as FontScale).name); setState(() => metadataFontSizeScale = value); - if (context.mounted) context.read().add(ThemeChangeEvent()); + if (context.mounted) { + context.read().reload(); + } break; // Name Settings @@ -233,6 +249,7 @@ class _ThemeSettingsPageState extends State { if (context.mounted) { context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } diff --git a/lib/src/features/settings/presentation/pages/video_player_settings.dart b/lib/src/features/settings/presentation/pages/video_player_settings.dart index e19d3eae0..d97337339 100644 --- a/lib/src/features/settings/presentation/pages/video_player_settings.dart +++ b/lib/src/features/settings/presentation/pages/video_player_settings.dart @@ -10,7 +10,7 @@ import 'package:thunder/src/core/enums/video_playback_speed.dart'; import 'package:thunder/src/core/enums/video_player_mode.dart'; import 'package:thunder/src/core/singletons/preferences.dart'; import 'package:thunder/src/features/settings/settings.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/bottom_sheet_list_picker.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/app/utils/global_context.dart'; @@ -111,7 +111,7 @@ class _VideoPlayerSettingsPageState extends State { } if (context.mounted) { - context.read().add(UserPreferencesChangeEvent()); + context.read().reload(); } } diff --git a/lib/src/features/user/presentation/pages/media_management_page.dart b/lib/src/features/user/presentation/pages/media_management_page.dart index 7c7ac66db..1cfac731e 100644 --- a/lib/src/features/user/presentation/pages/media_management_page.dart +++ b/lib/src/features/user/presentation/pages/media_management_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:extended_image/extended_image.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -6,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/features/comment/comment.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/core/enums/image_caching_mode.dart'; import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/shared/dialogs.dart'; @@ -13,6 +15,8 @@ import 'package:thunder/src/shared/full_name_widgets.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/utils/constants.dart'; import 'package:thunder/src/shared/utils/media/image.dart'; @@ -22,9 +26,12 @@ class MediaManagementPage extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final AppLocalizations l10n = AppLocalizations.of(context)!; - final ThunderBloc thunderBloc = context.read(); + final theme = Theme.of(context); + final l10n = AppLocalizations.of(context)!; + + final dateFormat = context.select((cubit) => cubit.state.dateFormat); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); + final imageCachingMode = context.select((cubit) => cubit.state.imageCachingMode); return BlocBuilder( builder: (context, state) { @@ -95,7 +102,7 @@ class MediaManagementPage extends StatelessWidget { ExtendedImage.network( url, cache: true, - clearMemoryCacheWhenDispose: thunderBloc.state.imageCachingMode == ImageCachingMode.relaxed, + clearMemoryCacheWhenDispose: imageCachingMode == ImageCachingMode.relaxed, loadStateChanged: (state) { if (state.extendedImageLoadState == LoadState.loading) { return SizedBox( @@ -143,7 +150,7 @@ class MediaManagementPage extends StatelessWidget { Row( children: [ const SizedBox(width: 12), - Text(l10n.uploadedDate(thunderBloc.state.dateFormat?.format(DateTime.parse(state.images![index]['local_image']['published']).toLocal()) ?? '')), + Text(l10n.uploadedDate(dateFormat?.format(DateTime.parse(state.images![index]['local_image']['published']).toLocal()) ?? '')), const Spacer(), IconButton( onPressed: () async { @@ -217,7 +224,7 @@ class MediaManagementPage extends StatelessWidget { l10n.noReferencesToImage, textAlign: TextAlign.center, style: theme.textTheme.titleSmall, - fontScale: thunderBloc.state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, ), ), ), @@ -280,7 +287,7 @@ class MediaManagementPage extends StatelessWidget { l10n.noImages, textAlign: TextAlign.center, style: theme.textTheme.titleSmall, - fontScale: thunderBloc.state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, ), ), ), diff --git a/lib/src/features/user/presentation/utils/user_groups.dart b/lib/src/features/user/presentation/utils/user_groups.dart index f50b34695..93073adf2 100644 --- a/lib/src/features/user/presentation/utils/user_groups.dart +++ b/lib/src/features/user/presentation/utils/user_groups.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/core/enums/user_type.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/features/account/account.dart'; import 'package:thunder/src/features/comment/comment.dart'; @@ -34,7 +34,7 @@ List getCommentUserGroups(ThunderComment comment, Account account) { /// The order is: OP > Self > Admin > Moderator > Bot Color? fetchUserGroupColor(BuildContext context, List userGroups) { final theme = Theme.of(context); - final bool darkTheme = context.read().state.useDarkTheme; + final bool darkTheme = context.read().state.useDarkTheme; Color? color; diff --git a/lib/src/features/user/presentation/widgets/user_label_chip.dart b/lib/src/features/user/presentation/widgets/user_label_chip.dart index e2451c5e7..6d0eed655 100644 --- a/lib/src/features/user/presentation/widgets/user_label_chip.dart +++ b/lib/src/features/user/presentation/widgets/user_label_chip.dart @@ -4,7 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/shared/utils/colors.dart'; /// A widget that displays a user's label in a chip format. @@ -22,7 +23,7 @@ class UserLabelChip extends StatelessWidget { @override Widget build(BuildContext context) { - final metadataFontSizeScale = context.select((ThunderBloc bloc) => bloc.state.metadataFontSizeScale); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); final color = getBackgroundColor(context); return FutureBuilder( diff --git a/lib/src/shared/comment_reference.dart b/lib/src/shared/comment_reference.dart index f75b2d2f3..b9a719001 100644 --- a/lib/src/shared/comment_reference.dart +++ b/lib/src/shared/comment_reference.dart @@ -9,7 +9,7 @@ import 'package:thunder/src/features/comment/comment.dart'; import 'package:thunder/src/shared/full_name_widgets.dart'; import 'package:thunder/src/shared/snackbar.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/date_time.dart'; import 'package:thunder/src/shared/utils/instance.dart'; import 'package:thunder/src/app/utils/navigation.dart'; @@ -96,7 +96,7 @@ class _CommentReferenceHeader extends StatelessWidget { final theme = Theme.of(context); final l10n = GlobalContext.l10n; - final contentFontSizeScale = context.select((bloc) => bloc.state.contentFontSizeScale); + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); return Padding( padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), diff --git a/lib/src/shared/full_name_widgets.dart b/lib/src/shared/full_name_widgets.dart index 9828a78e6..3e7aa0cbc 100644 --- a/lib/src/shared/full_name_widgets.dart +++ b/lib/src/shared/full_name_widgets.dart @@ -5,7 +5,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/core/enums/full_name.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; /// A customizable [Text] widget which displays the given name and instance based on the user preferences. /// @@ -50,14 +50,16 @@ class UserFullNameWidget extends StatelessWidget { @override Widget build(BuildContext context) { - String prefix = generateUserFullNamePrefix(outerContext, name, displayName, userSeparator: userSeparator, useDisplayName: useDisplayName); - String suffix = generateUserFullNameSuffix(outerContext, instance, userSeparator: userSeparator); - NameThickness userNameThickness = this.userNameThickness ?? outerContext!.read().state.userFullNameUserNameThickness; - NameColor userNameColor = this.userNameColor ?? outerContext!.read().state.userFullNameUserNameColor; - NameThickness instanceNameThickness = this.instanceNameThickness ?? outerContext!.read().state.userFullNameInstanceNameThickness; - NameColor instanceNameColor = this.instanceNameColor ?? outerContext!.read().state.userFullNameInstanceNameColor; - TextStyle? textStyle = this.textStyle ?? Theme.of(outerContext!).textTheme.bodyMedium; - Color? Function(Color?) transformColor = this.transformColor ?? (color) => color; + final prefix = generateUserFullNamePrefix(outerContext, name, displayName, userSeparator: userSeparator, useDisplayName: useDisplayName); + final suffix = generateUserFullNameSuffix(outerContext, instance, userSeparator: userSeparator); + + final userNameThickness = this.userNameThickness ?? outerContext!.select((cubit) => cubit.state.userFullNameUserNameThickness); + final userNameColor = this.userNameColor ?? outerContext!.select((cubit) => cubit.state.userFullNameUserNameColor); + final instanceNameThickness = this.instanceNameThickness ?? outerContext!.select((cubit) => cubit.state.userFullNameInstanceNameThickness); + final instanceNameColor = this.instanceNameColor ?? outerContext!.select((cubit) => cubit.state.userFullNameInstanceNameColor); + + final textStyle = this.textStyle ?? Theme.of(outerContext!).textTheme.bodyMedium; + final transformColor = this.transformColor ?? (color) => color; TextSpan textSpan = TextSpan( children: [ @@ -146,10 +148,10 @@ class CommunityFullNameWidget extends StatelessWidget { Widget build(BuildContext context) { String prefix = generateCommunityFullNamePrefix(outerContext, name, displayName, communitySeparator: communitySeparator, useDisplayName: useDisplayName); String suffix = generateCommunityFullNameSuffix(outerContext, instance, communitySeparator: communitySeparator); - NameThickness communityNameThickness = this.communityNameThickness ?? outerContext!.read().state.communityFullNameCommunityNameThickness; - NameColor communityNameColor = this.communityNameColor ?? outerContext!.read().state.communityFullNameCommunityNameColor; - NameThickness instanceNameThickness = this.instanceNameThickness ?? outerContext!.read().state.communityFullNameInstanceNameThickness; - NameColor instanceNameColor = this.instanceNameColor ?? outerContext!.read().state.communityFullNameInstanceNameColor; + NameThickness communityNameThickness = this.communityNameThickness ?? outerContext!.read().state.communityFullNameCommunityNameThickness; + NameColor communityNameColor = this.communityNameColor ?? outerContext!.read().state.communityFullNameCommunityNameColor; + NameThickness instanceNameThickness = this.instanceNameThickness ?? outerContext!.read().state.communityFullNameInstanceNameThickness; + NameColor instanceNameColor = this.instanceNameColor ?? outerContext!.read().state.communityFullNameInstanceNameColor; TextStyle? textStyle = this.textStyle ?? Theme.of(outerContext!).textTheme.bodyMedium; Color? Function(Color?) transformColor = this.transformColor ?? (color) => color; diff --git a/lib/src/shared/gesture_fab.dart b/lib/src/shared/gesture_fab.dart index 3c6d09d7d..7bceed454 100644 --- a/lib/src/shared/gesture_fab.dart +++ b/lib/src/shared/gesture_fab.dart @@ -2,11 +2,15 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; + import 'package:flutter_bloc/flutter_bloc.dart'; + import 'package:thunder/l10n/generated/app_localizations.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/fab_cubit/fab_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; -import '../app/bloc/thunder_bloc.dart'; +/// Enum to distinguish between feed and post FABs +enum FabType { feed, post } class GestureFab extends StatefulWidget { const GestureFab({ @@ -23,6 +27,7 @@ class GestureFab extends StatefulWidget { this.centered = false, this.heroTag, this.fabBackgroundColor, + this.fabType = FabType.feed, }); final bool? initialOpen; @@ -38,6 +43,9 @@ class GestureFab extends StatefulWidget { final String? heroTag; final Color? fabBackgroundColor; + /// The type of FAB (feed or post) - determines which state to use + final FabType fabType; + @override State createState() => _GestureFabState(); } @@ -73,19 +81,45 @@ class _GestureFabState extends State with SingleTickerProviderStateM super.dispose(); } + /// Gets the current isFabOpen state based on the fabType + bool _getIsFabOpen(FabStateState state) { + return widget.fabType == FabType.feed ? state.isFeedFabOpen : state.isPostFabOpen; + } + + /// Sets the FAB open state based on the fabType + void _setFabOpen(BuildContext context, bool isOpen) { + final cubit = context.read(); + if (widget.fabType == FabType.feed) { + cubit.setFeedFabOpen(isOpen); + } else { + cubit.setPostFabOpen(isOpen); + } + } + + /// Sets the FAB summoned state based on the fabType + void _setFabSummoned(BuildContext context, bool isSummoned) { + final cubit = context.read(); + if (widget.fabType == FabType.feed) { + cubit.setFeedFabSummoned(isSummoned); + } else { + cubit.setPostFabSummoned(isSummoned); + } + } + @override Widget build(BuildContext context) { - return BlocConsumer( - listenWhen: (previous, current) => previous.isFabOpen != current.isFabOpen, + return BlocConsumer( + listenWhen: (previous, current) => _getIsFabOpen(previous) != _getIsFabOpen(current), listener: (context, state) { - if (state.isFabOpen) { + final isOpen = _getIsFabOpen(state); + if (isOpen) { _controller.forward(); } else { _controller.reverse(); } - if (isFabOpen != state.isFabOpen) { - setState(() => isFabOpen = state.isFabOpen); + if (isFabOpen != isOpen) { + setState(() => isFabOpen = isOpen); } }, builder: (context, state) { @@ -122,7 +156,7 @@ class _GestureFabState extends State with SingleTickerProviderStateM child: InkWell( borderRadius: BorderRadius.circular(50), onTap: () { - context.read().add(const OnFabToggle(false)); + _setFabOpen(context, false); }, child: Padding( padding: EdgeInsets.all(widget.centered ? 12 : 8), @@ -179,11 +213,11 @@ class _GestureFabState extends State with SingleTickerProviderStateM child: GestureDetector( onVerticalDragUpdate: (details) { if (details.delta.dy < -5) { - context.read().add(const OnFabToggle(true)); + _setFabOpen(context, true); } if (details.delta.dy > 5) { // Only allow hiding fab when on the main feed, and not when opening a community on a new page - if (Navigator.of(context).canPop() == false) context.read().add(const OnFabSummonToggle(false)); + if (Navigator.of(context).canPop() == false) _setFabSummoned(context, false); } }, onHorizontalDragStart: null, @@ -237,6 +271,7 @@ class ActionButton extends StatelessWidget { required this.icon, this.centered = false, this.backgroundColor, + this.fabType = FabType.feed, }); final VoidCallback? onPressed; @@ -244,14 +279,25 @@ class ActionButton extends StatelessWidget { final String? title; final bool centered; final Color? backgroundColor; + final FabType fabType; bool? first; bool? last; + /// Sets the FAB open state based on the fabType + void _setFabOpen(BuildContext context, bool isOpen) { + final cubit = context.read(); + if (fabType == FabType.feed) { + cubit.setFeedFabOpen(isOpen); + } else { + cubit.setPostFabOpen(isOpen); + } + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); - final bool darkTheme = context.read().state.useDarkTheme; + final bool darkTheme = context.read().state.useDarkTheme; return centered ? SizedBox( @@ -287,7 +333,7 @@ class ActionButton extends StatelessWidget { bottomRight: Radius.circular(last == true ? 20 : 0), ), onTap: () { - context.read().add(const OnFabToggle(true)); + _setFabOpen(context, false); onPressed?.call(); }, ), @@ -344,7 +390,7 @@ class ActionButton extends StatelessWidget { elevation: 4, child: InkWell( onTap: () { - context.read().add(const OnFabToggle(true)); + _setFabOpen(context, false); onPressed?.call(); }, child: icon, diff --git a/lib/src/shared/markdown/common_markdown_body.dart b/lib/src/shared/markdown/common_markdown_body.dart index 25a67d3f3..a7ddb0d6c 100644 --- a/lib/src/shared/markdown/common_markdown_body.dart +++ b/lib/src/shared/markdown/common_markdown_body.dart @@ -17,7 +17,7 @@ import 'package:thunder/src/shared/markdown/markdown_utils.dart'; import 'package:thunder/src/shared/utils/media/image.dart'; import 'package:thunder/src/shared/utils/links.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/shared/markdown/extended_markdown.dart'; import 'package:thunder/src/shared/utils/media/video.dart'; import 'package:thunder/src/shared/widgets/media/media_view.dart'; @@ -92,9 +92,9 @@ class _CommonMarkdownBodyState extends State { }; } - double _getTextScaleFactor(ThunderState state) { + double _getTextScaleFactor(FontScale commentFontSizeScale, FontScale contentFontSizeScale) { final baseScale = MediaQuery.of(context).textScaleFactor; - final fontScale = widget.isComment == true ? state.commentFontSizeScale.textScaleFactor : state.contentFontSizeScale.textScaleFactor; + final fontScale = widget.isComment == true ? commentFontSizeScale.textScaleFactor : contentFontSizeScale.textScaleFactor; return baseScale * fontScale; } @@ -102,7 +102,8 @@ class _CommonMarkdownBodyState extends State { Widget build(BuildContext context) { if (_spoilerMarkdownStyleSheet == null || _normalMarkdownStyleSheet == null) _initializeStyleSheets(); - final state = context.watch().state; + final commentFontSizeScale = context.select((cubit) => cubit.state.commentFontSizeScale); + final contentFontSizeScale = context.select((cubit) => cubit.state.contentFontSizeScale); final styleSheet = widget.hidden ? _spoilerMarkdownStyleSheet! : _normalMarkdownStyleSheet!; // Disable semantics if the accessibility feature is disabled. This allows the widget to be more performant as it doesn't need to compute the semantics tree. @@ -126,9 +127,9 @@ class _CommonMarkdownBodyState extends State { isComment: widget.isComment, imageMaxWidth: widget.imageMaxWidth, ), - onTapLink: (text, url, title) => handleLinkTap(context, state, text, url), + onTapLink: (text, url, title) => handleLinkTap(context, text, url), onLongPressLink: (text, url, title) => handleLinkLongPress(context, text, url), - styleSheet: styleSheet.copyWith(textScaleFactor: _getTextScaleFactor(state)), + styleSheet: styleSheet.copyWith(textScaleFactor: _getTextScaleFactor(commentFontSizeScale, contentFontSizeScale)), ), ), ); diff --git a/lib/src/shared/markdown/markdown_spoiler.dart b/lib/src/shared/markdown/markdown_spoiler.dart index 1692d8257..0d4f0ff2a 100644 --- a/lib/src/shared/markdown/markdown_spoiler.dart +++ b/lib/src/shared/markdown/markdown_spoiler.dart @@ -6,7 +6,8 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:markdown/markdown.dart' as md; import 'package:thunder/l10n/generated/app_localizations.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; import 'package:thunder/src/shared/utils/colors.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; @@ -156,7 +157,7 @@ class _SpoilerWidgetState extends State { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final theme = Theme.of(context); - final state = context.read().state; + final contentFontSizeScale = context.read().state.contentFontSizeScale; return Container( decoration: BoxDecoration( @@ -166,14 +167,14 @@ class _SpoilerWidgetState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildSpoilerHeader(l10n, theme, state), - _buildSpoilerContent(state), + _buildSpoilerHeader(l10n, theme, contentFontSizeScale), + _buildSpoilerContent(contentFontSizeScale), ], ), ); } - Widget _buildSpoilerHeader(AppLocalizations l10n, ThemeData theme, ThunderState state) { + Widget _buildSpoilerHeader(AppLocalizations l10n, ThemeData theme, FontScale contentFontSizeScale) { return Material( color: Colors.transparent, child: InkWell( @@ -197,7 +198,7 @@ class _SpoilerWidgetState extends State { Expanded( child: ScalableText( widget.title ?? l10n.spoiler, - fontScale: state.contentFontSizeScale, + fontScale: contentFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold), ), ), @@ -208,7 +209,7 @@ class _SpoilerWidgetState extends State { ); } - Widget _buildSpoilerContent(ThunderState state) { + Widget _buildSpoilerContent(FontScale contentFontSizeScale) { return Expandable( controller: expandableController, collapsed: const SizedBox.shrink(), diff --git a/lib/src/shared/markdown/markdown_subsuperscript.dart b/lib/src/shared/markdown/markdown_subsuperscript.dart index df8bfe38d..6d727b1d3 100644 --- a/lib/src/shared/markdown/markdown_subsuperscript.dart +++ b/lib/src/shared/markdown/markdown_subsuperscript.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:markdown/markdown.dart' as md; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; enum CustomMarkdownType { superscript, subscript } @@ -91,7 +91,7 @@ class SuperscriptSubscriptWidget extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final state = context.read().state; + final contentFontSizeScale = context.read().state.contentFontSizeScale; return RichText( text: TextSpan( @@ -101,7 +101,7 @@ class SuperscriptSubscriptWidget extends StatelessWidget { offset: Offset(0.0, type == CustomMarkdownType.subscript ? 3.0 : -5.0), child: ScalableText( text, - fontScale: state.contentFontSizeScale, + fontScale: contentFontSizeScale, style: theme.textTheme.bodyMedium?.copyWith(fontSize: 11), ), ), diff --git a/lib/src/shared/pages/loading_page.dart b/lib/src/shared/pages/loading_page.dart index e7b68ad6a..2278fd21c 100644 --- a/lib/src/shared/pages/loading_page.dart +++ b/lib/src/shared/pages/loading_page.dart @@ -1,100 +1,102 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; -import 'package:thunder/src/app/routing/swipeable_page_route.dart'; -import 'package:thunder/src/shared/utils/constants.dart'; - -bool isLoadingPageShown = false; - -class LoadingPage extends StatelessWidget { - const LoadingPage({super.key}); - - @override - Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - - return Scaffold( - body: Container( - color: theme.colorScheme.surface, - child: SafeArea( - top: false, - child: CustomScrollView( - slivers: [ - SliverAppBar( - toolbarHeight: APP_BAR_HEIGHT, - leading: IconButton( - icon: !kIsWeb && Platform.isIOS - ? Icon( - Icons.arrow_back_ios_new_rounded, - semanticLabel: MaterialLocalizations.of(context).backButtonTooltip, - ) - : Icon(Icons.arrow_back_rounded, semanticLabel: MaterialLocalizations.of(context).backButtonTooltip), - onPressed: null, - )), - const SliverFillRemaining( - child: Center( - child: CircularProgressIndicator(), - ), - ), - ], - ), - ), - ), - ); - } -} - -void showLoadingPage(BuildContext context) { - if (isLoadingPageShown) return; - - isLoadingPageShown = true; - - // Immediately push the loading page. - final ThunderBloc thunderBloc = context.read(); - final bool reduceAnimations = thunderBloc.state.reduceAnimations; - Navigator.of(context).push( - SwipeablePageRoute( - transitionDuration: reduceAnimations ? const Duration(milliseconds: 100) : null, - backGestureDetectionWidth: 45, - canOnlySwipeFromEdge: !thunderBloc.state.enableFullScreenSwipeNavigationGesture, - canSwipe: false, - builder: (context) => MultiBlocProvider( - providers: [ - BlocProvider.value(value: thunderBloc), - ], - child: PopScope( - onPopInvokedWithResult: (didPop, result) => isLoadingPageShown = !didPop, - child: const LoadingPage(), - ), - ), - ), - ); -} - -Future hideLoadingPage(BuildContext context, {bool delay = false}) async { - if (isLoadingPageShown) { - isLoadingPageShown = false; - - if (delay) { - await Future.delayed(const Duration(seconds: 1)); - } - - if (context.mounted) { - Navigator.of(context).maybePop(); - } - } -} - -Future pushOnTopOfLoadingPage(BuildContext context, Route route) async { - if (isLoadingPageShown) { - isLoadingPageShown = false; - return await Navigator.of(context).pushReplacement(route); - } else { - return await Navigator.of(context).push(route); - } -} +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/app/routing/swipeable_page_route.dart'; +import 'package:thunder/src/app/thunder.dart'; +import 'package:thunder/src/shared/utils/constants.dart'; + +bool isLoadingPageShown = false; + +class LoadingPage extends StatelessWidget { + const LoadingPage({super.key}); + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + + return Scaffold( + body: Container( + color: theme.colorScheme.surface, + child: SafeArea( + top: false, + child: CustomScrollView( + slivers: [ + SliverAppBar( + toolbarHeight: APP_BAR_HEIGHT, + leading: IconButton( + icon: !kIsWeb && Platform.isIOS + ? Icon( + Icons.arrow_back_ios_new_rounded, + semanticLabel: MaterialLocalizations.of(context).backButtonTooltip, + ) + : Icon(Icons.arrow_back_rounded, semanticLabel: MaterialLocalizations.of(context).backButtonTooltip), + onPressed: null, + )), + const SliverFillRemaining( + child: Center( + child: CircularProgressIndicator(), + ), + ), + ], + ), + ), + ), + ); + } +} + +void showLoadingPage(BuildContext context) { + if (isLoadingPageShown) return; + + isLoadingPageShown = true; + + final enableFullScreenSwipeNavigationGesture = context.read().state.enableFullScreenSwipeNavigationGesture; + final reduceAnimations = context.read().state.reduceAnimations; + + // Immediately push the loading page. + Navigator.of(context).push( + SwipeablePageRoute( + transitionDuration: reduceAnimations ? const Duration(milliseconds: 100) : null, + backGestureDetectionWidth: 45, + canOnlySwipeFromEdge: !enableFullScreenSwipeNavigationGesture, + canSwipe: false, + builder: (context) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: context.read()), + ], + child: PopScope( + onPopInvokedWithResult: (didPop, result) => isLoadingPageShown = !didPop, + child: const LoadingPage(), + ), + ), + ), + ); +} + +Future hideLoadingPage(BuildContext context, {bool delay = false}) async { + if (isLoadingPageShown) { + isLoadingPageShown = false; + + if (delay) { + await Future.delayed(const Duration(seconds: 1)); + } + + if (context.mounted) { + Navigator.of(context).maybePop(); + } + } +} + +Future pushOnTopOfLoadingPage(BuildContext context, Route route) async { + if (isLoadingPageShown) { + isLoadingPageShown = false; + return await Navigator.of(context).pushReplacement(route); + } else { + return await Navigator.of(context).push(route); + } +} diff --git a/lib/src/shared/utils/colors.dart b/lib/src/shared/utils/colors.dart index b5e23d9f6..b1de9fc0b 100644 --- a/lib/src/shared/utils/colors.dart +++ b/lib/src/shared/utils/colors.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; + import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; + +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/constants.dart'; /// Gets a tinted background color that looks good in light and dark mode Color getBackgroundColor(BuildContext context) { - final useDarkTheme = context.read().state.useDarkTheme; + final useDarkTheme = context.read().state.useDarkTheme; return useDarkTheme ? DARK_THEME_BACKGROUND_COLOR : LIGHT_THEME_BACKGROUND_COLOR; } diff --git a/lib/src/shared/utils/links.dart b/lib/src/shared/utils/links.dart index 9be761ace..87e719a7f 100644 --- a/lib/src/shared/utils/links.dart +++ b/lib/src/shared/utils/links.dart @@ -29,6 +29,7 @@ import 'package:thunder/src/shared/picker_item.dart'; import 'package:thunder/src/shared/utils/media/image.dart'; import 'package:thunder/src/shared/utils/media/video.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart'; import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/shared/utils/instance.dart'; import 'package:thunder/src/features/user/user.dart'; @@ -72,20 +73,24 @@ Future getLinkInfo(String url) async { } void _openLink(BuildContext context, {required String url, bool isVideo = false}) async { - ThunderState state = context.read().state; + final thunderPreferences = context.read().state; + final browserMode = thunderPreferences.browserMode; + final openInReaderMode = thunderPreferences.openInReaderMode; + + final videoPlayerMode = context.read().state.videoPlayerMode; bool launchInExternalApp = false; bool launchInCustomTab = false; - if (isVideo && state.videoPlayerMode == VideoPlayerMode.externalPlayer) { + if (isVideo && videoPlayerMode == VideoPlayerMode.externalPlayer) { launchInExternalApp = true; - } else if (!isVideo && state.browserMode == BrowserMode.external) { + } else if (!isVideo && browserMode == BrowserMode.external) { launchInExternalApp = true; } - if (isVideo && state.videoPlayerMode == VideoPlayerMode.customTabs) { + if (isVideo && videoPlayerMode == VideoPlayerMode.customTabs) { launchInCustomTab = true; - } else if (!isVideo && state.browserMode == BrowserMode.customTabs) { + } else if (!isVideo && browserMode == BrowserMode.customTabs) { launchInCustomTab = true; } @@ -116,10 +121,10 @@ void _openLink(BuildContext context, {required String url, bool isVideo = false} preferredBarTintColor: Theme.of(context).canvasColor, preferredControlTintColor: Theme.of(context).textTheme.titleLarge?.color ?? Theme.of(context).primaryColor, barCollapsingEnabled: true, - entersReaderIfAvailable: state.openInReaderMode, + entersReaderIfAvailable: openInReaderMode, ), ); - } else if (state.browserMode == BrowserMode.inApp) { + } else if (browserMode == BrowserMode.inApp) { // Launches the link within the in-app browser if possible // Check if the scheme is not https, in which case the in-app browser can't handle it Uri? uri = Uri.tryParse(url); @@ -368,7 +373,6 @@ class _LinkBottomSheetState extends State { Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final AppLocalizations l10n = AppLocalizations.of(context)!; - final ThunderState thunderState = context.read().state; bool isValidUrl = widget.url?.startsWith('http') ?? false; @@ -451,7 +455,7 @@ class _LinkBottomSheetState extends State { PickerItem( label: l10n.open, icon: Icons.language, - onSelected: () => handleLinkTap(context, thunderState, widget.text, widget.url), + onSelected: () => handleLinkTap(context, widget.text, widget.url), ), PickerItem( label: l10n.copy, @@ -500,7 +504,7 @@ class _LinkBottomSheetState extends State { } } -Future handleLinkTap(BuildContext context, ThunderState state, String text, String? url) async { +Future handleLinkTap(BuildContext context, String text, String? url) async { Uri? parsedUri = Uri.tryParse(url ?? '') ?? Uri.tryParse(text); String parsedUrl = text; diff --git a/lib/src/shared/utils/media/video.dart b/lib/src/shared/utils/media/video.dart index 3d74b86b0..574af2a87 100644 --- a/lib/src/shared/utils/media/video.dart +++ b/lib/src/shared/utils/media/video.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/src/core/enums/video_player_mode.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; -import 'package:thunder/src/shared/utils/links.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:thunder/src/core/enums/video_player_mode.dart'; +import 'package:thunder/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart'; +import 'package:thunder/src/shared/utils/links.dart'; import 'package:thunder/src/shared/utils/video_player/video_player.dart'; bool isVideoUrl(String url) { @@ -42,9 +42,9 @@ void showVideoPlayer(BuildContext context, {String? url, int? postId}) { String? videoId = YoutubePlayer.convertUrlToId(url); - final thunderState = context.read().state; + final videoPlayerMode = context.read().state.videoPlayerMode; - switch (thunderState.videoPlayerMode) { + switch (videoPlayerMode) { case VideoPlayerMode.inApp: Navigator.of(context).push( PageRouteBuilder( diff --git a/lib/src/shared/utils/swipe.dart b/lib/src/shared/utils/swipe.dart index b97449858..c8a00fa39 100644 --- a/lib/src/shared/utils/swipe.dart +++ b/lib/src/shared/utils/swipe.dart @@ -1,36 +1,40 @@ import 'package:flutter/widgets.dart'; import 'package:thunder/src/core/enums/swipe_action.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; - -DismissDirection determinePostSwipeDirection(bool isUserLoggedIn, ThunderState state, {bool disableSwiping = false}) { +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; + +DismissDirection determinePostSwipeDirection({ + required bool isUserLoggedIn, + required bool enablePostGestures, + required SwipeAction leftPrimaryPostGesture, + required SwipeAction leftSecondaryPostGesture, + required SwipeAction rightPrimaryPostGesture, + required SwipeAction rightSecondaryPostGesture, + bool disableSwiping = false, +}) { if (!isUserLoggedIn) return DismissDirection.none; - if (state.enablePostGestures == false) return DismissDirection.none; + if (enablePostGestures == false) return DismissDirection.none; if (disableSwiping) return DismissDirection.none; // If all of the actions are none, then disable swiping - if (state.leftPrimaryPostGesture == SwipeAction.none && - state.leftSecondaryPostGesture == SwipeAction.none && - state.rightPrimaryPostGesture == SwipeAction.none && - state.rightSecondaryPostGesture == SwipeAction.none) { + if (leftPrimaryPostGesture == SwipeAction.none && leftSecondaryPostGesture == SwipeAction.none && rightPrimaryPostGesture == SwipeAction.none && rightSecondaryPostGesture == SwipeAction.none) { return DismissDirection.none; } // If there is at least 1 action on either side, then allow swiping from both sides - if ((state.leftPrimaryPostGesture != SwipeAction.none || state.leftSecondaryPostGesture != SwipeAction.none) && - (state.rightPrimaryPostGesture != SwipeAction.none || state.rightSecondaryPostGesture != SwipeAction.none)) { + if ((leftPrimaryPostGesture != SwipeAction.none || leftSecondaryPostGesture != SwipeAction.none) && (rightPrimaryPostGesture != SwipeAction.none || rightSecondaryPostGesture != SwipeAction.none)) { return DismissDirection.horizontal; } // If there is no action on left side, disable left side swiping - if (state.leftPrimaryPostGesture == SwipeAction.none && state.leftSecondaryPostGesture == SwipeAction.none) { + if (leftPrimaryPostGesture == SwipeAction.none && leftSecondaryPostGesture == SwipeAction.none) { return DismissDirection.endToStart; } // If there is no action on the right side, disable right side swiping - if (state.rightPrimaryPostGesture == SwipeAction.none && state.rightSecondaryPostGesture == SwipeAction.none) { + if (rightPrimaryPostGesture == SwipeAction.none && rightSecondaryPostGesture == SwipeAction.none) { return DismissDirection.startToEnd; } @@ -56,7 +60,7 @@ DismissDirection determineCommentSwipeDirection(bool isUserLoggedIn, bool enable return DismissDirection.none; } -bool disableFullPageSwipe({bool isUserLoggedIn = false, required ThunderState state, bool isPostPage = false, isFeedPage = false}) { +bool disableFullPageSwipe({bool isUserLoggedIn = false, required GesturePreferencesState state, bool isPostPage = false, isFeedPage = false}) { if (isPostPage == false && isFeedPage == false) { return false; } @@ -74,7 +78,14 @@ bool disableFullPageSwipe({bool isUserLoggedIn = false, required ThunderState st if (isFeedPage) { // If the page we are pushing is a feed type page (community/user page), then we check for swipe actions on posts - direction = determinePostSwipeDirection(isUserLoggedIn, state); + direction = determinePostSwipeDirection( + isUserLoggedIn: isUserLoggedIn, + enablePostGestures: state.enablePostGestures, + leftPrimaryPostGesture: state.leftPrimaryPostGesture, + leftSecondaryPostGesture: state.leftSecondaryPostGesture, + rightPrimaryPostGesture: state.rightPrimaryPostGesture, + rightSecondaryPostGesture: state.rightSecondaryPostGesture, + ); } if (direction == DismissDirection.none || direction == DismissDirection.endToStart) { diff --git a/lib/src/shared/utils/video_player/src/thunder_video_player.dart b/lib/src/shared/utils/video_player/src/thunder_video_player.dart index 0b94edeea..5a3ffa483 100644 --- a/lib/src/shared/utils/video_player/src/thunder_video_player.dart +++ b/lib/src/shared/utils/video_player/src/thunder_video_player.dart @@ -13,7 +13,7 @@ import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/core/enums/internet_connection_type.dart'; import 'package:thunder/src/core/enums/video_auto_play.dart'; import 'package:thunder/src/shared/snackbar.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart'; import 'package:thunder/src/app/cubits/network_checker_cubit/network_checker_cubit.dart'; import 'package:thunder/src/shared/utils/links.dart'; @@ -62,12 +62,12 @@ class _ThunderVideoPlayerState extends State { _initializePlayer(); } - bool autoPlayVideo(ThunderState thunderBloc) { + bool autoPlayVideo(VideoPreferencesState videoState) { final networkCubit = context.read().state; - if (thunderBloc.videoAutoPlay == VideoAutoPlay.always) { + if (videoState.videoAutoPlay == VideoAutoPlay.always) { return true; - } else if (thunderBloc.videoAutoPlay == VideoAutoPlay.onWifi && networkCubit.internetConnectionType == InternetConnectionType.wifi) { + } else if (videoState.videoAutoPlay == VideoAutoPlay.onWifi && networkCubit.internetConnectionType == InternetConnectionType.wifi) { return true; } @@ -75,16 +75,16 @@ class _ThunderVideoPlayerState extends State { } Future _initializePlayer() async { - final state = context.read().state; + final videoState = context.read().state; _videoPlayerController = VideoPlayerController.networkUrl( Uri.parse(widget.videoUrl), videoPlayerOptions: VideoPlayerOptions(), ); - _videoPlayerController.setVolume(state.videoAutoMute ? 0 : 1); - _videoPlayerController.setPlaybackSpeed(state.videoDefaultPlaybackSpeed.value); - _videoPlayerController.setLooping(state.videoAutoLoop); + _videoPlayerController.setVolume(videoState.videoAutoMute ? 0 : 1); + _videoPlayerController.setPlaybackSpeed(videoState.videoDefaultPlaybackSpeed.value); + _videoPlayerController.setLooping(videoState.videoAutoLoop); _videoPlayerController.addListener(() { if (_videoPlayerController.value.isPlaying && isVideoControlsVisible && timer?.isActive != true) { @@ -113,13 +113,13 @@ class _ThunderVideoPlayerState extends State { _videoPlayerController.initialize().then( (value) { setState(() { - isFullScreen = state.videoAutoFullscreen; + isFullScreen = videoState.videoAutoFullscreen; if (isFullScreen) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); } }); - if (autoPlayVideo(state)) { + if (autoPlayVideo(videoState)) { _videoPlayerController.play(); } }, diff --git a/lib/src/shared/utils/video_player/src/thunder_youtube_player.dart b/lib/src/shared/utils/video_player/src/thunder_youtube_player.dart index 3064efefd..ba8350345 100644 --- a/lib/src/shared/utils/video_player/src/thunder_youtube_player.dart +++ b/lib/src/shared/utils/video_player/src/thunder_youtube_player.dart @@ -7,6 +7,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:youtube_player_flutter/youtube_player_flutter.dart' as ypf; import 'package:youtube_player_iframe/youtube_player_iframe.dart'; +import 'package:thunder/src/app/cubits/network_checker_cubit/network_checker_cubit.dart'; +import 'package:thunder/src/app/cubits/video_preferences_cubit/video_preferences_cubit.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/core/enums/internet_connection_type.dart'; import 'package:thunder/src/core/enums/video_auto_play.dart'; @@ -34,7 +36,11 @@ class _ThunderYoutubePlayerState extends State with Single void initState() { super.initState(); - final state = context.read().state; + final videoPreferences = context.read().state; + final videoAutoLoop = videoPreferences.videoAutoLoop; + final videoAutoMute = videoPreferences.videoAutoMute; + final videoAutoFullscreen = videoPreferences.videoAutoFullscreen; + final videoDefaultPlaybackSpeed = videoPreferences.videoDefaultPlaybackSpeed.value; if (Platform.isAndroid || Platform.isIOS) { _ypfController = ypf.YoutubePlayerController( @@ -44,26 +50,26 @@ class _ThunderYoutubePlayerState extends State with Single autoPlay: autoPlayVideo(), enableCaption: false, hideControls: false, - loop: state.videoAutoLoop, - mute: state.videoAutoMute, + loop: videoAutoLoop, + mute: videoAutoMute, ), - )..setPlaybackRate(state.videoDefaultPlaybackSpeed.value); - if (state.videoAutoFullscreen) _ypfController.toggleFullScreenMode(); + )..setPlaybackRate(videoDefaultPlaybackSpeed); + if (videoAutoFullscreen) _ypfController.toggleFullScreenMode(); } else { _controller = YoutubePlayerController( params: YoutubePlayerParams( showControls: true, - mute: state.videoAutoMute, + mute: videoAutoMute, showFullscreenButton: true, - loop: state.videoAutoLoop, + loop: videoAutoLoop, ), ); _controller ..loadVideoById(videoId: ypf.YoutubePlayer.convertUrlToId(widget.videoUrl)!) - ..setPlaybackRate(state.videoDefaultPlaybackSpeed.value); + ..setPlaybackRate(videoDefaultPlaybackSpeed); } - setState(() => muted = state.videoAutoMute); + setState(() => muted = videoAutoMute); } @override @@ -77,12 +83,12 @@ class _ThunderYoutubePlayerState extends State with Single } bool autoPlayVideo() { - final state = context.read().state; - final networkCubit = context.read().state; + final videoAutoPlay = context.read().state.videoAutoPlay; + final internetConnectionType = context.read().state.internetConnectionType; - if (state.videoAutoPlay == VideoAutoPlay.always) { + if (videoAutoPlay == VideoAutoPlay.always) { return true; - } else if (state.videoAutoPlay == VideoAutoPlay.onWifi && networkCubit.internetConnectionType == InternetConnectionType.wifi) { + } else if (videoAutoPlay == VideoAutoPlay.onWifi && internetConnectionType == InternetConnectionType.wifi) { return true; } diff --git a/lib/src/shared/widgets/chips/community_chip.dart b/lib/src/shared/widgets/chips/community_chip.dart index 918cc68f1..b27521b21 100644 --- a/lib/src/shared/widgets/chips/community_chip.dart +++ b/lib/src/shared/widgets/chips/community_chip.dart @@ -5,7 +5,9 @@ import 'package:thunder/src/core/enums/full_name.dart'; import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/shared/widgets/avatars/community_avatar.dart'; import 'package:thunder/src/shared/full_name_widgets.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; +import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/shared/utils/instance.dart'; import 'package:thunder/src/app/utils/navigation.dart'; @@ -43,8 +45,9 @@ class CommunityChip extends StatelessWidget { @override Widget build(BuildContext context) { - final state = context.read().state; - final showCommunityAvatar = state.postBodyShowCommunityAvatar; + final showCommunityAvatar = context.select((cubit) => cubit.state.postBodyShowCommunityAvatar); + final postBodyShowCommunityInstance = context.select((cubit) => cubit.state.postBodyShowCommunityInstance); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); return InkWell( borderRadius: BorderRadius.circular(5), @@ -69,8 +72,8 @@ class CommunityChip extends StatelessWidget { communityName, communityTitle, fetchInstanceNameFromUrl(communityUrl), - includeInstance: includeInstance ?? state.postBodyShowCommunityInstance, - fontScale: state.metadataFontSizeScale, + includeInstance: includeInstance ?? postBodyShowCommunityInstance, + fontScale: metadataFontSizeScale, transformColor: (color) => color?.withValues(alpha: 0.75), ), ], diff --git a/lib/src/shared/widgets/chips/user_chip.dart b/lib/src/shared/widgets/chips/user_chip.dart index 2f493f012..7338a382f 100644 --- a/lib/src/shared/widgets/chips/user_chip.dart +++ b/lib/src/shared/widgets/chips/user_chip.dart @@ -8,7 +8,8 @@ import 'package:thunder/src/core/enums/user_type.dart'; import 'package:thunder/src/features/feed/feed.dart'; import 'package:thunder/src/shared/widgets/avatars/user_avatar.dart'; import 'package:thunder/src/shared/full_name_widgets.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/comment_preferences_cubit/comment_preferences_cubit.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/app/widgets/thunder_icons.dart'; import 'package:thunder/src/features/user/user.dart'; import 'package:thunder/src/shared/utils/instance.dart'; @@ -54,8 +55,8 @@ class UserChip extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final state = context.read().state; - final showUserAvatar = state.commentShowUserAvatar; + final showUserAvatar = context.select((cubit) => cubit.state.commentShowUserAvatar); + final metadataFontSizeScale = context.select((cubit) => cubit.state.metadataFontSizeScale); return IgnorePointer( ignoring: ignorePointerEvents, @@ -91,7 +92,7 @@ class UserChip extends StatelessWidget { user.displayName, fetchInstanceNameFromUrl(user.actorId), includeInstance: includeInstance, - fontScale: state.metadataFontSizeScale, + fontScale: metadataFontSizeScale, transformColor: (c) => userGroups.isNotEmpty ? theme.textTheme.bodyMedium?.color : c?.withValues(alpha: opacity), ), ), @@ -101,7 +102,7 @@ class UserChip extends StatelessWidget { padding: const EdgeInsets.only(left: 1), child: Icon( Thunder.microphone_variant, - size: 15.0 * state.metadataFontSizeScale.textScaleFactor, + size: 15.0 * metadataFontSizeScale.textScaleFactor, color: theme.colorScheme.onSurface, ), ), @@ -110,7 +111,7 @@ class UserChip extends StatelessWidget { padding: const EdgeInsets.only(left: 1), child: Icon( Icons.person, - size: 15.0 * state.metadataFontSizeScale.textScaleFactor, + size: 15.0 * metadataFontSizeScale.textScaleFactor, color: theme.colorScheme.onSurface, ), ), @@ -119,7 +120,7 @@ class UserChip extends StatelessWidget { padding: const EdgeInsets.only(left: 1), child: Icon( Thunder.shield_crown, - size: 14.0 * state.metadataFontSizeScale.textScaleFactor, + size: 14.0 * metadataFontSizeScale.textScaleFactor, color: theme.colorScheme.onSurface, ), ), @@ -128,7 +129,7 @@ class UserChip extends StatelessWidget { padding: const EdgeInsets.only(left: 1), child: Icon( Thunder.shield, - size: 14.0 * state.metadataFontSizeScale.textScaleFactor, + size: 14.0 * metadataFontSizeScale.textScaleFactor, color: theme.colorScheme.onSurface, ), ), @@ -137,7 +138,7 @@ class UserChip extends StatelessWidget { padding: const EdgeInsets.only(left: 1, right: 2), child: Icon( Thunder.robot, - size: 13.0 * state.metadataFontSizeScale.textScaleFactor, + size: 13.0 * metadataFontSizeScale.textScaleFactor, color: theme.colorScheme.onSurface, ), ), @@ -146,7 +147,7 @@ class UserChip extends StatelessWidget { padding: const EdgeInsets.only(left: 1, right: 2), child: Icon( Icons.cake_rounded, - size: 13.0 * state.metadataFontSizeScale.textScaleFactor, + size: 13.0 * metadataFontSizeScale.textScaleFactor, color: theme.colorScheme.onSurface, ), ), diff --git a/lib/src/shared/widgets/comment_navigator_fab.dart b/lib/src/shared/widgets/comment_navigator_fab.dart index eb69a4d05..3de4b1d11 100644 --- a/lib/src/shared/widgets/comment_navigator_fab.dart +++ b/lib/src/shared/widgets/comment_navigator_fab.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; -import 'package:thunder/l10n/generated/app_localizations.dart'; +import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/features/comment/comment.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; class CommentNavigatorFab extends StatefulWidget { /// The [ScrollController] for the scrollable list @@ -62,8 +62,8 @@ class _CommentNavigatorFabState extends State { @override Widget build(BuildContext context) { - final bool darkTheme = context.read().state.useDarkTheme; - final ThemeData theme = Theme.of(context); + final theme = Theme.of(context); + final darkTheme = context.select((cubit) => cubit.state.useDarkTheme); return SizedBox( width: 135, diff --git a/lib/src/shared/widgets/media/compact_thumbnail_preview.dart b/lib/src/shared/widgets/media/compact_thumbnail_preview.dart index 5ca75893e..eb6e49776 100644 --- a/lib/src/shared/widgets/media/compact_thumbnail_preview.dart +++ b/lib/src/shared/widgets/media/compact_thumbnail_preview.dart @@ -7,7 +7,7 @@ import 'package:thunder/src/shared/widgets/media/media_type_badge.dart'; import 'package:thunder/src/core/enums/view_mode.dart'; import 'package:thunder/src/core/models/media.dart'; import 'package:thunder/src/shared/widgets/media/media_view.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/feed_preferences_cubit/feed_preferences_cubit.dart'; /// Displays a compact thumbnail preview for a post card. class CompactThumbnailPreview extends StatelessWidget { @@ -34,9 +34,8 @@ class CompactThumbnailPreview extends StatelessWidget { @override Widget build(BuildContext context) { - final state = context.select((ThunderBloc bloc) => (bloc.state.hideNsfwPreviews, bloc.state.markPostReadOnMediaView)); - final hideNsfwPreviews = state.$1; - final markPostReadOnMediaView = state.$2; + final hideNsfwPreviews = context.select((cubit) => cubit.state.hideNsfwPreviews); + final markPostReadOnMediaView = context.select((cubit) => cubit.state.markPostReadOnMediaView); final isUserLoggedIn = context.select((ProfileBloc bloc) => bloc.state.isLoggedIn); diff --git a/lib/src/shared/widgets/media/media_type_badge.dart b/lib/src/shared/widgets/media/media_type_badge.dart index c7c5817c6..3254cfbb9 100644 --- a/lib/src/shared/widgets/media/media_type_badge.dart +++ b/lib/src/shared/widgets/media/media_type_badge.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/src/core/enums/media_type.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; /// Base representation of a media type badge. Holds the icon and color. class MediaTypeBadgeItem { @@ -90,7 +90,7 @@ class MediaTypeBadge extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final darkTheme = context.select((ThemeBloc bloc) => bloc.state.useDarkTheme); + final darkTheme = context.select((cubit) => cubit.state.useDarkTheme); final mediaTypeItem = mediaTypeBadgeItems[mediaType]!; diff --git a/lib/src/shared/widgets/media/media_view.dart b/lib/src/shared/widgets/media/media_view.dart index 6ec527c37..b2803c0ee 100644 --- a/lib/src/shared/widgets/media/media_view.dart +++ b/lib/src/shared/widgets/media/media_view.dart @@ -3,7 +3,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/core/models/media.dart'; import 'package:thunder/src/shared/images/image_preview.dart'; @@ -16,6 +15,7 @@ import 'package:thunder/src/core/enums/view_mode.dart'; import 'package:thunder/src/core/enums/media_type.dart'; import 'package:thunder/src/features/post/post.dart'; import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/gesture_preferences_cubit/gesture_preferences_cubit.dart'; import 'package:thunder/src/shared/utils/links.dart'; import 'package:thunder/src/shared/utils/media/image.dart'; import 'package:thunder/src/shared/utils/media/video.dart'; @@ -198,7 +198,7 @@ class _MediaViewState extends State with TickerProviderStateMixin { // At this point, all other media types should contain images, so we display the image as well as any additional information final theme = Theme.of(context); - final imagePeekDurationMs = context.select((ThunderBloc bloc) => bloc.state.imagePeekDuration); + final imagePeekDurationMs = context.select((cubit) => cubit.state.imagePeekDuration); final tabletMode = widget.viewMode == ViewMode.comfortable ? context.select((ThunderBloc bloc) => bloc.state.tabletMode) : false; final blurNSFWPreviews = widget.hideNsfwPreviews && widget.media.nsfw; late final l10n = GlobalContext.l10n; diff --git a/lib/src/shared/widgets/text/selectable_text_modal.dart b/lib/src/shared/widgets/text/selectable_text_modal.dart index e3b111e7d..5757ca686 100644 --- a/lib/src/shared/widgets/text/selectable_text_modal.dart +++ b/lib/src/shared/widgets/text/selectable_text_modal.dart @@ -1,26 +1,30 @@ import 'dart:io'; -import 'package:fading_edge_scrollview/fading_edge_scrollview.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:fading_edge_scrollview/fading_edge_scrollview.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:thunder/src/app/utils/global_context.dart'; import 'package:thunder/src/core/enums/font_scale.dart'; import 'package:thunder/src/shared/widgets/chips/thunder_action_chip.dart'; import 'package:thunder/src/shared/markdown/common_markdown_body.dart'; -import 'package:thunder/l10n/generated/app_localizations.dart'; import 'package:thunder/src/shared/widgets/text/scalable_text.dart'; -import 'package:thunder/src/app/bloc/thunder_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; void showSelectableTextModal(BuildContext context, {String? title, required String text}) { - final AppLocalizations l10n = AppLocalizations.of(context)!; - final ThemeData theme = Theme.of(context); - final ThunderState thunderState = context.read().state; + final l10n = GlobalContext.l10n; + final theme = Theme.of(context); + + final themePreferences = context.read().state; + final contentFontSizeScale = themePreferences.contentFontSizeScale; + final titleFontSizeScale = themePreferences.titleFontSizeScale; - final ScrollController textScrollController = ScrollController(); - final ScrollController actionsScrollController = ScrollController(); - final FocusNode focusNode = FocusNode(); - final GlobalKey selectableRegionKey = GlobalKey(); + final textScrollController = ScrollController(); + final actionsScrollController = ScrollController(); + final focusNode = FocusNode(); + final selectableRegionKey = GlobalKey(); bool isAnythingSelected = false; @@ -117,7 +121,7 @@ void showSelectableTextModal(BuildContext context, {String? title, required Stri title!, style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, - fontSize: MediaQuery.textScalerOf(context).scale(theme.textTheme.bodyMedium!.fontSize! * thunderState.titleFontSizeScale.textScaleFactor), + fontSize: MediaQuery.textScalerOf(context).scale(theme.textTheme.bodyMedium!.fontSize! * titleFontSizeScale.textScaleFactor), ), ), ), @@ -129,7 +133,7 @@ void showSelectableTextModal(BuildContext context, {String? title, required Stri ? ScalableText( text, style: theme.textTheme.bodySmall?.copyWith(fontFamily: 'monospace'), - fontScale: thunderState.contentFontSizeScale, + fontScale: contentFontSizeScale, ) : CommonMarkdownBody( body: text, diff --git a/test/utils/user_groups_test.dart b/test/utils/user_groups_test.dart index 0ce7fd744..f13950915 100644 --- a/test/utils/user_groups_test.dart +++ b/test/utils/user_groups_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:thunder/src/core/enums/user_type.dart'; -import 'package:thunder/src/app/theme/bloc/theme_bloc.dart'; +import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; import '../widgets/base_widget.dart'; @@ -39,7 +39,7 @@ void main() { testWidgets('fetchUsernameColor returns no color if user is in no groups', (tester) async { await tester.pumpWidget(BaseWidget( child: BlocProvider( - create: (context) => ThemeBloc(), + create: (context) => ThemePreferencesCubit(), child: Builder(builder: (context) { Color? color = fetchUserGroupColor(context, []); @@ -53,7 +53,7 @@ void main() { testWidgets('fetchUsernameColor returns correct color if user is in a single group', (tester) async { await tester.pumpWidget(BaseWidget( child: BlocProvider( - create: (context) => ThemeBloc(), + create: (context) => ThemePreferencesCubit(), child: Builder(builder: (context) { final theme = Theme.of(context); @@ -72,7 +72,7 @@ void main() { testWidgets('fetchUsernameColor returns correct color if user is in multiple groups', (tester) async { await tester.pumpWidget(BaseWidget( child: BlocProvider( - create: (context) => ThemeBloc(), + create: (context) => ThemePreferencesCubit(), child: Builder(builder: (context) { final theme = Theme.of(context); From fd2e161ccc70c1785cdbf77a37eb4ae51a94d34a Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Wed, 31 Dec 2025 17:39:30 -0800 Subject: [PATCH 2/2] fix: fix tests --- test/utils/user_groups_test.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/utils/user_groups_test.dart b/test/utils/user_groups_test.dart index f13950915..6460b4b09 100644 --- a/test/utils/user_groups_test.dart +++ b/test/utils/user_groups_test.dart @@ -2,16 +2,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:thunder/src/core/enums/user_type.dart'; +import 'package:thunder/src/core/singletons/preferences.dart'; import 'package:thunder/src/app/cubits/theme_preferences_cubit/theme_preferences_cubit.dart'; import 'package:thunder/src/features/user/user.dart'; import '../widgets/base_widget.dart'; void main() { - setUpAll(() { + setUpAll(() async { TestWidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues({}); + await UserPreferences.instance.initialize(); }); group('Test user group logic', () {