diff --git a/app/lib/features/search_browser/domain/entities/modes.dart b/app/lib/features/search_browser/domain/entities/modes.dart index 0137ced5..1489dc94 100644 --- a/app/lib/features/search_browser/domain/entities/modes.dart +++ b/app/lib/features/search_browser/domain/entities/modes.dart @@ -1,4 +1,9 @@ -enum KagiTool { search, assistant, summarizer } +enum KagiTool { + search, + summarizer, + //Sort early access features last + assistant, +} enum AssistantMode { research(7), diff --git a/app/lib/features/search_browser/presentation/screens/browser.dart b/app/lib/features/search_browser/presentation/screens/browser.dart index 63baa20f..2dfe21ee 100644 --- a/app/lib/features/search_browser/presentation/screens/browser.dart +++ b/app/lib/features/search_browser/presentation/screens/browser.dart @@ -34,6 +34,12 @@ class KagiScreen extends HookConsumerWidget { final displayedSheet = ref.watch(bottomSheetProvider); final displayedOverlayDialog = ref.watch(overlayDialogProvider); + final showEarlyAccessFeatures = ref.watch( + settingsRepositoryProvider.select( + (value) => value.valueOrNull?.showEarlyAccessFeatures ?? true, + ), + ); + final lastBackButtonPress = useRef(null); final webViewController = useRef(null); @@ -188,15 +194,6 @@ class KagiScreen extends HookConsumerWidget { leadingIcon: const Icon(MdiIcons.searchWeb), child: const Text('Search'), ), - MenuItemButton( - onPressed: () { - ref.read(createTabStreamProvider.notifier).createTab( - CreateTab(preferredTool: KagiTool.assistant), - ); - }, - leadingIcon: const Icon(MdiIcons.brain), - child: const Text('Assistant'), - ), MenuItemButton( onPressed: () { ref.read(createTabStreamProvider.notifier).createTab( @@ -206,15 +203,26 @@ class KagiScreen extends HookConsumerWidget { leadingIcon: const Icon(MdiIcons.text), child: const Text('Summarizer'), ), + if (showEarlyAccessFeatures) + MenuItemButton( + onPressed: () { + ref.read(createTabStreamProvider.notifier).createTab( + CreateTab(preferredTool: KagiTool.assistant), + ); + }, + leadingIcon: const Icon(MdiIcons.brain), + child: const Text('Assistant'), + ), const Divider(), - MenuItemButton( - onPressed: () async { - await context.push(ChatArchiveListRoute().location); - }, - leadingIcon: const Icon(MdiIcons.archive), - child: const Text('Chat Archive'), - ), - const Divider(), + if (showEarlyAccessFeatures) + MenuItemButton( + onPressed: () async { + await context.push(ChatArchiveListRoute().location); + }, + leadingIcon: const Icon(MdiIcons.archive), + child: const Text('Chat Archive'), + ), + if (showEarlyAccessFeatures) const Divider(), MenuItemButton( onPressed: () async { await context.push(AboutRoute().location); diff --git a/app/lib/features/search_browser/presentation/widgets/landing/action.dart b/app/lib/features/search_browser/presentation/widgets/landing/action.dart index a203deec..a1245d8a 100644 --- a/app/lib/features/search_browser/presentation/widgets/landing/action.dart +++ b/app/lib/features/search_browser/presentation/widgets/landing/action.dart @@ -21,6 +21,12 @@ class LandingAction extends HookConsumerWidget { ), ); + final showEarlyAccessFeatures = ref.watch( + settingsRepositoryProvider.select( + (value) => value.valueOrNull?.showEarlyAccessFeatures ?? true, + ), + ); + return Visibility( visible: sessionTokenAvailable, replacement: Container( @@ -68,15 +74,6 @@ class LandingAction extends HookConsumerWidget { icon: const Icon(MdiIcons.searchWeb), label: const Text('Search'), ), - OutlinedButton.icon( - onPressed: () { - ref.read(createTabStreamProvider.notifier).createTab( - CreateTab(preferredTool: KagiTool.assistant), - ); - }, - icon: const Icon(MdiIcons.brain), - label: const Text('Assistant'), - ), OutlinedButton.icon( onPressed: () { ref.read(createTabStreamProvider.notifier).createTab( @@ -86,6 +83,16 @@ class LandingAction extends HookConsumerWidget { icon: const Icon(MdiIcons.text), label: const Text('Summarizer'), ), + if (showEarlyAccessFeatures) + OutlinedButton.icon( + onPressed: () { + ref.read(createTabStreamProvider.notifier).createTab( + CreateTab(preferredTool: KagiTool.assistant), + ); + }, + icon: const Icon(MdiIcons.brain), + label: const Text('Assistant'), + ), ], ), ); diff --git a/app/lib/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart b/app/lib/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart index 8a59c49e..991ff5bd 100644 --- a/app/lib/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart +++ b/app/lib/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart @@ -3,6 +3,7 @@ import 'package:bang_navigator/features/search_browser/domain/entities/sheet.dar import 'package:bang_navigator/features/search_browser/presentation/widgets/tabs/assistant_tab.dart'; import 'package:bang_navigator/features/search_browser/presentation/widgets/tabs/search_tab.dart'; import 'package:bang_navigator/features/search_browser/presentation/widgets/tabs/summarize_tab.dart'; +import 'package:bang_navigator/features/settings/data/repositories/settings_repository.dart'; import 'package:bang_navigator/features/share_intent/domain/entities/shared_content.dart'; import 'package:bang_navigator/presentation/hooks/sync_page_tab.dart'; import 'package:expandable_page_view/expandable_page_view.dart'; @@ -24,6 +25,12 @@ class SharedContentSheet extends HookConsumerWidget { }); @override Widget build(BuildContext context, WidgetRef ref) { + final showEarlyAccessFeatures = ref.watch( + settingsRepositoryProvider.select( + (value) => value.valueOrNull?.showEarlyAccessFeatures ?? true, + ), + ); + final sharedContent = useMemoized( () => (parameter.content != null) ? SharedContent.parse(parameter.content!) @@ -31,14 +38,17 @@ class SharedContentSheet extends HookConsumerWidget { ); final tabController = useTabController( - initialLength: KagiTool.values.length, + initialLength: KagiTool.values.length - (showEarlyAccessFeatures ? 0 : 1), initialIndex: parameter.preferredTool?.index ?? switch (sharedContent) { - SharedText(text: final text) => (text.length > 25) + SharedText(text: final text) => + (showEarlyAccessFeatures && text.length > 25) + ? KagiTool.assistant.index + : KagiTool.search.index, + SharedUrl() => KagiTool.summarizer.index, + null => showEarlyAccessFeatures ? KagiTool.assistant.index : KagiTool.search.index, - SharedUrl() => KagiTool.summarizer.index, - null => KagiTool.assistant.index, }, ); final pageController = usePageController(initialPage: tabController.index); @@ -50,19 +60,20 @@ class SharedContentSheet extends HookConsumerWidget { children: [ TabBar( controller: tabController, - tabs: const [ - Tab( + tabs: [ + const Tab( icon: Icon(MdiIcons.searchWeb), text: 'Search', ), - Tab( - icon: Icon(MdiIcons.brain), - text: 'Assistant', - ), - Tab( + const Tab( icon: Icon(MdiIcons.text), text: 'Summarize', ), + if (showEarlyAccessFeatures) + const Tab( + icon: Icon(MdiIcons.brain), + text: 'Assistant', + ), ], ), Padding( @@ -74,14 +85,15 @@ class SharedContentSheet extends HookConsumerWidget { sharedContent: sharedContent, onSubmit: onSubmit, ), - AssistantTab( - sharedContent: sharedContent, - onSubmit: onSubmit, - ), SummarizeTab( sharedContent: sharedContent, onSubmit: onSubmit, ), + if (showEarlyAccessFeatures) + AssistantTab( + sharedContent: sharedContent, + onSubmit: onSubmit, + ), ], ), ), diff --git a/app/lib/features/settings/data/models/settings.dart b/app/lib/features/settings/data/models/settings.dart index d782d389..3e4d6c41 100644 --- a/app/lib/features/settings/data/models/settings.dart +++ b/app/lib/features/settings/data/models/settings.dart @@ -6,12 +6,14 @@ part 'settings.g.dart'; @CopyWith() class Settings with FastEquatable { final String? kagiSession; + final bool showEarlyAccessFeatures; final bool incognitoMode; final bool enableJavascript; final bool launchUrlExternal; Settings({ required this.kagiSession, + required this.showEarlyAccessFeatures, required this.incognitoMode, required this.enableJavascript, required this.launchUrlExternal, @@ -19,10 +21,12 @@ class Settings with FastEquatable { Settings.withDefaults({ required this.kagiSession, + bool? showEarlyAccessFeatures, bool? incognitoMode, bool? enableJavascript, bool? launchUrlExternal, - }) : incognitoMode = incognitoMode ?? true, + }) : showEarlyAccessFeatures = showEarlyAccessFeatures ?? true, + incognitoMode = incognitoMode ?? true, enableJavascript = enableJavascript ?? true, launchUrlExternal = launchUrlExternal ?? false; @@ -32,6 +36,7 @@ class Settings with FastEquatable { @override List get hashParameters => [ kagiSession, + showEarlyAccessFeatures, incognitoMode, enableJavascript, launchUrlExternal, diff --git a/app/lib/features/settings/data/models/settings.g.dart b/app/lib/features/settings/data/models/settings.g.dart index 2598daf0..36aa2537 100644 --- a/app/lib/features/settings/data/models/settings.g.dart +++ b/app/lib/features/settings/data/models/settings.g.dart @@ -9,6 +9,8 @@ part of 'settings.dart'; abstract class _$SettingsCWProxy { Settings kagiSession(String? kagiSession); + Settings showEarlyAccessFeatures(bool showEarlyAccessFeatures); + Settings incognitoMode(bool incognitoMode); Settings enableJavascript(bool enableJavascript); @@ -23,6 +25,7 @@ abstract class _$SettingsCWProxy { /// ```` Settings call({ String? kagiSession, + bool? showEarlyAccessFeatures, bool? incognitoMode, bool? enableJavascript, bool? launchUrlExternal, @@ -38,6 +41,10 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy { @override Settings kagiSession(String? kagiSession) => this(kagiSession: kagiSession); + @override + Settings showEarlyAccessFeatures(bool showEarlyAccessFeatures) => + this(showEarlyAccessFeatures: showEarlyAccessFeatures); + @override Settings incognitoMode(bool incognitoMode) => this(incognitoMode: incognitoMode); @@ -60,6 +67,7 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy { /// ```` Settings call({ Object? kagiSession = const $CopyWithPlaceholder(), + Object? showEarlyAccessFeatures = const $CopyWithPlaceholder(), Object? incognitoMode = const $CopyWithPlaceholder(), Object? enableJavascript = const $CopyWithPlaceholder(), Object? launchUrlExternal = const $CopyWithPlaceholder(), @@ -69,6 +77,12 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy { ? _value.kagiSession // ignore: cast_nullable_to_non_nullable : kagiSession as String?, + showEarlyAccessFeatures: + showEarlyAccessFeatures == const $CopyWithPlaceholder() || + showEarlyAccessFeatures == null + ? _value.showEarlyAccessFeatures + // ignore: cast_nullable_to_non_nullable + : showEarlyAccessFeatures as bool, incognitoMode: incognitoMode == const $CopyWithPlaceholder() || incognitoMode == null ? _value.incognitoMode diff --git a/app/lib/features/settings/data/repositories/settings_repository.dart b/app/lib/features/settings/data/repositories/settings_repository.dart index 27da69a0..fde66c83 100644 --- a/app/lib/features/settings/data/repositories/settings_repository.dart +++ b/app/lib/features/settings/data/repositories/settings_repository.dart @@ -10,6 +10,7 @@ typedef UpdateSettingsFunc = Settings Function(Settings currentSettings); @Riverpod(keepAlive: true) class SettingsRepository extends _$SettingsRepository { static const _sessionStorageKey = 'b4ng_kagi_session'; + static const _showEarlyAccessFeaturesKey = 'b4ng_show_early_access'; static const _incognitoStorageKey = 'b4ng_settings_incognito'; static const _javascriptStorageKey = 'b4ng_settings_js'; static const _launchExternalStorageKey = 'b4ng_settings_launch_external'; @@ -35,6 +36,16 @@ class SettingsRepository extends _$SettingsRepository { ); } + if (newSettings.showEarlyAccessFeatures != + oldSettings.showEarlyAccessFeatures) { + await _sharedPreferences.then( + (s) => s.setBool( + _showEarlyAccessFeaturesKey, + newSettings.showEarlyAccessFeatures, + ), + ); + } + if (newSettings.incognitoMode != oldSettings.incognitoMode) { await _sharedPreferences.then( (s) => s.setBool(_incognitoStorageKey, newSettings.incognitoMode), @@ -66,6 +77,8 @@ class SettingsRepository extends _$SettingsRepository { return Settings.withDefaults( kagiSession: await _flutterSecureStorage.read(key: _sessionStorageKey), + showEarlyAccessFeatures: + sharedPreferences.getBool(_showEarlyAccessFeaturesKey), incognitoMode: sharedPreferences.getBool(_incognitoStorageKey), enableJavascript: sharedPreferences.getBool(_javascriptStorageKey), launchUrlExternal: sharedPreferences.getBool(_launchExternalStorageKey), diff --git a/app/lib/features/settings/data/repositories/settings_repository.g.dart b/app/lib/features/settings/data/repositories/settings_repository.g.dart index 3e947766..8985aa2c 100644 --- a/app/lib/features/settings/data/repositories/settings_repository.g.dart +++ b/app/lib/features/settings/data/repositories/settings_repository.g.dart @@ -7,7 +7,7 @@ part of 'settings_repository.dart'; // ************************************************************************** String _$settingsRepositoryHash() => - r'2b715f29915e541cec5c7c0993c8099944989f48'; + r'0beffd6449e5488e61beb181fefefd4aa24361b0'; /// See also [SettingsRepository]. @ProviderFor(SettingsRepository) diff --git a/app/lib/features/settings/presentation/screens/settings.dart b/app/lib/features/settings/presentation/screens/settings.dart index ca3ea7d9..601aa1c3 100644 --- a/app/lib/features/settings/presentation/screens/settings.dart +++ b/app/lib/features/settings/presentation/screens/settings.dart @@ -22,9 +22,11 @@ class SettingsScreen extends HookConsumerWidget { ); final hideSessionText = useState(true); - // final isSavingSettings = ref.watch( - // saveSettingsControllerProvider.select((value) => value.isLoading), - // ); + final showEarlyAccessFeatures = ref.watch( + settingsRepositoryProvider.select( + (value) => value.valueOrNull?.showEarlyAccessFeatures ?? true, + ), + ); final incognitoEnabled = ref.watch( settingsRepositoryProvider @@ -114,6 +116,19 @@ class SettingsScreen extends HookConsumerWidget { ), ), ), + SwitchListTile.adaptive( + title: const Text('Show Early Access Features'), + subtitle: const Text( + "Displays Kagi's early access features in the UI. As an Ultimate subscriber, you will likely want to have this enabled.", + ), + value: showEarlyAccessFeatures, + onChanged: (value) async { + await ref.read(saveSettingsControllerProvider.notifier).save( + (currentSettings) => currentSettings.copyWith + .showEarlyAccessFeatures(value), + ); + }, + ), SwitchListTile.adaptive( title: const Text('Incognito Mode'), subtitle: const Text( diff --git a/app/lib/features/web_view/presentation/widgets/web_view.dart b/app/lib/features/web_view/presentation/widgets/web_view.dart index 9e27eade..e43ef870 100644 --- a/app/lib/features/web_view/presentation/widgets/web_view.dart +++ b/app/lib/features/web_view/presentation/widgets/web_view.dart @@ -88,6 +88,12 @@ class _WebViewState extends ConsumerState { @override Widget build(BuildContext context) { + final showEarlyAccessFeatures = ref.watch( + settingsRepositoryProvider.select( + (value) => value.valueOrNull?.showEarlyAccessFeatures ?? true, + ), + ); + final initialSettings = useMemoized( () => InAppWebViewSettings( // isInspectable: kDebugMode, @@ -162,23 +168,24 @@ class _WebViewState extends ConsumerState { } }, ), - ContextMenuItem( - id: 2, - title: "Assistant", - action: () async { - final selectedText = - await widget.page.value.controller?.getSelectedText(); + if (showEarlyAccessFeatures) + ContextMenuItem( + id: 2, + title: "Assistant", + action: () async { + final selectedText = + await widget.page.value.controller?.getSelectedText(); - if (selectedText != null && selectedText.isNotEmpty) { - ref.read(bottomSheetProvider.notifier).show( - CreateTab( - content: selectedText, - preferredTool: KagiTool.assistant, - ), - ); - } - }, - ), + if (selectedText != null && selectedText.isNotEmpty) { + ref.read(bottomSheetProvider.notifier).show( + CreateTab( + content: selectedText, + preferredTool: KagiTool.assistant, + ), + ); + } + }, + ), ], ), onWebViewCreated: (controller) async {