decouple app bar related widgets from logic
This commit is contained in:
+137
-98
@@ -18,7 +18,6 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
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:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
@@ -27,6 +26,7 @@ import 'package:weblibre/core/design/app_colors.dart';
|
|||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
import 'package:weblibre/extensions/uri.dart';
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart';
|
||||||
@@ -35,14 +35,11 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||||
import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart';
|
import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart';
|
||||||
|
|
||||||
class CompactAppBarTitle extends HookConsumerWidget {
|
class CompactAppBarTitle extends ConsumerWidget {
|
||||||
const CompactAppBarTitle({super.key});
|
const CompactAppBarTitle({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
final appColors = AppColors.of(context);
|
|
||||||
|
|
||||||
final tabState = ref.watch(selectedTabStateProvider);
|
final tabState = ref.watch(selectedTabStateProvider);
|
||||||
final isTabTuneledAsync = ref.watch(isTabTunneledProvider(tabState?.id));
|
final isTabTuneledAsync = ref.watch(isTabTunneledProvider(tabState?.id));
|
||||||
final showSiteSettingsBadge = ref.watch(
|
final showSiteSettingsBadge = ref.watch(
|
||||||
@@ -53,42 +50,60 @@ class CompactAppBarTitle extends HookConsumerWidget {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final icon = useMemoized(() {
|
return CompactAppBarTitleView(
|
||||||
if (tabState.url.isHttp) {
|
tabState: tabState,
|
||||||
return Icon(
|
isTabTunneled:
|
||||||
MdiIcons.lockOff,
|
isTabTuneledAsync.hasValue && isTabTuneledAsync.value == true,
|
||||||
color: Theme.of(context).colorScheme.error,
|
showSiteSettingsBadge: showSiteSettingsBadge,
|
||||||
size: 16,
|
onSiteSettingsTap: () {
|
||||||
);
|
ref
|
||||||
} else if (tabState.readerableState.active) {
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
return const Icon(MdiIcons.lockMinus, size: 16);
|
.show(SiteSettingsSheet(tabState: tabState));
|
||||||
} else if (!tabState.securityInfoState.secure) {
|
},
|
||||||
return Icon(
|
onTitleTap: () async {
|
||||||
MdiIcons.lockAlert,
|
await SearchRoute(
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
tabId: tabState.id,
|
||||||
size: 16,
|
searchText: _searchTextForTab(tabState),
|
||||||
);
|
tabType: tabState.tabMode.toTabType(),
|
||||||
} else if (!tabState.isLoading) {
|
).push(context);
|
||||||
return const Icon(MdiIcons.lock, size: 16);
|
},
|
||||||
} else {
|
);
|
||||||
return const Icon(MdiIcons.timerSand, size: 16);
|
}
|
||||||
}
|
}
|
||||||
}, [tabState]);
|
|
||||||
|
class CompactAppBarTitleView extends StatelessWidget {
|
||||||
|
const CompactAppBarTitleView({
|
||||||
|
super.key,
|
||||||
|
required this.tabState,
|
||||||
|
required this.isTabTunneled,
|
||||||
|
required this.showSiteSettingsBadge,
|
||||||
|
required this.onSiteSettingsTap,
|
||||||
|
required this.onTitleTap,
|
||||||
|
this.tabIcon,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TabState tabState;
|
||||||
|
final bool isTabTunneled;
|
||||||
|
final bool showSiteSettingsBadge;
|
||||||
|
final VoidCallback onSiteSettingsTap;
|
||||||
|
final VoidCallback onTitleTap;
|
||||||
|
final Widget? tabIcon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final appColors = AppColors.of(context);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
ToolbarButton(
|
ToolbarButton(
|
||||||
onTap: () {
|
onTap: onSiteSettingsTap,
|
||||||
ref
|
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
|
||||||
.show(SiteSettingsSheet(tabState: tabState));
|
|
||||||
},
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
TabIcon(tabState: tabState, iconSize: 24),
|
tabIcon ?? TabIcon(tabState: tabState, iconSize: 24),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: -4,
|
top: -4,
|
||||||
right: -4,
|
right: -4,
|
||||||
@@ -106,19 +121,7 @@ class CompactAppBarTitle extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () async {
|
onTap: onTitleTap,
|
||||||
final searchText = tabState.url.scheme == 'about'
|
|
||||||
? ''
|
|
||||||
: tabState.url.toString();
|
|
||||||
|
|
||||||
await SearchRoute(
|
|
||||||
tabId: tabState.id,
|
|
||||||
searchText: searchText.isEmpty
|
|
||||||
? SearchRoute.emptySearchText
|
|
||||||
: searchText,
|
|
||||||
tabType: tabState.tabMode.toTabType(),
|
|
||||||
).push(context);
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -144,12 +147,11 @@ class CompactAppBarTitle extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
if (isTabTuneledAsync.hasValue &&
|
if (isTabTunneled) ...[
|
||||||
isTabTuneledAsync.value == true) ...[
|
|
||||||
const Icon(MdiIcons.tunnelOutline, size: 16),
|
const Icon(MdiIcons.tunnelOutline, size: 16),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
icon,
|
_SecurityStatusIcon(tabState: tabState, size: 16),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: UriBreadcrumb(
|
child: UriBreadcrumb(
|
||||||
@@ -170,14 +172,11 @@ class CompactAppBarTitle extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppBarTitle extends HookConsumerWidget {
|
class AppBarTitle extends ConsumerWidget {
|
||||||
const AppBarTitle({super.key});
|
const AppBarTitle({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
final appColors = AppColors.of(context);
|
|
||||||
|
|
||||||
final tabState = ref.watch(selectedTabStateProvider);
|
final tabState = ref.watch(selectedTabStateProvider);
|
||||||
final isTabTuneledAsync = ref.watch(isTabTunneledProvider(tabState?.id));
|
final isTabTuneledAsync = ref.watch(isTabTunneledProvider(tabState?.id));
|
||||||
final showSiteSettingsBadge = ref.watch(
|
final showSiteSettingsBadge = ref.watch(
|
||||||
@@ -188,42 +187,60 @@ class AppBarTitle extends HookConsumerWidget {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final icon = useMemoized(() {
|
return AppBarTitleView(
|
||||||
if (tabState.url.isHttp) {
|
tabState: tabState,
|
||||||
return Icon(
|
isTabTunneled:
|
||||||
MdiIcons.lockOff,
|
isTabTuneledAsync.hasValue && isTabTuneledAsync.value == true,
|
||||||
color: Theme.of(context).colorScheme.error,
|
showSiteSettingsBadge: showSiteSettingsBadge,
|
||||||
size: 14,
|
onSiteSettingsTap: () {
|
||||||
);
|
ref
|
||||||
} else if (tabState.readerableState.active) {
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
return const Icon(MdiIcons.lockMinus, size: 14);
|
.show(SiteSettingsSheet(tabState: tabState));
|
||||||
} else if (!tabState.securityInfoState.secure) {
|
},
|
||||||
return Icon(
|
onTitleTap: () async {
|
||||||
MdiIcons.lockAlert,
|
await SearchRoute(
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
tabId: tabState.id,
|
||||||
size: 14,
|
searchText: _searchTextForTab(tabState),
|
||||||
);
|
tabType: tabState.tabMode.toTabType(),
|
||||||
} else if (!tabState.isLoading) {
|
).push(context);
|
||||||
return const Icon(MdiIcons.lock, size: 14);
|
},
|
||||||
} else {
|
);
|
||||||
return const Icon(MdiIcons.timerSand, size: 14);
|
}
|
||||||
}
|
}
|
||||||
}, [tabState]);
|
|
||||||
|
class AppBarTitleView extends StatelessWidget {
|
||||||
|
const AppBarTitleView({
|
||||||
|
super.key,
|
||||||
|
required this.tabState,
|
||||||
|
required this.isTabTunneled,
|
||||||
|
required this.showSiteSettingsBadge,
|
||||||
|
required this.onSiteSettingsTap,
|
||||||
|
required this.onTitleTap,
|
||||||
|
this.tabIcon,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TabState tabState;
|
||||||
|
final bool isTabTunneled;
|
||||||
|
final bool showSiteSettingsBadge;
|
||||||
|
final VoidCallback onSiteSettingsTap;
|
||||||
|
final VoidCallback onTitleTap;
|
||||||
|
final Widget? tabIcon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final appColors = AppColors.of(context);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
ToolbarButton(
|
ToolbarButton(
|
||||||
onTap: () {
|
onTap: onSiteSettingsTap,
|
||||||
ref
|
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
|
||||||
.show(SiteSettingsSheet(tabState: tabState));
|
|
||||||
},
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
TabIcon(tabState: tabState, iconSize: 24),
|
tabIcon ?? TabIcon(tabState: tabState, iconSize: 24),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: -4,
|
top: -4,
|
||||||
right: -4,
|
right: -4,
|
||||||
@@ -239,23 +256,9 @@ class AppBarTitle extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Title/URL tap → opens search screen
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () async {
|
onTap: onTitleTap,
|
||||||
// Don't pre-fill for internal URLs
|
|
||||||
final searchText = tabState.url.scheme == 'about'
|
|
||||||
? ''
|
|
||||||
: tabState.url.toString();
|
|
||||||
|
|
||||||
await SearchRoute(
|
|
||||||
tabId: tabState.id,
|
|
||||||
searchText: searchText.isEmpty
|
|
||||||
? SearchRoute.emptySearchText
|
|
||||||
: searchText,
|
|
||||||
tabType: tabState.tabMode.toTabType(),
|
|
||||||
).push(context);
|
|
||||||
},
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -273,7 +276,6 @@ class AppBarTitle extends HookConsumerWidget {
|
|||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
// mode: TextScrollMode.bouncing,
|
|
||||||
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
|
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
|
||||||
delayBefore: const Duration(milliseconds: 500),
|
delayBefore: const Duration(milliseconds: 500),
|
||||||
pauseBetween: const Duration(milliseconds: 5000),
|
pauseBetween: const Duration(milliseconds: 5000),
|
||||||
@@ -302,12 +304,11 @@ class AppBarTitle extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
if (isTabTuneledAsync.hasValue &&
|
if (isTabTunneled) ...[
|
||||||
isTabTuneledAsync.value == true) ...[
|
|
||||||
const Icon(MdiIcons.tunnelOutline, size: 14),
|
const Icon(MdiIcons.tunnelOutline, size: 14),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
icon,
|
_SecurityStatusIcon(tabState: tabState, size: 14),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: UriBreadcrumb(
|
child: UriBreadcrumb(
|
||||||
@@ -328,3 +329,41 @@ class AppBarTitle extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _SecurityStatusIcon extends StatelessWidget {
|
||||||
|
const _SecurityStatusIcon({required this.tabState, required this.size});
|
||||||
|
|
||||||
|
final TabState tabState;
|
||||||
|
final double size;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (tabState.url.isHttp) {
|
||||||
|
return Icon(
|
||||||
|
MdiIcons.lockOff,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
size: size,
|
||||||
|
);
|
||||||
|
} else if (tabState.readerableState.active) {
|
||||||
|
return Icon(MdiIcons.lockMinus, size: size);
|
||||||
|
} else if (!tabState.securityInfoState.secure) {
|
||||||
|
return Icon(
|
||||||
|
MdiIcons.lockAlert,
|
||||||
|
color: Theme.of(context).colorScheme.errorContainer,
|
||||||
|
size: size,
|
||||||
|
);
|
||||||
|
} else if (!tabState.isLoading) {
|
||||||
|
return Icon(MdiIcons.lock, size: size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Icon(MdiIcons.timerSand, size: size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _searchTextForTab(TabState tabState) {
|
||||||
|
final searchText = tabState.url.scheme == 'about'
|
||||||
|
? ''
|
||||||
|
: tabState.url.toString();
|
||||||
|
|
||||||
|
return searchText.isEmpty ? SearchRoute.emptySearchText : searchText;
|
||||||
|
}
|
||||||
|
|||||||
+429
-245
@@ -27,12 +27,10 @@ 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:flutter_mozilla_components/flutter_mozilla_components.dart';
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
|
||||||
import 'package:weblibre/core/design/app_colors.dart';
|
import 'package:weblibre/core/design/app_colors.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
@@ -60,7 +58,7 @@ import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
|||||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||||
|
|
||||||
class BrowserTopAppBar extends HookConsumerWidget {
|
class BrowserTopAppBar extends StatelessWidget {
|
||||||
final bool showMainToolbar;
|
final bool showMainToolbar;
|
||||||
final bool showContextualToolbar;
|
final bool showContextualToolbar;
|
||||||
final bool showQuickTabSwitcherBar;
|
final bool showQuickTabSwitcherBar;
|
||||||
@@ -85,7 +83,7 @@ class BrowserTopAppBar extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: SizedBox(height: preferredSize.height, child: _tabBar),
|
child: SizedBox(height: preferredSize.height, child: _tabBar),
|
||||||
);
|
);
|
||||||
@@ -94,7 +92,7 @@ class BrowserTopAppBar extends HookConsumerWidget {
|
|||||||
Size get preferredSize => _size;
|
Size get preferredSize => _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BrowserBottomAppBar extends HookConsumerWidget {
|
class BrowserBottomAppBar extends StatelessWidget {
|
||||||
final bool showMainToolbar;
|
final bool showMainToolbar;
|
||||||
final bool showContextualToolbar;
|
final bool showContextualToolbar;
|
||||||
final bool showQuickTabSwitcherBar;
|
final bool showQuickTabSwitcherBar;
|
||||||
@@ -121,7 +119,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
final bottomPadding = MediaQuery.of(context).padding.bottom;
|
final bottomPadding = MediaQuery.of(context).padding.bottom;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
@@ -205,8 +203,97 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
|
|
||||||
final dragStartPosition = useRef(Offset.zero);
|
final dragStartPosition = useRef(Offset.zero);
|
||||||
|
|
||||||
return GestureDetector(
|
final showTabTitle =
|
||||||
// Tap handling moved to AppBarTitle for split icon/title behavior
|
selectedTabId != null && displayedSheet is! ViewTabsSheet;
|
||||||
|
|
||||||
|
final backgroundColor =
|
||||||
|
(settings.showContainerUi &&
|
||||||
|
containerColor != null &&
|
||||||
|
displayedSheet is! ViewTabsSheet)
|
||||||
|
? ContainerColors.forAppBar(containerColor)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return BrowserTabBarView(
|
||||||
|
showMainToolbar: showMainToolbar,
|
||||||
|
showContextualToolbar: showContextualToolbar,
|
||||||
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
|
displayAppBar: displayAppBar,
|
||||||
|
displayQuickTabSwitcher: displayQuickTabSwitcher,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
title: showTabTitle
|
||||||
|
? settings.tabBarLayout == TabBarLayout.compact
|
||||||
|
? const CompactAppBarTitle()
|
||||||
|
: const AppBarTitle()
|
||||||
|
: null,
|
||||||
|
actions: [
|
||||||
|
if (showTabTitle)
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final tabBarReaderView = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.tabBarReaderView,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final readerabilityStateActive = ref.watch(
|
||||||
|
selectedTabStateProvider.select(
|
||||||
|
(state) =>
|
||||||
|
(state?.readerableState ?? ReaderableState.$default())
|
||||||
|
.active,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Visibility(
|
||||||
|
visible: tabBarReaderView || readerabilityStateActive,
|
||||||
|
child: ReaderButton(
|
||||||
|
buttonBuilder: (isLoading, readerActive, icon) =>
|
||||||
|
ToolbarButton(
|
||||||
|
onTap: isLoading
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
readerableScreenControllerProvider
|
||||||
|
.notifier,
|
||||||
|
)
|
||||||
|
.toggleReaderView(!readerActive);
|
||||||
|
},
|
||||||
|
child: icon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (showExtensionShortcut)
|
||||||
|
ExtensionShortcutMenu(
|
||||||
|
controller: extensionMenuController,
|
||||||
|
child: ToolbarButton(
|
||||||
|
onTap: () {
|
||||||
|
if (extensionMenuController.isOpen) {
|
||||||
|
extensionMenuController.close();
|
||||||
|
} else {
|
||||||
|
extensionMenuController.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Icon(MdiIcons.puzzle),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showMainToolbarTabsCount)
|
||||||
|
TabsCountButton(
|
||||||
|
selectedTabId: selectedTabId,
|
||||||
|
displayedSheet: displayedSheet,
|
||||||
|
showLongPressMenu: true,
|
||||||
|
),
|
||||||
|
if (showMainToolbarNavigationButton)
|
||||||
|
NavigationMenuButton(selectedTabId: selectedTabId),
|
||||||
|
],
|
||||||
|
quickTabSwitcher: QuickTabSwitcher(
|
||||||
|
quickTabSwitcherMode: quickTabSwitcherMode,
|
||||||
|
),
|
||||||
|
contextualToolbar: ContextualToolbar(
|
||||||
|
selectedTabId: selectedTabId,
|
||||||
|
displayedSheet: displayedSheet,
|
||||||
|
),
|
||||||
onHorizontalDragStart: (details) {
|
onHorizontalDragStart: (details) {
|
||||||
dragStartPosition.value = details.globalPosition;
|
dragStartPosition.value = details.globalPosition;
|
||||||
},
|
},
|
||||||
@@ -262,6 +349,52 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
.dismiss();
|
.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrowserTabBarView extends StatelessWidget {
|
||||||
|
const BrowserTabBarView({
|
||||||
|
super.key,
|
||||||
|
required this.showMainToolbar,
|
||||||
|
required this.showContextualToolbar,
|
||||||
|
required this.showQuickTabSwitcherBar,
|
||||||
|
required this.displayAppBar,
|
||||||
|
required this.displayQuickTabSwitcher,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.title,
|
||||||
|
required this.actions,
|
||||||
|
required this.quickTabSwitcher,
|
||||||
|
required this.contextualToolbar,
|
||||||
|
this.onHorizontalDragStart,
|
||||||
|
this.onHorizontalDragEnd,
|
||||||
|
this.onVerticalDragStart,
|
||||||
|
this.onVerticalDragEnd,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool showMainToolbar;
|
||||||
|
final bool showContextualToolbar;
|
||||||
|
final bool showQuickTabSwitcherBar;
|
||||||
|
final bool displayAppBar;
|
||||||
|
final bool displayQuickTabSwitcher;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final Widget? title;
|
||||||
|
final List<Widget> actions;
|
||||||
|
final Widget quickTabSwitcher;
|
||||||
|
final Widget contextualToolbar;
|
||||||
|
final GestureDragStartCallback? onHorizontalDragStart;
|
||||||
|
final GestureDragEndCallback? onHorizontalDragEnd;
|
||||||
|
final GestureDragStartCallback? onVerticalDragStart;
|
||||||
|
final GestureDragEndCallback? onVerticalDragEnd;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
// Tap handling moved to AppBarTitle for split icon/title behavior
|
||||||
|
onHorizontalDragStart: onHorizontalDragStart,
|
||||||
|
onHorizontalDragEnd: onHorizontalDragEnd,
|
||||||
|
onVerticalDragStart: onVerticalDragStart,
|
||||||
|
onVerticalDragEnd: onVerticalDragEnd,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -269,9 +402,7 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
Visibility(
|
Visibility(
|
||||||
visible: displayQuickTabSwitcher,
|
visible: displayQuickTabSwitcher,
|
||||||
maintainState: true,
|
maintainState: true,
|
||||||
child: QuickTabSwitcher(
|
child: quickTabSwitcher,
|
||||||
quickTabSwitcherMode: quickTabSwitcherMode,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (showMainToolbar)
|
if (showMainToolbar)
|
||||||
Visibility(
|
Visibility(
|
||||||
@@ -283,95 +414,19 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
titleSpacing: 0.0,
|
titleSpacing: 0.0,
|
||||||
leadingWidth: 40.0,
|
leadingWidth: 40.0,
|
||||||
toolbarHeight: kToolbarHeight,
|
toolbarHeight: kToolbarHeight,
|
||||||
backgroundColor:
|
backgroundColor: backgroundColor,
|
||||||
(settings.showContainerUi &&
|
title: title,
|
||||||
containerColor != null &&
|
actions: actions,
|
||||||
displayedSheet is! ViewTabsSheet)
|
|
||||||
? ContainerColors.forAppBar(containerColor)
|
|
||||||
: null,
|
|
||||||
title:
|
|
||||||
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
|
||||||
? settings.tabBarLayout == TabBarLayout.compact
|
|
||||||
? const CompactAppBarTitle()
|
|
||||||
: const AppBarTitle()
|
|
||||||
: null,
|
|
||||||
actions: [
|
|
||||||
if (selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
|
||||||
Consumer(
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
final tabBarReaderView = ref.watch(
|
|
||||||
generalSettingsWithDefaultsProvider.select(
|
|
||||||
(value) => value.tabBarReaderView,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final readerabilityStateActive = ref.watch(
|
|
||||||
selectedTabStateProvider.select(
|
|
||||||
(state) =>
|
|
||||||
(state?.readerableState ??
|
|
||||||
ReaderableState.$default())
|
|
||||||
.active,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Visibility(
|
|
||||||
visible: tabBarReaderView || readerabilityStateActive,
|
|
||||||
child: ReaderButton(
|
|
||||||
buttonBuilder: (isLoading, readerActive, icon) =>
|
|
||||||
ToolbarButton(
|
|
||||||
onTap: isLoading
|
|
||||||
? null
|
|
||||||
: () async {
|
|
||||||
await ref
|
|
||||||
.read(
|
|
||||||
readerableScreenControllerProvider
|
|
||||||
.notifier,
|
|
||||||
)
|
|
||||||
.toggleReaderView(!readerActive);
|
|
||||||
},
|
|
||||||
child: icon,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (showExtensionShortcut)
|
|
||||||
ExtensionShortcutMenu(
|
|
||||||
controller: extensionMenuController,
|
|
||||||
child: ToolbarButton(
|
|
||||||
onTap: () {
|
|
||||||
if (extensionMenuController.isOpen) {
|
|
||||||
extensionMenuController.close();
|
|
||||||
} else {
|
|
||||||
extensionMenuController.open();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Icon(MdiIcons.puzzle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (showMainToolbarTabsCount)
|
|
||||||
TabsCountButton(
|
|
||||||
selectedTabId: selectedTabId,
|
|
||||||
displayedSheet: displayedSheet,
|
|
||||||
showLongPressMenu: true,
|
|
||||||
),
|
|
||||||
if (showMainToolbarNavigationButton)
|
|
||||||
NavigationMenuButton(selectedTabId: selectedTabId),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showContextualToolbar)
|
if (showContextualToolbar) contextualToolbar,
|
||||||
ContextualToolbar(
|
|
||||||
selectedTabId: selectedTabId,
|
|
||||||
displayedSheet: displayedSheet,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _QuickTabItem with FastEquatable {
|
class QuickTabSwitcherItem with FastEquatable {
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final String id;
|
final String id;
|
||||||
final TabMode tabMode;
|
final TabMode tabMode;
|
||||||
@@ -379,9 +434,9 @@ class _QuickTabItem with FastEquatable {
|
|||||||
final bool isPinned;
|
final bool isPinned;
|
||||||
final String title;
|
final String title;
|
||||||
final Uri url;
|
final Uri url;
|
||||||
final TabState? tabState;
|
final Widget avatar;
|
||||||
|
|
||||||
_QuickTabItem({
|
QuickTabSwitcherItem({
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.tabMode,
|
required this.tabMode,
|
||||||
@@ -389,7 +444,7 @@ class _QuickTabItem with FastEquatable {
|
|||||||
required this.isPinned,
|
required this.isPinned,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.url,
|
required this.url,
|
||||||
required this.tabState,
|
required this.avatar,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -401,7 +456,7 @@ class _QuickTabItem with FastEquatable {
|
|||||||
isPinned,
|
isPinned,
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
tabState,
|
avatar,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,35 +474,71 @@ class ContextualToolbar extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tabState = ref.watch(tabStateProvider(selectedTabId));
|
final tabState = ref.watch(tabStateProvider(selectedTabId));
|
||||||
|
|
||||||
|
return ContextualToolbarView(
|
||||||
|
canGoBack:
|
||||||
|
tabState?.historyState.canGoBack == true ||
|
||||||
|
tabState?.isLoading == true,
|
||||||
|
canGoForward: tabState?.historyState.canGoForward == true,
|
||||||
|
onBookmarksTap: () async {
|
||||||
|
await BookmarkListRoute(entryGuid: BookmarkRoot.root.id).push(context);
|
||||||
|
},
|
||||||
|
backButton: NavigateBackButton(
|
||||||
|
selectedTabId: selectedTabId,
|
||||||
|
isLoading: tabState?.isLoading ?? false,
|
||||||
|
),
|
||||||
|
forwardButton: NavigateForwardButton(selectedTabId: selectedTabId),
|
||||||
|
shareButton: ShareMenuButton(selectedTabId: selectedTabId),
|
||||||
|
addTabButton: const AddTabButton(),
|
||||||
|
tabsCountButton: TabsCountButton(
|
||||||
|
selectedTabId: selectedTabId,
|
||||||
|
displayedSheet: displayedSheet,
|
||||||
|
showLongPressMenu: false,
|
||||||
|
),
|
||||||
|
navigationButton: NavigationMenuButton(selectedTabId: selectedTabId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContextualToolbarView extends StatelessWidget {
|
||||||
|
const ContextualToolbarView({
|
||||||
|
super.key,
|
||||||
|
required this.canGoBack,
|
||||||
|
required this.canGoForward,
|
||||||
|
required this.onBookmarksTap,
|
||||||
|
required this.backButton,
|
||||||
|
required this.forwardButton,
|
||||||
|
required this.shareButton,
|
||||||
|
required this.addTabButton,
|
||||||
|
required this.tabsCountButton,
|
||||||
|
required this.navigationButton,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool canGoBack;
|
||||||
|
final bool canGoForward;
|
||||||
|
final VoidCallback onBookmarksTap;
|
||||||
|
final Widget backButton;
|
||||||
|
final Widget forwardButton;
|
||||||
|
final Widget shareButton;
|
||||||
|
final Widget addTabButton;
|
||||||
|
final Widget tabsCountButton;
|
||||||
|
final Widget navigationButton;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
if (tabState?.historyState.canGoBack == true ||
|
if (canGoBack)
|
||||||
tabState?.isLoading == true)
|
backButton
|
||||||
NavigateBackButton(
|
|
||||||
selectedTabId: selectedTabId,
|
|
||||||
isLoading: tabState?.isLoading ?? false,
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: onBookmarksTap,
|
||||||
await BookmarkListRoute(
|
|
||||||
entryGuid: BookmarkRoot.root.id,
|
|
||||||
).push(context);
|
|
||||||
},
|
|
||||||
icon: const Icon(MdiIcons.bookmarkMultiple),
|
icon: const Icon(MdiIcons.bookmarkMultiple),
|
||||||
),
|
),
|
||||||
if (tabState?.historyState.canGoForward == true)
|
if (canGoForward) forwardButton else shareButton,
|
||||||
NavigateForwardButton(selectedTabId: selectedTabId)
|
addTabButton,
|
||||||
else
|
tabsCountButton,
|
||||||
ShareMenuButton(selectedTabId: selectedTabId),
|
navigationButton,
|
||||||
const AddTabButton(),
|
|
||||||
TabsCountButton(
|
|
||||||
selectedTabId: selectedTabId,
|
|
||||||
displayedSheet: displayedSheet,
|
|
||||||
showLongPressMenu: false,
|
|
||||||
),
|
|
||||||
NavigationMenuButton(selectedTabId: selectedTabId),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -460,7 +551,6 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final appColors = AppColors.of(context);
|
|
||||||
final showIsolatedTabUi = ref.watch(
|
final showIsolatedTabUi = ref.watch(
|
||||||
generalSettingsWithDefaultsProvider.select((s) => s.showIsolatedTabUi),
|
generalSettingsWithDefaultsProvider.select((s) => s.showIsolatedTabUi),
|
||||||
);
|
);
|
||||||
@@ -484,8 +574,8 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
|||||||
.watch(quickTabSwitcherHistorySuggestionsProvider(quickTabSwitcherMode))
|
.watch(quickTabSwitcherHistorySuggestionsProvider(quickTabSwitcherMode))
|
||||||
.value;
|
.value;
|
||||||
final availableItems = tabStates.value
|
final availableItems = tabStates.value
|
||||||
.map<_QuickTabItem>(
|
.map<QuickTabSwitcherItem>(
|
||||||
(state) => _QuickTabItem(
|
(state) => QuickTabSwitcherItem(
|
||||||
id: state.$1.id,
|
id: state.$1.id,
|
||||||
title: state.$1.titleOrAuthority,
|
title: state.$1.titleOrAuthority,
|
||||||
tabMode: state.$1.tabMode,
|
tabMode: state.$1.tabMode,
|
||||||
@@ -493,14 +583,14 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
|||||||
isPinned: pinnedTabIds?.contains(state.$1.id) ?? false,
|
isPinned: pinnedTabIds?.contains(state.$1.id) ?? false,
|
||||||
url: state.$1.url,
|
url: state.$1.url,
|
||||||
color: state.$2?.color,
|
color: state.$2?.color,
|
||||||
tabState: state.$1,
|
avatar: TabIcon(tabState: state.$1, iconSize: 20),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.followedBy(
|
.followedBy(
|
||||||
(historySuggestions ?? []).map<_QuickTabItem>((state) {
|
(historySuggestions ?? []).map<QuickTabSwitcherItem>((state) {
|
||||||
final url = Uri.parse(state.url);
|
final url = Uri.parse(state.url);
|
||||||
|
|
||||||
return _QuickTabItem(
|
return QuickTabSwitcherItem(
|
||||||
id: state.url,
|
id: state.url,
|
||||||
title: state.title ?? url.authority,
|
title: state.title ?? url.authority,
|
||||||
tabMode: TabMode.regular,
|
tabMode: TabMode.regular,
|
||||||
@@ -508,136 +598,164 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
|||||||
isPinned: false,
|
isPinned: false,
|
||||||
url: url,
|
url: url,
|
||||||
color: null,
|
color: null,
|
||||||
tabState: null,
|
avatar: UrlIcon([url], iconSize: 20),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
final chipScrollController = useScrollController();
|
||||||
|
|
||||||
|
return QuickTabSwitcherView(
|
||||||
|
availableItems: availableItems,
|
||||||
|
scrollController: chipScrollController,
|
||||||
|
showTitles: showTitles,
|
||||||
|
showIsolatedTabUi: showIsolatedTabUi,
|
||||||
|
onSelected: (item) async {
|
||||||
|
final animation = chipScrollController.animateTo(
|
||||||
|
0,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
);
|
||||||
|
if (item.isHistory) {
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(url: item.url, tabMode: TabMode.regular, selectTab: true);
|
||||||
|
} else {
|
||||||
|
await ref.read(tabRepositoryProvider.notifier).selectTab(item.id);
|
||||||
|
}
|
||||||
|
await animation;
|
||||||
|
},
|
||||||
|
itemWrapBuilder: (child, item) {
|
||||||
|
if (item.isHistory) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TabMenu(
|
||||||
|
selectedTabId: item.id,
|
||||||
|
enableFindInPage: false,
|
||||||
|
enableFetchFeeds: false,
|
||||||
|
enableDesktopMode: false,
|
||||||
|
enableReaderMode: false,
|
||||||
|
enableReloadButton: false,
|
||||||
|
enableNavigationButtons: false,
|
||||||
|
enableAddToHomeScreen: false,
|
||||||
|
enablePinTab: effectiveMode == QuickTabSwitcherMode.containerTabs,
|
||||||
|
builder: (context, controller, _) {
|
||||||
|
return InkWell(
|
||||||
|
onLongPress: () {
|
||||||
|
if (controller.isOpen) {
|
||||||
|
controller.close();
|
||||||
|
} else {
|
||||||
|
controller.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuickTabSwitcherView extends StatelessWidget {
|
||||||
|
const QuickTabSwitcherView({
|
||||||
|
super.key,
|
||||||
|
required this.availableItems,
|
||||||
|
required this.scrollController,
|
||||||
|
required this.showTitles,
|
||||||
|
required this.showIsolatedTabUi,
|
||||||
|
required this.onSelected,
|
||||||
|
required this.itemWrapBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<QuickTabSwitcherItem> availableItems;
|
||||||
|
final ScrollController scrollController;
|
||||||
|
final bool showTitles;
|
||||||
|
final bool showIsolatedTabUi;
|
||||||
|
final Future<void> Function(QuickTabSwitcherItem item) onSelected;
|
||||||
|
final Widget Function(Widget child, QuickTabSwitcherItem item)
|
||||||
|
itemWrapBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final appColors = AppColors.of(context);
|
||||||
|
|
||||||
if (availableItems.isEmpty) {
|
if (availableItems.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final chipScrollController = useScrollController();
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 48,
|
height: 48,
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
child: SelectableChips<_QuickTabItem, _QuickTabItem, String>(
|
child:
|
||||||
enableDelete: false,
|
SelectableChips<QuickTabSwitcherItem, QuickTabSwitcherItem, String>(
|
||||||
scrollController: chipScrollController,
|
enableDelete: false,
|
||||||
itemId: (item) => item.id,
|
scrollController: scrollController,
|
||||||
labelPadding: (item) =>
|
itemId: (item) => item.id,
|
||||||
(!showTitles &&
|
labelPadding: (item) =>
|
||||||
!item.isHistory &&
|
(!showTitles &&
|
||||||
!item.isPinned &&
|
!item.isHistory &&
|
||||||
item.tabMode is! PrivateTabMode &&
|
!item.isPinned &&
|
||||||
item.tabMode is! IsolatedTabMode)
|
item.tabMode is! PrivateTabMode &&
|
||||||
? EdgeInsets.zero
|
item.tabMode is! IsolatedTabMode)
|
||||||
: null,
|
? EdgeInsets.zero
|
||||||
itemLabel: (item) {
|
: null,
|
||||||
return Row(
|
itemLabel: (item) {
|
||||||
mainAxisSize: MainAxisSize.min,
|
return Row(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
if (item.isHistory || showTitles)
|
children: [
|
||||||
ConstrainedBox(
|
if (item.isHistory || showTitles)
|
||||||
constraints: const BoxConstraints(maxWidth: 64),
|
ConstrainedBox(
|
||||||
child: Text(item.title),
|
constraints: const BoxConstraints(maxWidth: 64),
|
||||||
),
|
child: Text(item.title),
|
||||||
if (showIsolatedTabUi && item.tabMode is IsolatedTabMode)
|
),
|
||||||
Padding(
|
if (showIsolatedTabUi && item.tabMode is IsolatedTabMode)
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
Padding(
|
||||||
child: Icon(
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
MdiIcons.snowflake,
|
child: Icon(
|
||||||
color: appColors.isolatedTabTeal,
|
MdiIcons.snowflake,
|
||||||
size: 20,
|
color: appColors.isolatedTabTeal,
|
||||||
),
|
size: 20,
|
||||||
)
|
),
|
||||||
else if (item.tabMode is PrivateTabMode)
|
)
|
||||||
Padding(
|
else if (item.tabMode is PrivateTabMode)
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
Padding(
|
||||||
child: Icon(
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
MdiIcons.dominoMask,
|
child: Icon(
|
||||||
color: appColors.privateTabPurple,
|
MdiIcons.dominoMask,
|
||||||
size: 20,
|
color: appColors.privateTabPurple,
|
||||||
),
|
size: 20,
|
||||||
),
|
),
|
||||||
if (item.isPinned)
|
),
|
||||||
Padding(
|
if (item.isPinned)
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
Padding(
|
||||||
child: Icon(
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
MdiIcons.pin,
|
child: Icon(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
MdiIcons.pin,
|
||||||
size: 20,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
size: 20,
|
||||||
),
|
),
|
||||||
if (item.isHistory)
|
),
|
||||||
const Padding(
|
if (item.isHistory)
|
||||||
padding: EdgeInsets.only(left: 8.0),
|
const Padding(
|
||||||
child: Icon(MdiIcons.history, size: 20),
|
padding: EdgeInsets.only(left: 8.0),
|
||||||
),
|
child: Icon(MdiIcons.history, size: 20),
|
||||||
],
|
),
|
||||||
);
|
],
|
||||||
},
|
|
||||||
itemAvatar: (item) =>
|
|
||||||
item.tabState.mapNotNull(
|
|
||||||
(tabState) => TabIcon(tabState: tabState, iconSize: 20),
|
|
||||||
) ??
|
|
||||||
UrlIcon([item.url], iconSize: 20),
|
|
||||||
itemBackgroundColor: (item) =>
|
|
||||||
item.color != null ? ContainerColors.forChip(item.color!) : null,
|
|
||||||
onSelected: (item) async {
|
|
||||||
final animation = chipScrollController.animateTo(
|
|
||||||
0,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
curve: Curves.easeOutBack,
|
|
||||||
);
|
|
||||||
if (item.isHistory) {
|
|
||||||
await ref
|
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.addTab(
|
|
||||||
url: item.url,
|
|
||||||
tabMode: TabMode.regular,
|
|
||||||
selectTab: true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await ref.read(tabRepositoryProvider.notifier).selectTab(item.id);
|
|
||||||
}
|
|
||||||
await animation;
|
|
||||||
},
|
|
||||||
itemWrap: (child, item) {
|
|
||||||
if (item.isHistory) {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TabMenu(
|
|
||||||
selectedTabId: item.id,
|
|
||||||
enableFindInPage: false,
|
|
||||||
enableFetchFeeds: false,
|
|
||||||
enableDesktopMode: false,
|
|
||||||
enableReaderMode: false,
|
|
||||||
enableReloadButton: false,
|
|
||||||
enableNavigationButtons: false,
|
|
||||||
enableAddToHomeScreen: false,
|
|
||||||
enablePinTab: effectiveMode == QuickTabSwitcherMode.containerTabs,
|
|
||||||
builder: (context, controller, _) {
|
|
||||||
return InkWell(
|
|
||||||
onLongPress: () {
|
|
||||||
if (controller.isOpen) {
|
|
||||||
controller.close();
|
|
||||||
} else {
|
|
||||||
controller.open();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: child,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
itemAvatar: (item) => item.avatar,
|
||||||
},
|
itemBackgroundColor: (item) => item.color != null
|
||||||
availableItems: availableItems,
|
? ContainerColors.forChip(item.color!)
|
||||||
),
|
: null,
|
||||||
|
onSelected: onSelected,
|
||||||
|
itemWrap: itemWrapBuilder,
|
||||||
|
availableItems: availableItems,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -650,18 +768,28 @@ class ShareMenuButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IconButton(
|
return ShareMenuButtonView(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final tabId = selectedTabId;
|
final tabId = selectedTabId;
|
||||||
if (tabId != null) {
|
if (tabId != null) {
|
||||||
await showShareBottomSheet(context, selectedTabId: tabId);
|
await showShareBottomSheet(context, selectedTabId: tabId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.share),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ShareMenuButtonView extends StatelessWidget {
|
||||||
|
const ShareMenuButtonView({super.key, this.onPressed});
|
||||||
|
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(onPressed: onPressed, icon: const Icon(Icons.share));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NavigationMenuButton extends StatelessWidget {
|
class NavigationMenuButton extends StatelessWidget {
|
||||||
final String? selectedTabId;
|
final String? selectedTabId;
|
||||||
|
|
||||||
@@ -669,15 +797,25 @@ class NavigationMenuButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ToolbarButton(
|
return NavigationMenuButtonView(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await showBrowserMenuSheet(context);
|
await showBrowserMenuSheet(context);
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.more_vert),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NavigationMenuButtonView extends StatelessWidget {
|
||||||
|
const NavigationMenuButtonView({super.key, this.onTap});
|
||||||
|
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ToolbarButton(onTap: onTap, child: const Icon(Icons.more_vert));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AddTabButton extends HookConsumerWidget {
|
class AddTabButton extends HookConsumerWidget {
|
||||||
const AddTabButton({super.key});
|
const AddTabButton({super.key});
|
||||||
|
|
||||||
@@ -687,7 +825,7 @@ class AddTabButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
return TabCreationMenu(
|
return TabCreationMenu(
|
||||||
controller: tabMenuController,
|
controller: tabMenuController,
|
||||||
child: IconButton(
|
child: AddTabButtonView(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final settings = ref.read(generalSettingsWithDefaultsProvider);
|
final settings = ref.read(generalSettingsWithDefaultsProvider);
|
||||||
|
|
||||||
@@ -701,7 +839,6 @@ class AddTabButton extends HookConsumerWidget {
|
|||||||
const BrowserRoute().go(context);
|
const BrowserRoute().go(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(MdiIcons.tabPlus),
|
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (tabMenuController.isOpen) {
|
if (tabMenuController.isOpen) {
|
||||||
tabMenuController.close();
|
tabMenuController.close();
|
||||||
@@ -714,6 +851,53 @@ class AddTabButton extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AddTabButtonView extends StatelessWidget {
|
||||||
|
const AddTabButtonView({super.key, this.onPressed, this.onLongPress});
|
||||||
|
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final VoidCallback? onLongPress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: const Icon(MdiIcons.tabPlus),
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TabsCountButtonView extends StatelessWidget {
|
||||||
|
const TabsCountButtonView({
|
||||||
|
super.key,
|
||||||
|
required this.isActive,
|
||||||
|
required this.onTap,
|
||||||
|
this.onLongPress,
|
||||||
|
this.buttonBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isActive;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final VoidCallback? onLongPress;
|
||||||
|
final Widget Function(
|
||||||
|
bool isActive,
|
||||||
|
VoidCallback onTap,
|
||||||
|
VoidCallback? onLongPress,
|
||||||
|
)?
|
||||||
|
buttonBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return (buttonBuilder != null)
|
||||||
|
? buttonBuilder!(isActive, onTap, onLongPress)
|
||||||
|
: TabsActionButton(
|
||||||
|
isActive: isActive,
|
||||||
|
onTap: onTap,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TabsCountButton extends HookConsumerWidget {
|
class TabsCountButton extends HookConsumerWidget {
|
||||||
const TabsCountButton({
|
const TabsCountButton({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -732,7 +916,7 @@ class TabsCountButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
return TabCreationMenu(
|
return TabCreationMenu(
|
||||||
controller: tabMenuController,
|
controller: tabMenuController,
|
||||||
child: TabsActionButton(
|
child: TabsCountButtonView(
|
||||||
isActive: displayedSheet is ViewTabsSheet,
|
isActive: displayedSheet is ViewTabsSheet,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final tabViewBottomSheet = ref
|
final tabViewBottomSheet = ref
|
||||||
|
|||||||
+88
-51
@@ -25,6 +25,52 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
|||||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||||
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||||
|
|
||||||
|
class NavigateForwardButtonView extends StatelessWidget {
|
||||||
|
const NavigateForwardButtonView({
|
||||||
|
super.key,
|
||||||
|
required this.canGoForward,
|
||||||
|
this.onPressed,
|
||||||
|
this.onLongPress,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool canGoForward;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final VoidCallback? onLongPress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: canGoForward ? onPressed : null,
|
||||||
|
onLongPress: canGoForward ? onLongPress : null,
|
||||||
|
icon: const Icon(Icons.arrow_forward),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NavigateBackButtonView extends StatelessWidget {
|
||||||
|
const NavigateBackButtonView({
|
||||||
|
super.key,
|
||||||
|
required this.canGoBack,
|
||||||
|
required this.isLoading,
|
||||||
|
this.onPressed,
|
||||||
|
this.onLongPress,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool canGoBack;
|
||||||
|
final bool isLoading;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final VoidCallback? onLongPress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: (canGoBack || isLoading) ? onPressed : null,
|
||||||
|
onLongPress: (canGoBack && !isLoading) ? onLongPress : null,
|
||||||
|
icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NavigateForwardButton extends HookConsumerWidget {
|
class NavigateForwardButton extends HookConsumerWidget {
|
||||||
const NavigateForwardButton({
|
const NavigateForwardButton({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -45,25 +91,21 @@ class NavigateForwardButton extends HookConsumerWidget {
|
|||||||
selectedTabId: selectedTabId,
|
selectedTabId: selectedTabId,
|
||||||
controller: historyMenuController,
|
controller: historyMenuController,
|
||||||
direction: HistoryMenuDirection.forward,
|
direction: HistoryMenuDirection.forward,
|
||||||
child: IconButton(
|
child: NavigateForwardButtonView(
|
||||||
onPressed: canGoForward
|
canGoForward: canGoForward,
|
||||||
? () async {
|
onPressed: () async {
|
||||||
final controller = ref.read(
|
final controller = ref.read(
|
||||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
await controller.goForward();
|
await controller.goForward();
|
||||||
menuControllerToClose?.close();
|
menuControllerToClose?.close();
|
||||||
}
|
},
|
||||||
: null,
|
onLongPress: () {
|
||||||
onLongPress: canGoForward
|
if (!historyMenuController.isOpen) {
|
||||||
? () {
|
historyMenuController.open();
|
||||||
if (!historyMenuController.isOpen) {
|
}
|
||||||
historyMenuController.open();
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
icon: const Icon(Icons.arrow_forward),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -91,42 +133,37 @@ class NavigateBackButton extends HookConsumerWidget {
|
|||||||
selectedTabId: selectedTabId,
|
selectedTabId: selectedTabId,
|
||||||
controller: historyMenuController,
|
controller: historyMenuController,
|
||||||
direction: HistoryMenuDirection.back,
|
direction: HistoryMenuDirection.back,
|
||||||
child: IconButton(
|
child: NavigateBackButtonView(
|
||||||
onPressed: (canGoBack || isLoading)
|
canGoBack: canGoBack,
|
||||||
? () async {
|
isLoading: isLoading,
|
||||||
final controller = ref.read(
|
onPressed: () async {
|
||||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
final controller = ref.read(
|
||||||
);
|
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
final isReaderActive = ref.read(
|
final isReaderActive = ref.read(
|
||||||
selectedTabStateProvider.select(
|
selectedTabStateProvider.select(
|
||||||
(state) => state?.readerableState.active ?? false,
|
(state) => state?.readerableState.active ?? false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
await controller.stopLoading();
|
await controller.stopLoading();
|
||||||
} else if (isReaderActive) {
|
} else if (isReaderActive) {
|
||||||
await ref
|
await ref
|
||||||
.read(readerableScreenControllerProvider.notifier)
|
.read(readerableScreenControllerProvider.notifier)
|
||||||
.toggleReaderView(false);
|
.toggleReaderView(false);
|
||||||
} else {
|
} else {
|
||||||
await controller.goBack();
|
await controller.goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
menuControllerToClose?.close();
|
menuControllerToClose?.close();
|
||||||
}
|
},
|
||||||
: null,
|
onLongPress: () {
|
||||||
onLongPress: (canGoBack && !isLoading)
|
if (!historyMenuController.isOpen) {
|
||||||
? () {
|
historyMenuController.open();
|
||||||
if (!historyMenuController.isOpen) {
|
}
|
||||||
historyMenuController.open();
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
icon: isLoading
|
|
||||||
? const Icon(Icons.close)
|
|
||||||
: const Icon(Icons.arrow_back),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-63
@@ -26,6 +26,56 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/containe
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||||
|
|
||||||
|
class TabsActionButtonView extends StatelessWidget {
|
||||||
|
const TabsActionButtonView({
|
||||||
|
super.key,
|
||||||
|
required this.isActive,
|
||||||
|
required this.tabCountText,
|
||||||
|
this.showSkeleton = false,
|
||||||
|
this.onTap,
|
||||||
|
this.onDoubleTap,
|
||||||
|
this.onLongPress,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isActive;
|
||||||
|
final String tabCountText;
|
||||||
|
final bool showSkeleton;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
final VoidCallback? onDoubleTap;
|
||||||
|
final VoidCallback? onLongPress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final iconColor = isActive
|
||||||
|
? theme.colorScheme.primary
|
||||||
|
: theme.colorScheme.onSurfaceVariant;
|
||||||
|
|
||||||
|
final text = Text(
|
||||||
|
tabCountText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: iconColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return ToolbarButton(
|
||||||
|
onTap: onTap,
|
||||||
|
onDoubleTap: onDoubleTap,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(width: 2.0, color: iconColor),
|
||||||
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(minWidth: 25.0),
|
||||||
|
child: Center(child: showSkeleton ? Skeletonizer(child: text) : text),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TabsActionButton extends HookConsumerWidget {
|
class TabsActionButton extends HookConsumerWidget {
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
@@ -42,78 +92,32 @@ class TabsActionButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
final tabCount = isActive
|
final tabCount = isActive
|
||||||
// ignore: provider_parameters
|
// ignore: provider_parameters
|
||||||
? ref.watch(containerTabCountProvider(ContainerFilterDisabled()))
|
? ref.watch(containerTabCountProvider(ContainerFilterDisabled()))
|
||||||
: ref.watch(selectedContainerTabCountProvider);
|
: ref.watch(selectedContainerTabCountProvider);
|
||||||
|
|
||||||
final iconColor = isActive
|
final tabCountText = tabCount.when(
|
||||||
? theme.colorScheme.primary
|
skipLoadingOnReload: true,
|
||||||
: theme.colorScheme.onSurfaceVariant;
|
data: (count) => count.toString(),
|
||||||
|
loading: () => tabCount.hasValue ? tabCount.value.toString() : '0',
|
||||||
|
error: (error, stackTrace) {
|
||||||
|
logger.e(
|
||||||
|
'Could not determine tab count',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
return '-1';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return ToolbarButton(
|
return TabsActionButtonView(
|
||||||
|
isActive: isActive,
|
||||||
|
tabCountText: tabCountText,
|
||||||
|
showSkeleton: tabCount.isLoading && !tabCount.hasValue,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onDoubleTap: onDoubleTap,
|
onDoubleTap: onDoubleTap,
|
||||||
onLongPress: onLongPress,
|
onLongPress: onLongPress,
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(width: 2.0, color: iconColor),
|
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
|
||||||
),
|
|
||||||
constraints: const BoxConstraints(minWidth: 25.0),
|
|
||||||
child: Center(
|
|
||||||
child: tabCount.when(
|
|
||||||
skipLoadingOnReload: true,
|
|
||||||
data: (count) {
|
|
||||||
return Text(
|
|
||||||
count.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14.0,
|
|
||||||
color: iconColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loading: () => (tabCount.hasValue)
|
|
||||||
? Text(
|
|
||||||
tabCount.value.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14.0,
|
|
||||||
color: iconColor,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Skeletonizer(
|
|
||||||
child: Text(
|
|
||||||
'0',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14.0,
|
|
||||||
color: iconColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
error: (error, stackTrace) {
|
|
||||||
logger.e(
|
|
||||||
'Could not determine tab count',
|
|
||||||
error: error,
|
|
||||||
stackTrace: stackTrace,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Text(
|
|
||||||
'-1',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14.0,
|
|
||||||
color: iconColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user