first implementation finished
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:kagi_bang_bang/core/routing/routes.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/domain/entities/modes.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/domain/entities/sheet.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/domain/providers.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/domain/services/create_tab.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/domain/services/session.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/presentation/widgets/app_bar_title.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/presentation/widgets/sheets/view_tabs_sheet.dart';
|
||||
import 'package:kagi_bang_bang/features/search_browser/presentation/widgets/tabs_action_button.dart';
|
||||
import 'package:kagi_bang_bang/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:kagi_bang_bang/features/web_view/domain/repositories/web_view.dart';
|
||||
import 'package:kagi_bang_bang/features/web_view/presentation/controllers/switch_new_tab.dart';
|
||||
import 'package:kagi_bang_bang/features/web_view/presentation/widgets/web_page_dialog.dart';
|
||||
import 'package:kagi_bang_bang/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:kagi_bang_bang/presentation/hooks/overlay_portal_controller.dart';
|
||||
import 'package:kagi_bang_bang/presentation/widgets/animated_indexed_stack.dart';
|
||||
import 'package:kagi_bang_bang/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class KagiScreen extends HookConsumerWidget {
|
||||
const KagiScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final displayedSheet = ref.watch(bottomSheetProvider);
|
||||
final displayedOverlayDialog = ref.watch(overlayDialogProvider);
|
||||
|
||||
final lastBackButtonPress = useRef<DateTime?>(null);
|
||||
final webViewController = useRef<InAppWebViewController?>(null);
|
||||
|
||||
final overlayController = useOverlayPortalController();
|
||||
|
||||
ref.listen(
|
||||
overlayDialogProvider,
|
||||
(previous, next) {
|
||||
if (next != null) {
|
||||
overlayController.show();
|
||||
} else {
|
||||
overlayController.hide();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
settingsRepositoryProvider
|
||||
.select((value) => value.valueOrNull?.kagiSession),
|
||||
(previous, next) async {
|
||||
if (next != null && next.isNotEmpty) {
|
||||
await ref.read(sessionServiceProvider.notifier).setKagiSession(next);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: AppBarTitle(
|
||||
onTap: () {
|
||||
final page = ref.read(webViewTabControllerProvider)?.page.value;
|
||||
|
||||
if (page != null) {
|
||||
ref.watch(overlayDialogProvider.notifier).show(
|
||||
WebPageDialog(
|
||||
page: page,
|
||||
webViewController: webViewController.value,
|
||||
onDismiss:
|
||||
ref.watch(overlayDialogProvider.notifier).dismiss,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TabsActionButton(
|
||||
onTap: () {
|
||||
if (displayedSheet case ViewTabs()) {
|
||||
ref.read(bottomSheetProvider.notifier).dismiss();
|
||||
} else {
|
||||
ref.read(bottomSheetProvider.notifier).show(ViewTabs());
|
||||
}
|
||||
},
|
||||
),
|
||||
MenuAnchor(
|
||||
builder: (context, controller, child) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
);
|
||||
},
|
||||
menuChildren: [
|
||||
HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final activeWebView = ref.watch(webViewTabControllerProvider);
|
||||
final history = useListenableSelector(
|
||||
activeWebView?.page,
|
||||
() =>
|
||||
activeWebView?.page.value.pageHistory ??
|
||||
(canGoBack: false, canGoForward: false),
|
||||
);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: IconButton(
|
||||
onPressed: (history.canGoBack)
|
||||
? () async {
|
||||
await webViewController.value?.goBack();
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48, child: VerticalDivider()),
|
||||
Expanded(
|
||||
child: IconButton(
|
||||
onPressed: (history.canGoForward)
|
||||
? () async {
|
||||
await webViewController.value?.goForward();
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await webViewController.value?.reload();
|
||||
},
|
||||
leadingIcon: const Icon(Icons.refresh),
|
||||
child: const Text('Reload'),
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context.push(SettingsRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.settings),
|
||||
child: const Text('Settings'),
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final url = await webViewController.value?.getUrl();
|
||||
if (url != null) {
|
||||
await Share.shareUri(url);
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.share),
|
||||
child: const Text('Share'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final url = await webViewController.value?.getUrl();
|
||||
if (url != null) {
|
||||
if (!await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
if (context.mounted) {
|
||||
ui_helper.showErrorMessage(
|
||||
context,
|
||||
'Could not launch URL ($url)',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.open_in_browser),
|
||||
child: const Text('Launch External'),
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
ref.read(createTabStreamProvider.notifier).createTab(
|
||||
CreateTab(preferredTool: KagiTool.search),
|
||||
);
|
||||
},
|
||||
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(
|
||||
CreateTab(preferredTool: KagiTool.summarizer),
|
||||
);
|
||||
},
|
||||
leadingIcon: const Icon(MdiIcons.text),
|
||||
child: const Text('Summarizer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: OverlayPortal(
|
||||
controller: overlayController,
|
||||
overlayChildBuilder: (context) {
|
||||
return displayedOverlayDialog!;
|
||||
},
|
||||
child: Listener(
|
||||
onPointerDown: (displayedSheet != null)
|
||||
? (_) {
|
||||
ref.read(bottomSheetProvider.notifier).dismiss();
|
||||
}
|
||||
: null,
|
||||
child: HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final webViews = ref.watch(webViewRepositoryProvider);
|
||||
final activeWebView = ref.watch(webViewTabControllerProvider);
|
||||
useListenableCallback(activeWebView?.page, () {
|
||||
webViewController.value = activeWebView?.page.value.controller;
|
||||
});
|
||||
|
||||
return BackButtonListener(
|
||||
onBackButtonPressed: () async {
|
||||
if (activeWebView?.page.value.pageHistory.canGoBack == true) {
|
||||
await activeWebView?.page.value.controller?.goBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastBackButtonPress.value != null &&
|
||||
lastBackButtonPress.value!.difference(DateTime.now()) <
|
||||
const Duration(seconds: 2)) {
|
||||
lastBackButtonPress.value = null;
|
||||
|
||||
return false;
|
||||
} else {
|
||||
lastBackButtonPress.value = DateTime.now();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please click BACK again to exit'),
|
||||
),
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: AnimatedIndexedStack(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
transitionBuilder: (child, animation, secondaryAnimation) =>
|
||||
SharedAxisTransition(
|
||||
animation: animation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.horizontal,
|
||||
child: child,
|
||||
),
|
||||
key: ValueKey(
|
||||
(activeWebView != null)
|
||||
? webViews.keys
|
||||
.toList()
|
||||
.indexOf(activeWebView.page.value.key)
|
||||
: null,
|
||||
),
|
||||
index: (activeWebView != null)
|
||||
? webViews.keys
|
||||
.toList()
|
||||
.indexOf(activeWebView.page.value.key)
|
||||
: null,
|
||||
children: webViews.values.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSheet: (displayedSheet != null)
|
||||
? NotificationListener<DraggableScrollableNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification.extent <= 0.1) {
|
||||
ref.read(bottomSheetProvider.notifier).dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
child: switch (displayedSheet) {
|
||||
ViewTabs() => DraggableScrollableSheet(
|
||||
expand: false,
|
||||
minChildSize: 0.1,
|
||||
builder: (context, scrollController) {
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: ViewTabsSheet(
|
||||
onClose: () {
|
||||
ref.read(bottomSheetProvider.notifier).dismiss();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
final CreateTab parameter => DraggableScrollableSheet(
|
||||
expand: false,
|
||||
initialChildSize: 0.8,
|
||||
minChildSize: 0.1,
|
||||
builder: (context, scrollController) {
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: SharedContentSheet(
|
||||
key: ObjectKey(parameter),
|
||||
parameter: parameter,
|
||||
onSubmit: (url) async {
|
||||
await ref
|
||||
.read(switchNewTabControllerProvider.notifier)
|
||||
.add(url);
|
||||
|
||||
ref.read(bottomSheetProvider.notifier).dismiss();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user