diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 4b3faa36..15567bfd 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -1569,6 +1569,7 @@ "searchFediverseHint": "Search by address, e.g. {}", "searchFediverseEmpty": "Search for users on other ActivityPub instances", "searchFediverseNoResults": "No users found for this search", + "fediverseUsers": "Fediverse Users", "following": "Following", "followers": "Followers", "follow": "Follow", @@ -1579,5 +1580,6 @@ "followersEmpty": "No followers yet", "followingEmptyHint": "Start by searching for users or explore other instances", "fediversePost": "Fediverse Post", - "fediversePostDescribe": "Post from the Fediverse Network" + "fediversePostDescribe": "Post from the Fediverse Network", + "settingsShowFediverseContent": "Show Fediverse Content" } \ No newline at end of file diff --git a/lib/pods/chat/call.g.dart b/lib/pods/chat/call.g.dart index 9bc41c17..6fb2c61b 100644 --- a/lib/pods/chat/call.g.dart +++ b/lib/pods/chat/call.g.dart @@ -41,7 +41,7 @@ final class CallNotifierProvider } } -String _$callNotifierHash() => r'40bd884d3918b8e817329589c921774ab3c62ea2'; +String _$callNotifierHash() => r'caa03913d98c6d98448af44059db5ef72b5d58f6'; abstract class _$CallNotifier extends $Notifier { CallState build(); diff --git a/lib/pods/chat/chat_online_count.g.dart b/lib/pods/chat/chat_online_count.g.dart index effa8a41..b0e0e891 100644 --- a/lib/pods/chat/chat_online_count.g.dart +++ b/lib/pods/chat/chat_online_count.g.dart @@ -52,7 +52,7 @@ final class ChatOnlineCountNotifierProvider } String _$chatOnlineCountNotifierHash() => - r'19af8fd0e9f62c65e12a68215406776085235fa3'; + r'b2f9f17bfece1937ec90590b8f11db2bec923156'; final class ChatOnlineCountNotifierFamily extends $Family with diff --git a/lib/pods/chat/chat_room.g.dart b/lib/pods/chat/chat_room.g.dart index 25db94bc..04ad9b47 100644 --- a/lib/pods/chat/chat_room.g.dart +++ b/lib/pods/chat/chat_room.g.dart @@ -34,7 +34,7 @@ final class ChatRoomJoinedNotifierProvider } String _$chatRoomJoinedNotifierHash() => - r'e69955be56ef2c04a8062a8a65925e0a23bfcbaa'; + r'b3726e10298b99a8529c5e28a5c402b95016f096'; abstract class _$ChatRoomJoinedNotifier extends $AsyncNotifier> { @@ -98,7 +98,7 @@ final class ChatRoomNotifierProvider } } -String _$chatRoomNotifierHash() => r'1e6391e2ab4eeb114fa001aaa6b06ab2bd646f38'; +String _$chatRoomNotifierHash() => r'9f7a8bdd4af381c6b60e65e74363a0af3c1a650e'; final class ChatRoomNotifierFamily extends $Family with @@ -190,7 +190,7 @@ final class ChatRoomIdentityNotifierProvider } String _$chatRoomIdentityNotifierHash() => - r'27c17d55366d39be81d7209837e5c01f80a68a24'; + r'1ce75462a19cc037c97ee6084a30fee1f5335875'; final class ChatRoomIdentityNotifierFamily extends $Family with @@ -279,4 +279,4 @@ final class ChatroomInvitesProvider } } -String _$chatroomInvitesHash() => r'5cd6391b09c5517ede19bacce43b45c8d71dd087'; +String _$chatroomInvitesHash() => r'fc23231d5f111b1c3796ffae2b471384b951861a'; diff --git a/lib/pods/chat/chat_subscribe.g.dart b/lib/pods/chat/chat_subscribe.g.dart index da6ad242..02743507 100644 --- a/lib/pods/chat/chat_subscribe.g.dart +++ b/lib/pods/chat/chat_subscribe.g.dart @@ -59,7 +59,7 @@ final class ChatSubscribeNotifierProvider } String _$chatSubscribeNotifierHash() => - r'1aa164429aaab1628b5edbae11e33b0860abdcdc'; + r'a05739450e6d23eb3d8c0a96078887b2b58ffd10'; final class ChatSubscribeNotifierFamily extends $Family with diff --git a/lib/pods/chat/chat_summary.g.dart b/lib/pods/chat/chat_summary.g.dart index 4e44284f..802597ab 100644 --- a/lib/pods/chat/chat_summary.g.dart +++ b/lib/pods/chat/chat_summary.g.dart @@ -34,7 +34,7 @@ final class ChatUnreadCountNotifierProvider } String _$chatUnreadCountNotifierHash() => - r'b8d93589dc37f772d4c3a07d9afd81c37026e57d'; + r'169b28f8759ebd9de75f7de17f60d493737ee7a8'; abstract class _$ChatUnreadCountNotifier extends $AsyncNotifier { FutureOr build(); @@ -79,7 +79,7 @@ final class ChatSummaryProvider ChatSummary create() => ChatSummary(); } -String _$chatSummaryHash() => r'dfa5e487586482ebdafef8d711f74db68ee86f84'; +String _$chatSummaryHash() => r'82f516d4ce8b67dadb815523df57a3c30a33ef91'; abstract class _$ChatSummary extends $AsyncNotifier> { diff --git a/lib/pods/chat/messages_notifier.g.dart b/lib/pods/chat/messages_notifier.g.dart index a2abf7fa..9e681dbd 100644 --- a/lib/pods/chat/messages_notifier.g.dart +++ b/lib/pods/chat/messages_notifier.g.dart @@ -50,7 +50,7 @@ final class MessagesNotifierProvider } } -String _$messagesNotifierHash() => r'c7e2cd7f5b8673af88f5076814393dbfbd0d43c5'; +String _$messagesNotifierHash() => r'a721a4b92b48ee7c2289cdcd7130bbf1ca9dcb40'; final class MessagesNotifierFamily extends $Family with diff --git a/lib/pods/config.dart b/lib/pods/config.dart index d55c3e20..dc256e46 100644 --- a/lib/pods/config.dart +++ b/lib/pods/config.dart @@ -40,6 +40,7 @@ const kAppFirstLaunchAt = 'app_first_launch_at'; const kAppAskedReview = 'app_asked_review'; const kAppDashSearchEngine = 'app_dash_search_engine'; const kAppDefaultScreen = 'app_default_screen'; +const kAppShowFediverseContent = 'app_show_fediverse_content'; // Will be overrided by the ProviderScope final sharedPreferencesProvider = Provider((ref) { @@ -91,6 +92,7 @@ sealed class AppSettings with _$AppSettings { required bool askedReview, required String? dashSearchEngine, required String? defaultScreen, + required bool showFediverseContent, }) = _AppSettings; } @@ -122,6 +124,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { firstLaunchAt: prefs.getString(kAppFirstLaunchAt), dashSearchEngine: prefs.getString(kAppDashSearchEngine), defaultScreen: prefs.getString(kAppDefaultScreen), + showFediverseContent: prefs.getBool(kAppShowFediverseContent) ?? true, ); } @@ -311,6 +314,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { } state = state.copyWith(dashSearchEngine: value); } + + void setShowFediverseContent(bool value) { + final prefs = ref.read(sharedPreferencesProvider); + prefs.setBool(kAppShowFediverseContent, value); + state = state.copyWith(showFediverseContent: value); + } } final updateInfoProvider = diff --git a/lib/pods/config.freezed.dart b/lib/pods/config.freezed.dart index 5257f381..f295cb50 100644 --- a/lib/pods/config.freezed.dart +++ b/lib/pods/config.freezed.dart @@ -290,7 +290,7 @@ mixin _$AppSettings { ThemeColors? get customColors; Size? get windowSize;// The window size for desktop platforms double get windowOpacity;// The window opacity for desktop platforms double get cardTransparency;// The card background opacity - String? get defaultPoolId; String get messageDisplayStyle; String? get themeMode; bool get disableAnimation; bool get groupedChatList; String? get firstLaunchAt; bool get askedReview; String? get dashSearchEngine; String? get defaultScreen; + String? get defaultPoolId; String get messageDisplayStyle; String? get themeMode; bool get disableAnimation; bool get groupedChatList; String? get firstLaunchAt; bool get askedReview; String? get dashSearchEngine; String? get defaultScreen; bool get showFediverseContent; /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -301,16 +301,16 @@ $AppSettingsCopyWith get copyWith => _$AppSettingsCopyWithImpl Object.hashAll([runtimeType,dataSavingMode,soundEffects,festivalFeatures,enterToSend,appBarTransparent,showBackgroundImage,notifyWithHaptic,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,disableAnimation,groupedChatList,firstLaunchAt,askedReview,dashSearchEngine,defaultScreen]); +int get hashCode => Object.hashAll([runtimeType,dataSavingMode,soundEffects,festivalFeatures,enterToSend,appBarTransparent,showBackgroundImage,notifyWithHaptic,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,disableAnimation,groupedChatList,firstLaunchAt,askedReview,dashSearchEngine,defaultScreen,showFediverseContent]); @override String toString() { - return 'AppSettings(dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, festivalFeatures: $festivalFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, notifyWithHaptic: $notifyWithHaptic, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, disableAnimation: $disableAnimation, groupedChatList: $groupedChatList, firstLaunchAt: $firstLaunchAt, askedReview: $askedReview, dashSearchEngine: $dashSearchEngine, defaultScreen: $defaultScreen)'; + return 'AppSettings(dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, festivalFeatures: $festivalFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, notifyWithHaptic: $notifyWithHaptic, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, disableAnimation: $disableAnimation, groupedChatList: $groupedChatList, firstLaunchAt: $firstLaunchAt, askedReview: $askedReview, dashSearchEngine: $dashSearchEngine, defaultScreen: $defaultScreen, showFediverseContent: $showFediverseContent)'; } @@ -321,7 +321,7 @@ abstract mixin class $AppSettingsCopyWith<$Res> { factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl; @useResult $Res call({ - bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen + bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen, bool showFediverseContent }); @@ -338,7 +338,7 @@ class _$AppSettingsCopyWithImpl<$Res> /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? dataSavingMode = null,Object? soundEffects = null,Object? festivalFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? notifyWithHaptic = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? disableAnimation = null,Object? groupedChatList = null,Object? firstLaunchAt = freezed,Object? askedReview = null,Object? dashSearchEngine = freezed,Object? defaultScreen = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? dataSavingMode = null,Object? soundEffects = null,Object? festivalFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? notifyWithHaptic = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? disableAnimation = null,Object? groupedChatList = null,Object? firstLaunchAt = freezed,Object? askedReview = null,Object? dashSearchEngine = freezed,Object? defaultScreen = freezed,Object? showFediverseContent = null,}) { return _then(_self.copyWith( dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable @@ -362,7 +362,8 @@ as bool,firstLaunchAt: freezed == firstLaunchAt ? _self.firstLaunchAt : firstLau as String?,askedReview: null == askedReview ? _self.askedReview : askedReview // ignore: cast_nullable_to_non_nullable as bool,dashSearchEngine: freezed == dashSearchEngine ? _self.dashSearchEngine : dashSearchEngine // ignore: cast_nullable_to_non_nullable as String?,defaultScreen: freezed == defaultScreen ? _self.defaultScreen : defaultScreen // ignore: cast_nullable_to_non_nullable -as String?, +as String?,showFediverseContent: null == showFediverseContent ? _self.showFediverseContent : showFediverseContent // ignore: cast_nullable_to_non_nullable +as bool, )); } /// Create a copy of AppSettings @@ -456,10 +457,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen, bool showFediverseContent)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _AppSettings() when $default != null: -return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen);case _: +return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen,_that.showFediverseContent);case _: return orElse(); } @@ -477,10 +478,10 @@ return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_ /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen, bool showFediverseContent) $default,) {final _that = this; switch (_that) { case _AppSettings(): -return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen);} +return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen,_that.showFediverseContent);} } /// A variant of `when` that fallback to returning `null` /// @@ -494,10 +495,10 @@ return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_ /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen, bool showFediverseContent)? $default,) {final _that = this; switch (_that) { case _AppSettings() when $default != null: -return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen);case _: +return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen,_that.showFediverseContent);case _: return null; } @@ -509,7 +510,7 @@ return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_ class _AppSettings implements AppSettings { - const _AppSettings({required this.dataSavingMode, required this.soundEffects, required this.festivalFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.notifyWithHaptic, required this.customFonts, required this.appColorScheme, required this.customColors, required this.windowSize, required this.windowOpacity, required this.cardTransparency, required this.defaultPoolId, required this.messageDisplayStyle, required this.themeMode, required this.disableAnimation, required this.groupedChatList, required this.firstLaunchAt, required this.askedReview, required this.dashSearchEngine, required this.defaultScreen}); + const _AppSettings({required this.dataSavingMode, required this.soundEffects, required this.festivalFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.notifyWithHaptic, required this.customFonts, required this.appColorScheme, required this.customColors, required this.windowSize, required this.windowOpacity, required this.cardTransparency, required this.defaultPoolId, required this.messageDisplayStyle, required this.themeMode, required this.disableAnimation, required this.groupedChatList, required this.firstLaunchAt, required this.askedReview, required this.dashSearchEngine, required this.defaultScreen, required this.showFediverseContent}); @override final bool dataSavingMode; @@ -538,6 +539,7 @@ class _AppSettings implements AppSettings { @override final bool askedReview; @override final String? dashSearchEngine; @override final String? defaultScreen; +@override final bool showFediverseContent; /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @@ -549,16 +551,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_ @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.festivalFeatures, festivalFeatures) || other.festivalFeatures == festivalFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.notifyWithHaptic, notifyWithHaptic) || other.notifyWithHaptic == notifyWithHaptic)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.customColors, customColors) || other.customColors == customColors)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.cardTransparency, cardTransparency) || other.cardTransparency == cardTransparency)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)&&(identical(other.themeMode, themeMode) || other.themeMode == themeMode)&&(identical(other.disableAnimation, disableAnimation) || other.disableAnimation == disableAnimation)&&(identical(other.groupedChatList, groupedChatList) || other.groupedChatList == groupedChatList)&&(identical(other.firstLaunchAt, firstLaunchAt) || other.firstLaunchAt == firstLaunchAt)&&(identical(other.askedReview, askedReview) || other.askedReview == askedReview)&&(identical(other.dashSearchEngine, dashSearchEngine) || other.dashSearchEngine == dashSearchEngine)&&(identical(other.defaultScreen, defaultScreen) || other.defaultScreen == defaultScreen)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.festivalFeatures, festivalFeatures) || other.festivalFeatures == festivalFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.notifyWithHaptic, notifyWithHaptic) || other.notifyWithHaptic == notifyWithHaptic)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.customColors, customColors) || other.customColors == customColors)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.cardTransparency, cardTransparency) || other.cardTransparency == cardTransparency)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)&&(identical(other.themeMode, themeMode) || other.themeMode == themeMode)&&(identical(other.disableAnimation, disableAnimation) || other.disableAnimation == disableAnimation)&&(identical(other.groupedChatList, groupedChatList) || other.groupedChatList == groupedChatList)&&(identical(other.firstLaunchAt, firstLaunchAt) || other.firstLaunchAt == firstLaunchAt)&&(identical(other.askedReview, askedReview) || other.askedReview == askedReview)&&(identical(other.dashSearchEngine, dashSearchEngine) || other.dashSearchEngine == dashSearchEngine)&&(identical(other.defaultScreen, defaultScreen) || other.defaultScreen == defaultScreen)&&(identical(other.showFediverseContent, showFediverseContent) || other.showFediverseContent == showFediverseContent)); } @override -int get hashCode => Object.hashAll([runtimeType,dataSavingMode,soundEffects,festivalFeatures,enterToSend,appBarTransparent,showBackgroundImage,notifyWithHaptic,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,disableAnimation,groupedChatList,firstLaunchAt,askedReview,dashSearchEngine,defaultScreen]); +int get hashCode => Object.hashAll([runtimeType,dataSavingMode,soundEffects,festivalFeatures,enterToSend,appBarTransparent,showBackgroundImage,notifyWithHaptic,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,disableAnimation,groupedChatList,firstLaunchAt,askedReview,dashSearchEngine,defaultScreen,showFediverseContent]); @override String toString() { - return 'AppSettings(dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, festivalFeatures: $festivalFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, notifyWithHaptic: $notifyWithHaptic, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, disableAnimation: $disableAnimation, groupedChatList: $groupedChatList, firstLaunchAt: $firstLaunchAt, askedReview: $askedReview, dashSearchEngine: $dashSearchEngine, defaultScreen: $defaultScreen)'; + return 'AppSettings(dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, festivalFeatures: $festivalFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, notifyWithHaptic: $notifyWithHaptic, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, disableAnimation: $disableAnimation, groupedChatList: $groupedChatList, firstLaunchAt: $firstLaunchAt, askedReview: $askedReview, dashSearchEngine: $dashSearchEngine, defaultScreen: $defaultScreen, showFediverseContent: $showFediverseContent)'; } @@ -569,7 +571,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl; @override @useResult $Res call({ - bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen + bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen, bool showFediverseContent }); @@ -586,7 +588,7 @@ class __$AppSettingsCopyWithImpl<$Res> /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? dataSavingMode = null,Object? soundEffects = null,Object? festivalFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? notifyWithHaptic = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? disableAnimation = null,Object? groupedChatList = null,Object? firstLaunchAt = freezed,Object? askedReview = null,Object? dashSearchEngine = freezed,Object? defaultScreen = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? dataSavingMode = null,Object? soundEffects = null,Object? festivalFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? notifyWithHaptic = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? disableAnimation = null,Object? groupedChatList = null,Object? firstLaunchAt = freezed,Object? askedReview = null,Object? dashSearchEngine = freezed,Object? defaultScreen = freezed,Object? showFediverseContent = null,}) { return _then(_AppSettings( dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable @@ -610,7 +612,8 @@ as bool,firstLaunchAt: freezed == firstLaunchAt ? _self.firstLaunchAt : firstLau as String?,askedReview: null == askedReview ? _self.askedReview : askedReview // ignore: cast_nullable_to_non_nullable as bool,dashSearchEngine: freezed == dashSearchEngine ? _self.dashSearchEngine : dashSearchEngine // ignore: cast_nullable_to_non_nullable as String?,defaultScreen: freezed == defaultScreen ? _self.defaultScreen : defaultScreen // ignore: cast_nullable_to_non_nullable -as String?, +as String?,showFediverseContent: null == showFediverseContent ? _self.showFediverseContent : showFediverseContent // ignore: cast_nullable_to_non_nullable +as bool, )); } diff --git a/lib/pods/config.g.dart b/lib/pods/config.g.dart index 2e176bfd..a0e1471d 100644 --- a/lib/pods/config.g.dart +++ b/lib/pods/config.g.dart @@ -65,7 +65,7 @@ final class AppSettingsNotifierProvider } String _$appSettingsNotifierHash() => - r'6592261baf8182fe78d3e58e2fd9bb53d3287736'; + r'2437c621dcb1625a120ed1f21ab5c29906ba98be'; abstract class _$AppSettingsNotifier extends $Notifier { AppSettings build(); diff --git a/lib/pods/timeline.dart b/lib/pods/timeline.dart index d5ff358f..3eaa6b90 100644 --- a/lib/pods/timeline.dart +++ b/lib/pods/timeline.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/activity.dart'; +import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/paging.dart'; @@ -35,12 +36,13 @@ class ActivityListNotifier @override Future> fetch() async { final client = ref.read(apiClientProvider); + final settings = ref.read(appSettingsProvider); final queryParameters = { if (cursor != null) 'cursor': cursor, 'take': pageSize, if (currentFilter != null) 'filter': currentFilter, - 'showFediverse': true, + 'showFediverse': settings.showFediverseContent, }; final response = await client.get( diff --git a/lib/route.dart b/lib/route.dart index b1f48978..5ce60106 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -21,6 +21,7 @@ import 'package:island/screens/files/file_detail.dart'; import 'package:island/screens/posts/post_categories_list.dart'; import 'package:island/screens/posts/post_category_detail.dart'; import 'package:island/screens/posts/post_search.dart'; +import 'package:island/screens/search.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_wrapper.dart'; import 'package:island/screens/tabs.dart'; @@ -187,6 +188,12 @@ final routerProvider = Provider((ref) { }, ), + GoRoute( + name: 'universalSearch', + path: '/search', + builder: (context, state) => const UniversalSearchScreen(), + ), + GoRoute( name: 'activitypubSearch', path: '/activitypub/search', diff --git a/lib/screens/account/profile.g.dart b/lib/screens/account/profile.g.dart index 4eb315e0..1ff13b66 100644 --- a/lib/screens/account/profile.g.dart +++ b/lib/screens/account/profile.g.dart @@ -287,7 +287,7 @@ final class AccountDirectChatProvider } } -String _$accountDirectChatHash() => r'149ea3a3730672cfbbb8c16fe1f2caa0bb9f0e17'; +String _$accountDirectChatHash() => r'71bc9eed34a436a3743e8ef87f7aaae861fc5746'; final class AccountDirectChatFamily extends $Family with $FunctionalFamilyOverride, String> { diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 6d5c4514..0cc488db 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -141,13 +141,13 @@ class ExploreScreen extends HookConsumerWidget { PopupMenuItem( child: Row( children: [ - const Icon(Symbols.linked_services), + const Icon(Symbols.search), const Gap(12), - Text('searchFediverse').tr(), + Text('search').tr(), ], ), onTap: () { - context.pushNamed('activitypubSearch'); + context.pushNamed('universalSearch'); }, ), PopupMenuItem( @@ -174,18 +174,6 @@ class ExploreScreen extends HookConsumerWidget { context.pushNamed('postShuffle'); }, ), - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.search), - const Gap(12), - Text('search').tr(), - ], - ), - onTap: () { - context.pushNamed('postSearch'); - }, - ), ], icon: Icon(Symbols.action_key), tooltip: 'search'.tr(), @@ -554,6 +542,18 @@ class ExploreScreen extends HookConsumerWidget { ), PopupMenuButton( itemBuilder: (context) => [ + PopupMenuItem( + child: Row( + children: [ + const Icon(Symbols.search), + const Gap(12), + Text('search').tr(), + ], + ), + onTap: () { + context.pushNamed('universalSearch'); + }, + ), PopupMenuItem( child: Row( children: [ @@ -578,18 +578,6 @@ class ExploreScreen extends HookConsumerWidget { context.pushNamed('postShuffle'); }, ), - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.search), - const Gap(12), - Text('search').tr(), - ], - ), - onTap: () { - context.pushNamed('postSearch'); - }, - ), ], icon: Icon(Symbols.action_key, color: foregroundColor), tooltip: 'search'.tr(), @@ -610,27 +598,14 @@ class ExploreScreen extends HookConsumerWidget { final notifier = ref.watch(activityListProvider.notifier); - final activityState = ref.watch(activityListProvider); - return Expanded( - child: Stack( - children: [ - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: ExtendedRefreshIndicator( - onRefresh: notifier.refresh, - child: CustomScrollView(slivers: [SliverGap(8), bodyView]), - ), - ).padding(horizontal: 8), - if (activityState.isLoading) - Positioned.fill( - child: Container( - color: Colors.grey.withOpacity(0.3), - child: const Center(child: CircularProgressIndicator()), - ), - ), - ], - ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: ExtendedRefreshIndicator( + onRefresh: notifier.refresh, + child: CustomScrollView(slivers: [SliverGap(8), bodyView]), + ), + ).padding(horizontal: 8), ); } } diff --git a/lib/screens/search.dart b/lib/screens/search.dart new file mode 100644 index 00000000..c4d98b0f --- /dev/null +++ b/lib/screens/search.dart @@ -0,0 +1,484 @@ +import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:gap/gap.dart'; +import 'package:island/models/activitypub.dart'; +import 'package:island/pods/post/post_list.dart'; +import 'package:island/services/activitypub_service.dart'; +import 'package:island/services/responsive.dart'; +import 'package:island/widgets/activitypub/actor_list_item.dart'; +import 'package:island/widgets/alert.dart'; +import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/widgets/post/post_item.dart'; +import 'package:island/widgets/post/post_item_skeleton.dart'; +import 'package:island/widgets/posts/post_filter.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:styled_widget/styled_widget.dart'; + +const kSearchPostListId = 'search'; + +enum SearchTab { posts, fediverse } + +class UniversalSearchScreen extends HookConsumerWidget { + final SearchTab initialTab; + + const UniversalSearchScreen({super.key, this.initialTab = SearchTab.posts}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final tabController = useTabController( + initialLength: 2, + initialIndex: initialTab.index, + ); + + return AppScaffold( + isNoBackground: false, + appBar: AppBar(title: Text('search'.tr()), elevation: 0), + body: Column( + children: [ + TabBar( + controller: tabController, + tabs: [ + Tab(text: 'posts'.tr()), + Tab(text: 'fediverseUsers'.tr()), + ], + ), + Expanded( + child: TabBarView( + controller: tabController, + children: [_PostsSearchTab(), _FediverseSearchTab()], + ), + ), + ], + ), + ); + } +} + +class _PostsSearchTab extends HookConsumerWidget { + const _PostsSearchTab(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final searchController = useTextEditingController(); + final debounce = useMemoized(() => Duration(milliseconds: 500)); + final debounceTimer = useRef(null); + final showFilters = useState(false); + final pubNameController = useTextEditingController(); + final realmController = useTextEditingController(); + + final categoryTabController = useTabController(initialLength: 3); + final queryState = useState(const PostListQuery()); + + final noti = ref.read( + postListProvider(PostListQueryConfig(id: kSearchPostListId)).notifier, + ); + + useEffect(() { + return () { + searchController.dispose(); + pubNameController.dispose(); + realmController.dispose(); + debounceTimer.value?.cancel(); + }; + }, []); + + void onSearchChanged(String query, {bool skipDebounce = false}) { + queryState.value = queryState.value.copyWith(queryTerm: query); + + if (skipDebounce) { + noti.applyFilter(queryState.value); + return; + } + + if (debounceTimer.value?.isActive ?? false) debounceTimer.value!.cancel(); + debounceTimer.value = Timer(debounce, () { + noti.applyFilter(queryState.value); + }); + } + + void toggleFilterDisplay() { + showFilters.value = !showFilters.value; + } + + Widget buildFilterPanel() { + return PostFilterWidget( + categoryTabController: categoryTabController, + initialQuery: queryState.value, + onQueryChanged: (newQuery) { + queryState.value = newQuery; + noti.applyFilter(newQuery); + }, + hideSearch: true, + ); + } + + return Consumer( + builder: (context, ref, child) { + final searchState = ref.watch( + postListProvider(PostListQueryConfig(id: kSearchPostListId)), + ); + + return isWideScreen(context) + ? Row( + children: [ + Flexible( + flex: 4, + child: ExtendedRefreshIndicator( + onRefresh: noti.refresh, + child: CustomScrollView( + slivers: [ + SliverGap(16), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: SearchBar( + elevation: WidgetStateProperty.all(4), + controller: searchController, + hintText: 'search'.tr(), + leading: const Icon(Icons.search), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric(horizontal: 24), + ), + onChanged: onSearchChanged, + onSubmitted: (value) { + onSearchChanged(value, skipDebounce: true); + }, + ), + ), + ), + const SliverGap(12), + PaginationList( + provider: postListProvider( + PostListQueryConfig(id: kSearchPostListId), + ), + notifier: postListProvider( + PostListQueryConfig(id: kSearchPostListId), + ).notifier, + isSliver: true, + isRefreshable: false, + footerSkeletonChild: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: const PostItemSkeleton(), + ), + itemBuilder: (context, index, post) { + return Card( + margin: EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: PostActionableItem( + item: post, + borderRadius: 8, + ), + ); + }, + ), + if (searchState.value?.items.isEmpty == true && + searchController.text.isNotEmpty && + !searchState.isLoading) + SliverFillRemaining( + child: Center(child: Text('noResultsFound'.tr())), + ), + SliverGap(MediaQuery.of(context).padding.bottom + 16), + ], + ).padding(left: 8), + ), + ), + Flexible( + flex: 3, + child: Align( + alignment: Alignment.topLeft, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Gap(16), + Card( + margin: EdgeInsets.symmetric(horizontal: 8), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Row( + children: [ + const Icon( + Symbols.tune, + ).padding(horizontal: 8), + Expanded( + child: Text( + 'filters'.tr(), + style: Theme.of( + context, + ).textTheme.bodyLarge, + ), + ), + IconButton( + icon: Icon( + Symbols.filter_alt, + fill: showFilters.value ? 1 : null, + ), + onPressed: toggleFilterDisplay, + tooltip: 'toggleFilters'.tr(), + ), + const Gap(4), + ], + ), + ), + ), + const Gap(8), + if (showFilters.value) buildFilterPanel(), + ], + ), + ), + ), + ), + ], + ) + : CustomScrollView( + slivers: [ + const SliverGap(4), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: SearchBar( + elevation: WidgetStateProperty.all(4), + controller: searchController, + hintText: 'search'.tr(), + leading: const Icon(Icons.search), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric(horizontal: 24), + ), + onChanged: onSearchChanged, + onSubmitted: (value) { + onSearchChanged(value, skipDebounce: true); + }, + ), + ), + ), + if (showFilters.value) + SliverToBoxAdapter( + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), + child: buildFilterPanel(), + ), + ), + ), + PaginationList( + provider: postListProvider( + PostListQueryConfig(id: kSearchPostListId), + ), + notifier: postListProvider( + PostListQueryConfig(id: kSearchPostListId), + ).notifier, + isSliver: true, + isRefreshable: false, + footerSkeletonChild: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: const PostItemSkeleton(), + ), + itemBuilder: (context, index, post) { + return Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 600), + child: Card( + margin: EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: PostActionableItem( + item: post, + borderRadius: 8, + ), + ), + ), + ); + }, + ), + if (searchState.value?.items.isEmpty == true && + searchController.text.isNotEmpty && + !searchState.isLoading) + SliverFillRemaining( + child: Center(child: Text('noResultsFound'.tr())), + ), + ], + ); + }, + ); + } +} + +class _FediverseSearchTab extends HookConsumerWidget { + const _FediverseSearchTab(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final searchController = useTextEditingController(); + final debounce = useMemoized(() => const Duration(milliseconds: 500)); + final debounceTimer = useRef(null); + final searchResults = useState>([]); + final isSearching = useState(false); + + useEffect(() { + return () { + searchController.dispose(); + debounceTimer.value?.cancel(); + }; + }, []); + + Future performSearch(String query) async { + if (query.trim().isEmpty) { + searchResults.value = []; + return; + } + + isSearching.value = true; + try { + final service = ref.read(activityPubServiceProvider); + final results = await service.searchUsers(query); + searchResults.value = results; + } catch (err) { + showErrorAlert(err); + } finally { + isSearching.value = false; + } + } + + void onSearchChanged(String query) { + if (debounceTimer.value?.isActive ?? false) { + debounceTimer.value!.cancel(); + } + debounceTimer.value = Timer(debounce, () { + performSearch(query); + }); + } + + void updateActorIsFollowing(String actorId, bool isFollowing) { + searchResults.value = searchResults.value + .map( + (a) => a.id == actorId ? a.copyWith(isFollowing: isFollowing) : a, + ) + .toList(); + } + + Future handleFollow(SnActivityPubActor actor) async { + try { + updateActorIsFollowing(actor.id, true); + final service = ref.read(activityPubServiceProvider); + await service.followRemoteUser(actor.uri); + showSnackBar( + 'followedUser'.tr( + args: [ + '${actor.username?.isNotEmpty ?? false ? actor.username : actor.displayName}', + ], + ), + ); + } catch (err) { + showErrorAlert(err); + updateActorIsFollowing(actor.id, false); + } + } + + Future handleUnfollow(SnActivityPubActor actor) async { + try { + updateActorIsFollowing(actor.id, false); + final service = ref.read(activityPubServiceProvider); + await service.unfollowRemoteUser(actor.uri); + showSnackBar( + 'unfollowedUser'.tr( + args: [ + '${actor.username?.isNotEmpty ?? false ? actor.username : actor.displayName}', + ], + ), + ); + } catch (err) { + showErrorAlert(err); + updateActorIsFollowing(actor.id, true); + } + } + + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: SearchBar( + controller: searchController, + hintText: 'searchFediverseHint'.tr( + args: ['@username@instance.com'], + ), + leading: const Icon(Symbols.search).padding(horizontal: 24), + onChanged: onSearchChanged, + onSubmitted: (value) { + onSearchChanged(value); + performSearch(value); + }, + ), + ), + Expanded( + child: isSearching.value + ? const Center(child: CircularProgressIndicator()) + : searchResults.value.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Symbols.search, + size: 64, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(height: 16), + if (searchController.text.isEmpty) + Text( + 'searchFediverseEmpty'.tr(), + style: Theme.of(context).textTheme.titleMedium, + ) + else + Text( + 'searchFediverseNoResults'.tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + ) + : ExtendedRefreshIndicator( + onRefresh: () => performSearch(searchController.text), + child: ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: searchResults.value.length, + separatorBuilder: (context, index) => const Gap(8), + itemBuilder: (context, index) { + final actor = searchResults.value[index]; + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 560), + child: ApActorListItem( + actor: actor, + isFollowing: actor.isFollowing ?? false, + isLoading: false, + onFollow: () => handleFollow(actor), + onUnfollow: () => handleUnfollow(actor), + ), + ), + ); + }, + ), + ), + ), + ], + ); + } +} diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index efa7d282..b873c75e 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -794,6 +794,22 @@ class SettingsScreen extends HookConsumerWidget { ), ), + // Show fediverse content settings + ListTile( + minLeadingWidth: 48, + title: Text('settingsShowFediverseContent').tr(), + contentPadding: const EdgeInsets.only(left: 24, right: 17), + leading: const Icon(Symbols.public), + trailing: Switch( + value: settings.showFediverseContent, + onChanged: (value) { + ref + .read(appSettingsProvider.notifier) + .setShowFediverseContent(value); + }, + ), + ), + // Default screen settings ListTile( minLeadingWidth: 48, diff --git a/lib/widgets/chat/call_button.g.dart b/lib/widgets/chat/call_button.g.dart index 1224008f..efb6bf3c 100644 --- a/lib/widgets/chat/call_button.g.dart +++ b/lib/widgets/chat/call_button.g.dart @@ -64,7 +64,7 @@ final class OngoingCallProvider } } -String _$ongoingCallHash() => r'48031badb79efa07aefb3a4fc51635be457bd3f9'; +String _$ongoingCallHash() => r'3d1efaaca2981ebf698e9241453dbf2b2f13bfe3'; final class OngoingCallFamily extends $Family with $FunctionalFamilyOverride, String> {