diff --git a/app/lib/core/routing/routes.browser.dart b/app/lib/core/routing/routes.browser.dart index bf91db53..97f365e5 100644 --- a/app/lib/core/routing/routes.browser.dart +++ b/app/lib/core/routing/routes.browser.dart @@ -28,6 +28,10 @@ part of 'routes.dart'; ), ], ), + TypedGoRoute( + name: 'ContainerSelectionRoute', + path: 'select_container', + ), TypedGoRoute( name: 'TabTreeRoute', path: 'tab_tree/:rootTabId', @@ -97,6 +101,14 @@ class ContainerListRoute extends GoRouteData with _$ContainerListRoute { } } +class ContainerSelectionRoute extends GoRouteData + with _$ContainerSelectionRoute { + @override + Widget build(BuildContext context, GoRouterState state) { + return const ContainerSelectionScreen(); + } +} + class ContainerEditRoute extends GoRouteData with _$ContainerEditRoute { final ContainerData $extra; diff --git a/app/lib/core/routing/routes.dart b/app/lib/core/routing/routes.dart index c0925132..053b6701 100644 --- a/app/lib/core/routing/routes.dart +++ b/app/lib/core/routing/routes.dart @@ -17,6 +17,7 @@ import 'package:weblibre/features/geckoview/features/search/presentation/screens import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_list.dart'; +import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_selection.dart'; import 'package:weblibre/features/onboarding/presentation/onboarding.dart'; import 'package:weblibre/features/settings/presentation/screens/bang_settings.dart'; import 'package:weblibre/features/settings/presentation/screens/developer_settings.dart'; diff --git a/app/lib/core/routing/routes.g.dart b/app/lib/core/routing/routes.g.dart index b3697f23..db5ec706 100644 --- a/app/lib/core/routing/routes.g.dart +++ b/app/lib/core/routing/routes.g.dart @@ -327,6 +327,12 @@ RouteBase get $browserRoute => GoRouteData.$route( ), ], ), + GoRouteData.$route( + path: 'select_container', + name: 'ContainerSelectionRoute', + + factory: _$ContainerSelectionRoute._fromState, + ), GoRouteData.$route( path: 'tab_tree/:rootTabId', name: 'TabTreeRoute', @@ -534,6 +540,27 @@ mixin _$ContainerEditRoute on GoRouteData { context.replace(location, extra: _self.$extra); } +mixin _$ContainerSelectionRoute on GoRouteData { + static ContainerSelectionRoute _fromState(GoRouterState state) => + ContainerSelectionRoute(); + + @override + String get location => GoRouteData.$location('/select_container'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + mixin _$TabTreeRoute on GoRouteData { static TabTreeRoute _fromState(GoRouterState state) => TabTreeRoute(state.pathParameters['rootTabId']!); diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart index 47263fad..b2fa8fcf 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart @@ -7,13 +7,17 @@ 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:share_plus/share_plus.dart'; +import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/data/models/web_page_info.dart'; import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/presentation/widgets/site_search.dart'; +import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; import 'package:weblibre/features/user/domain/providers.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/share_tile.dart'; @@ -110,7 +114,6 @@ class WebPageDialog extends HookConsumerWidget { availableBangCount == null || availableBangCount > 0) const Divider(), - WebsiteFeedTile(url, precachedInfo: precachedInfo), ListTile( leading: const Icon(MdiIcons.contentCopy), title: const Text('Copy address'), @@ -180,6 +183,34 @@ class WebPageDialog extends HookConsumerWidget { } }, ), + ListTile( + leading: const Icon(MdiIcons.folderArrowUpDown), + title: const Text('Assign container'), + onTap: () async { + final selectedTabId = ref.read(selectedTabProvider); + if (selectedTabId != null) { + final targetContainerId = + await ContainerSelectionRoute().push( + context, + ); + + if (targetContainerId != null) { + final containerData = await ref + .read(containerRepositoryProvider.notifier) + .getContainerData(targetContainerId); + + if (containerData != null) { + await ref + .read(tabDataRepositoryProvider.notifier) + .assignContainer( + selectedTabId, + containerData, + ); + } + } + } + }, + ), ShareTile( onTap: () async { await SharePlus.instance.share( @@ -231,6 +262,7 @@ class WebPageDialog extends HookConsumerWidget { } }, ), + WebsiteFeedTile(url, precachedInfo: precachedInfo), ], ), ); diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart new file mode 100644 index 00000000..49d6350b --- /dev/null +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart @@ -0,0 +1,83 @@ +import 'package:fading_scroll/fading_scroll.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:weblibre/core/routing/routes.dart'; +import 'package:weblibre/core/uuid.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; +import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_list_tile.dart'; + +class ContainerSelectionScreen extends HookConsumerWidget { + const ContainerSelectionScreen(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar(title: const Text('Select Container')), + body: HookConsumer( + builder: (context, ref, child) { + final containersAsync = ref.watch(containersWithCountProvider); + + return Skeletonizer( + enabled: containersAsync.isLoading, + child: containersAsync.when( + skipLoadingOnReload: true, + data: (containers) => FadingScroll( + fadingSize: 25, + builder: (context, controller) { + return ListView.builder( + controller: controller, + itemCount: containers.length, + itemBuilder: (context, index) { + final container = containers[index]; + return ContainerListTile( + container, + isSelected: false, + onTap: () { + context.pop(container.id); + }, + ); + }, + ); + }, + ), + error: (error, stackTrace) => SizedBox.shrink(), + loading: () => ListView.builder( + itemCount: 3, + itemBuilder: (context, index) => ContainerListTile( + ContainerData(id: 'null', color: Colors.transparent), + isSelected: false, + onTap: null, + ), + ), + ), + ); + }, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + final initialColor = await ref + .read(containerRepositoryProvider.notifier) + .unusedRandomContainerColor(); + + if (context.mounted) { + final result = await ContainerCreateRoute( + ContainerData(id: uuid.v7(), color: initialColor), + ).push(context); + + if (result != null) { + await ref + .read(containerRepositoryProvider.notifier) + .addContainer(result); + } + } + }, + label: const Text('Container'), + icon: const Icon(Icons.add), + ), + ); + } +}