various ui improvements

This commit is contained in:
Fabian Freund
2025-05-03 23:43:30 +02:00
parent ca32c7d43d
commit 10b2e46984
8 changed files with 122 additions and 89 deletions
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$tabRepositoryHash() => r'743732f6911372ea901b5a9402791569bfe80137'; String _$tabRepositoryHash() => r'9b7ef03bec41f149b3cb456b73d7ee0e1b2804df';
/// See also [TabRepository]. /// See also [TabRepository].
@ProviderFor(TabRepository) @ProviderFor(TabRepository)
@@ -39,10 +39,6 @@ class BrowserScreen extends HookConsumerWidget {
final displayedSheet = ref.watch(bottomSheetControllerProvider); final displayedSheet = ref.watch(bottomSheetControllerProvider);
final displayedOverlay = ref.watch(overlayControllerProvider); final displayedOverlay = ref.watch(overlayControllerProvider);
final selectedTabId = ref.watch(
selectedTabStateProvider.select((value) => value?.id),
);
final lastBackButtonPress = useRef<DateTime?>(null); final lastBackButtonPress = useRef<DateTime?>(null);
final overlayController = useOverlayPortalController(); final overlayController = useOverlayPortalController();
@@ -91,10 +87,7 @@ class BrowserScreen extends HookConsumerWidget {
return Visibility( return Visibility(
visible: !tabInFullScreen, visible: !tabInFullScreen,
child: BrowserBottomAppBar( child: BrowserBottomAppBar(displayedSheet: displayedSheet),
selectedTabId: selectedTabId,
displayedSheet: displayedSheet,
),
); );
}, },
), ),
@@ -129,10 +122,7 @@ class BrowserScreen extends HookConsumerWidget {
: null, : null,
child: BackButtonListener( child: BackButtonListener(
onBackButtonPressed: () async { onBackButtonPressed: () async {
final tabState = final tabState = ref.read(selectedTabStateProvider);
(selectedTabId != null)
? ref.read(tabStateProvider(selectedTabId))
: null;
final tabCount = ref.read( final tabCount = ref.read(
tabListProvider.select((tabs) => tabs.value.length), tabListProvider.select((tabs) => tabs.value.length),
@@ -167,7 +157,7 @@ class BrowserScreen extends HookConsumerWidget {
lastBackButtonPress.value = null; lastBackButtonPress.value = null;
final controller = ref.read( final controller = ref.read(
tabSessionProvider(tabId: selectedTabId).notifier, selectedTabSessionNotifierProvider,
); );
await controller.stopLoading(); await controller.stopLoading();
@@ -176,7 +166,7 @@ class BrowserScreen extends HookConsumerWidget {
lastBackButtonPress.value = null; lastBackButtonPress.value = null;
final controller = ref.read( final controller = ref.read(
tabSessionProvider(tabId: selectedTabId).notifier, selectedTabSessionNotifierProvider,
); );
await controller.goBack(); await controller.goBack();
@@ -190,7 +180,7 @@ class BrowserScreen extends HookConsumerWidget {
if (ref if (ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.hasLaunchedFromIntent(selectedTabId)) { .hasLaunchedFromIntent(tabState?.id)) {
//Mark back as unhandled and navigator will pop //Mark back as unhandled and navigator will pop
await SystemNavigator.pop(); await SystemNavigator.pop();
return false; return false;
@@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart'; import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart'; import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
import 'package:lensai/features/geckoview/domain/providers.dart'; import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart'; import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
@@ -24,12 +25,8 @@ import 'package:lensai/utils/ui_helper.dart' as ui_helper;
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
class BrowserBottomAppBar extends HookConsumerWidget { class BrowserBottomAppBar extends HookConsumerWidget {
const BrowserBottomAppBar({ const BrowserBottomAppBar({required this.displayedSheet});
required this.selectedTabId,
required this.displayedSheet,
});
final String? selectedTabId;
final Sheet? displayedSheet; final Sheet? displayedSheet;
@override @override
@@ -39,17 +36,26 @@ class BrowserBottomAppBar extends HookConsumerWidget {
final tabMenuController = useMenuController(); final tabMenuController = useMenuController();
final trippleDotMenuController = useMenuController(); final trippleDotMenuController = useMenuController();
final selectedTabId = ref.watch(selectedTabProvider);
final isPrivateTab = ref.watch(
selectedTabStateProvider.select((state) => state?.isPrivate ?? false),
);
return BottomAppBar( return BottomAppBar(
height: AppBar().preferredSize.height, height: AppBar().preferredSize.height,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: AppBar( child: AppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
titleSpacing: 8.0, titleSpacing: 8.0,
backgroundColor:
(isPrivateTab && displayedSheet is! ViewTabsSheet)
? const Color(0x648000D7)
: null,
title: title:
(selectedTabId != null && displayedSheet is! ViewTabsSheet) (selectedTabId != null && displayedSheet is! ViewTabsSheet)
? HookConsumer( ? HookConsumer(
builder: (context, ref, child) { builder: (context, ref, child) {
final tabState = ref.watch(tabStateProvider(selectedTabId)); final tabState = ref.watch(selectedTabStateProvider);
final dragStartPosition = useRef(Offset.zero); final dragStartPosition = useRef(Offset.zero);
@@ -111,7 +117,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
onPressed: () async { onPressed: () async {
await ref await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.closeTab(selectedTabId!); .closeTab(selectedTabId);
}, },
leadingIcon: const Icon(Icons.close), leadingIcon: const Icon(Icons.close),
child: const Text('Close Tab'), child: const Text('Close Tab'),
@@ -374,7 +380,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
onPressed: () async { onPressed: () async {
await ref await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.closeTab(selectedTabId!); .closeTab(selectedTabId);
trippleDotMenuController.close(); trippleDotMenuController.close();
}, },
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
@@ -11,6 +11,7 @@ import 'package:lensai/core/providers/global_drop.dart';
import 'package:lensai/core/routing/routes.dart'; import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/data/models/drag_data.dart'; import 'package:lensai/data/models/drag_data.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart'; import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart'; import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
@@ -91,7 +92,7 @@ class _TabSheetHeader extends HookConsumerWidget {
// enableIMEPersonalizedLearning: !incognitoEnabled, // enableIMEPersonalizedLearning: !incognitoEnabled,
decoration: InputDecoration( decoration: InputDecoration(
// border: InputBorder.none, // border: InputBorder.none,
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(MdiIcons.tabSearch),
hintText: 'Search inside tabs...', hintText: 'Search inside tabs...',
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIcon: Row( suffixIcon: Row(
@@ -411,7 +412,11 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
), ),
child: FloatingActionButton.small( child: FloatingActionButton.small(
onPressed: () async { onPressed: () async {
await const SearchRoute(tabType: TabType.regular).push(context); final isCurrentPrivate =
ref.read(selectedTabStateProvider)?.isPrivate ?? false;
await SearchRoute(
tabType: isCurrentPrivate ? TabType.private : TabType.regular,
).push(context);
onClose(); onClose();
}, },
@@ -87,7 +87,22 @@ class TabPreview extends StatelessWidget {
), ),
if (tab.isPrivate) ...[ if (tab.isPrivate) ...[
const SizedBox(width: 6.0), const SizedBox(width: 6.0),
const Icon(MdiIcons.dominoMask, color: Color(0xFF8000D7)), const SizedBox(
height: 16,
width: 24,
child: Stack(
fit: StackFit.expand,
children: [
Positioned(
top: -4,
child: Icon(
MdiIcons.dominoMask,
color: Color(0xFF8000D7),
),
),
],
),
),
], ],
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
], ],
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/entities/states/readerable.dart'; import 'package:lensai/features/geckoview/domain/entities/states/readerable.dart';
@@ -27,59 +26,66 @@ class ReaderButton extends HookConsumerWidget {
), ),
); );
final icon = useMemoized( final icon =
() => readerabilityState.active
readerabilityState.active ? Icon(
? Icon( MdiIcons.bookOpen,
MdiIcons.bookOpen, color: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.primary, )
) : Icon(
: const Icon(MdiIcons.bookOpenOutline, color: Colors.white), MdiIcons.bookOpenOutline,
[readerabilityState.active], color: Theme.of(context).colorScheme.onSurface,
); );
return Visibility( return Visibility(
visible: visible:
readerabilityState.readerable && readerabilityState.readerable &&
(enableReadability || readerabilityState.active), (enableReadability || readerabilityState.active),
child: Padding( child: readerChanging.when(
padding: const EdgeInsets.symmetric(vertical: 15.0, horizontal: 8.0), skipLoadingOnReload: true,
child: readerChanging.when( data:
skipLoadingOnReload: true, (_) => Visibility(
data: visible: readerabilityState.readerable,
(_) => Visibility( child: InkWell(
visible: readerabilityState.readerable, onTap:
child: InkWell( readerChanging.isLoading
onTap: ? null
readerChanging.isLoading : () async {
? null await ref
: () async { .read(readerableScreenControllerProvider.notifier)
await ref .toggleReaderView(!readerabilityState.active);
.read( },
readerableScreenControllerProvider.notifier, child: Padding(
) padding: const EdgeInsets.symmetric(
.toggleReaderView(!readerabilityState.active); vertical: 15.0,
}, horizontal: 8.0,
),
child: icon, child: icon,
), ),
), ),
error: (error, stackTrace) => SizedBox.shrink(), ),
loading: error: (error, stackTrace) => SizedBox.shrink(),
() => AnimateGradientShader( loading:
duration: const Duration(milliseconds: 500), () => AnimateGradientShader(
primaryEnd: Alignment.bottomLeft, duration: const Duration(milliseconds: 500),
secondaryEnd: Alignment.topRight, primaryEnd: Alignment.bottomLeft,
primaryColors: [ secondaryEnd: Alignment.topRight,
colorScheme.primary, primaryColors: [
colorScheme.primaryContainer, colorScheme.primary,
], colorScheme.primaryContainer,
secondaryColors: [ ],
colorScheme.secondary, secondaryColors: [
colorScheme.secondaryContainer, colorScheme.secondary,
], colorScheme.secondaryContainer,
],
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 8.0,
),
child: icon, child: icon,
), ),
), ),
), ),
); );
} }
@@ -88,24 +88,33 @@ class SearchScreen extends HookConsumerWidget {
pinned: true, pinned: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: Align( title: Align(
child: SegmentedButton( child: Focus(
showSelectedIcon: false, canRequestFocus: false,
segments: const [ child: SegmentedButton(
ButtonSegment( showSelectedIcon: false,
value: TabType.regular, segments: const [
label: Text('Regular'), ButtonSegment(
icon: Icon(MdiIcons.tab), value: TabType.regular,
), label: Text('Regular'),
ButtonSegment( icon: Icon(MdiIcons.tab),
value: TabType.private, ),
label: Text('Private'), ButtonSegment(
icon: Icon(MdiIcons.tabUnselected), value: TabType.private,
), label: Text('Private'),
], icon: Icon(MdiIcons.tabUnselected),
selected: {selectedTabType.value}, ),
onSelectionChanged: (value) { ],
selectedTabType.value = value.first; selected: {selectedTabType.value},
}, onSelectionChanged: (value) {
selectedTabType.value = value.first;
},
style: switch (selectedTabType.value) {
TabType.regular => null,
TabType.private => SegmentedButton.styleFrom(
selectedBackgroundColor: const Color(0x648000D7),
),
},
),
), ),
), ),
bottom: PreferredSize( bottom: PreferredSize(
@@ -63,6 +63,8 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
builder: (context, controller) { builder: (context, controller) {
return ListView.builder( return ListView.builder(
controller: controller, controller: controller,
//Improve list performance by not rendering outside screen at all
cacheExtent: 0,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: items.length, itemCount: items.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {