diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index 398e6545..b84536c0 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -59,6 +59,7 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart import 'package:weblibre/features/web_search/domain/controllers/sandbox_capture_controller.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart'; +import 'package:weblibre/utils/ui_helper.dart' as ui_helper; class BrowserTopAppBar extends StatelessWidget { final bool showMainToolbar; @@ -705,7 +706,15 @@ class QuickTabSwitcher extends HookConsumerWidget { hierarchical: false, sortPinnedFirst: sortPinnedFirst, ); - if (result == null) return; + if (result == null) { + if (context.mounted) { + ui_helper.showInfoMessage( + context, + 'Tab cannot be moved here', + ); + } + return; + } await ref .read(tabDataRepositoryProvider.notifier) .reorderTabs( diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart index 62901ee3..c359ee77 100644 --- a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart +++ b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart @@ -97,6 +97,7 @@ class ContainerEditScreen extends HookConsumerWidget { ); final clearDataOnExit = useState(initialContainer.metadata.clearDataOnExit); final assignedSites = useState(initialContainer.metadata.assignedSites); + final isPinned = useState(initialContainer.isPinned); final textController = useTextEditingController( text: initialContainer.name, @@ -108,6 +109,7 @@ class ContainerEditScreen extends HookConsumerWidget { return initialContainer.copyWith( name: name.isNotEmpty ? name : null, color: selectedColor.value, + isPinned: isPinned.value, metadata: initialContainer.metadata.copyWith( contextualIdentity: contextualIdentity.value, iconData: selectedIcon.value, @@ -123,15 +125,18 @@ class ContainerEditScreen extends HookConsumerWidget { Future saveContainer() async { final container = buildContainer(); + final repository = ref.read(containerRepositoryProvider.notifier); switch (_mode) { case _DialogMode.create: - await ref - .read(containerRepositoryProvider.notifier) - .addContainer(container); + await repository.addContainer(container); case _DialogMode.edit: - await ref - .read(containerRepositoryProvider.notifier) - .replaceContainer(container); + await repository.replaceContainer(container); + } + if (isPinned.value != initialContainer.isPinned) { + await repository.setContainerPinned( + container.id, + isPinned: isPinned.value, + ); } return container; } @@ -341,6 +346,31 @@ class ContainerEditScreen extends HookConsumerWidget { ), ), const SizedBox(height: 28), + Text( + 'Display', + style: theme.textTheme.titleSmall?.copyWith( + color: colorScheme.primary, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 12), + Card.filled( + margin: EdgeInsets.zero, + color: colorScheme.surfaceContainer, + clipBehavior: Clip.antiAlias, + child: SwitchListTile.adaptive( + value: isPinned.value, + title: const Text('Pin Container'), + subtitle: const Text( + 'Keep this container at the top of the list', + ), + secondary: const Icon(MdiIcons.pin), + onChanged: (value) { + isPinned.value = value; + }, + ), + ), + const SizedBox(height: 24), Text( 'Privacy & Security', style: theme.textTheme.titleSmall?.copyWith( diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart index 84575061..3942213c 100644 --- a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart +++ b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart @@ -21,6 +21,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:weblibre/core/routing/routes.dart'; @@ -246,6 +247,11 @@ class _ContainerCard extends HookConsumerWidget { label: '$tabCount ${tabCount == 1 ? 'tab' : 'tabs'}', ), + if (container.isPinned) + const _ContainerInfoChip( + icon: MdiIcons.pin, + label: 'Pinned', + ), if (container.metadata.contextualIdentity != null) const _ContainerInfoChip( icon: Icons.cookie_outlined, diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chip_content.dart b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chip_content.dart index a0e9d91a..b8cf6919 100644 --- a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chip_content.dart +++ b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chip_content.dart @@ -18,6 +18,7 @@ * along with this program. If not, see . */ import 'package:flutter/material.dart'; +import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:nullability/nullability.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.dart'; @@ -62,6 +63,10 @@ Widget buildContainerChipLabel( child: Row( mainAxisSize: MainAxisSize.min, children: [ + if (container.isPinned) ...[ + Icon(MdiIcons.pin, size: 14, color: foregroundColor), + const SizedBox(width: 4), + ], Flexible(child: ContainerTitle(container: container)), if (trailing != null) ...[const SizedBox(width: 6), trailing], ],