new setting to define tab bar swipe action
This commit is contained in:
@@ -7,7 +7,7 @@ part of 'providers.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$selectionActionServiceHash() =>
|
||||
r'b1deac5e0566443b4729656c7082903b529bc269';
|
||||
r'c89d981c2ba253ed940887fa59c11ad93d8589a8';
|
||||
|
||||
/// See also [selectionActionService].
|
||||
@ProviderFor(selectionActionService)
|
||||
|
||||
@@ -48,7 +48,6 @@ part 'tab.g.dart';
|
||||
class TabRepository extends _$TabRepository {
|
||||
final _tabsService = GeckoTabService();
|
||||
|
||||
String? _previousTabId;
|
||||
final _tabFromIntent = <String>{};
|
||||
|
||||
bool hasLaunchedFromIntent(String? tabId) {
|
||||
@@ -124,9 +123,40 @@ class TabRepository extends _$TabRepository {
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> selectPreviousTab() async {
|
||||
if (_previousTabId != null) {
|
||||
return selectTab(_previousTabId!);
|
||||
Future<bool> selectPreviouslyOpenedTab(String tabId) async {
|
||||
final previousTabId = await ref
|
||||
.read(tabDatabaseProvider)
|
||||
.previousTabByTimestamp(tabId: tabId)
|
||||
.getSingleOrNull();
|
||||
|
||||
if (previousTabId != null) {
|
||||
return selectTab(previousTabId);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> selectPreviousTab(String tabId) async {
|
||||
final previousTabId = await ref
|
||||
.read(tabDatabaseProvider)
|
||||
.previousTabByOrderKey(tabId: tabId)
|
||||
.getSingleOrNull();
|
||||
|
||||
if (previousTabId != null) {
|
||||
return selectTab(previousTabId);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> selectNextTab(String tabId) async {
|
||||
final previousTabId = await ref
|
||||
.read(tabDatabaseProvider)
|
||||
.nextTabByOrderKey(tabId: tabId)
|
||||
.getSingleOrNull();
|
||||
|
||||
if (previousTabId != null) {
|
||||
return selectTab(previousTabId);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -283,7 +313,6 @@ class TabRepository extends _$TabRepository {
|
||||
selectedTabProvider,
|
||||
(previous, tabId) async {
|
||||
if (tabId != null) {
|
||||
_previousTabId = previous;
|
||||
await db.tabDao.touchTab(tabId, timestamp: DateTime.now());
|
||||
}
|
||||
},
|
||||
@@ -303,10 +332,6 @@ class TabRepository extends _$TabRepository {
|
||||
final syncTabs =
|
||||
next.value.isNotEmpty || (previous?.value.isNotEmpty ?? false);
|
||||
|
||||
if (_previousTabId != null && !next.value.contains(_previousTabId)) {
|
||||
_previousTabId = null;
|
||||
}
|
||||
|
||||
if (syncTabs) {
|
||||
await db.tabDao.syncTabs(retainTabIds: next.value);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabRepositoryHash() => r'3465f351f1d36e0f21b4e8ef4ebe7fdc4fbc0b75';
|
||||
String _$tabRepositoryHash() => r'4f562c1456eadcda7986ea2048667dd083f37618';
|
||||
|
||||
/// See also [TabRepository].
|
||||
@ProviderFor(TabRepository)
|
||||
|
||||
+24
-1
@@ -42,6 +42,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||
import 'package:weblibre/presentation/icons/tor_icons.dart';
|
||||
@@ -93,7 +94,29 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
final distance = dragStartPosition.value - details.globalPosition;
|
||||
|
||||
if (distance.dx.abs() > 50) {
|
||||
await ref.read(tabRepositoryProvider.notifier).selectPreviousTab();
|
||||
final selectedTab = ref.read(selectedTabProvider);
|
||||
final setting = await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.fetchSettings();
|
||||
|
||||
if (selectedTab != null) {
|
||||
switch (setting.tabBarSwipeAction) {
|
||||
case TabBarSwipeAction.switchLastOpened:
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectPreviouslyOpenedTab(selectedTab);
|
||||
case TabBarSwipeAction.navigateOrderedTabs:
|
||||
if (distance.dx < 0) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectPreviousTab(selectedTab);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectNextTab(selectedTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
child: AppBar(
|
||||
|
||||
@@ -240,4 +240,34 @@ unorderedTabDescendants:
|
||||
JOIN descendants d ON t.parent_id = d.id
|
||||
)
|
||||
SELECT id, parent_id
|
||||
FROM descendants;
|
||||
FROM descendants;
|
||||
|
||||
previousTabByTimestamp:
|
||||
WITH ranked_tabs AS (
|
||||
SELECT id, timestamp,
|
||||
LAG(id) OVER (ORDER BY timestamp) as prev_tab_id
|
||||
FROM tab
|
||||
)
|
||||
SELECT prev_tab_id
|
||||
FROM ranked_tabs
|
||||
WHERE id = :tab_id;
|
||||
|
||||
previousTabByOrderKey:
|
||||
WITH ranked_tabs AS (
|
||||
SELECT id, order_key,
|
||||
LAG(id) OVER (ORDER BY order_key) as prev_tab_id
|
||||
FROM tab
|
||||
)
|
||||
SELECT prev_tab_id
|
||||
FROM ranked_tabs
|
||||
WHERE id = :tab_id;
|
||||
|
||||
nextTabByOrderKey:
|
||||
WITH ranked_tabs AS (
|
||||
SELECT id, order_key,
|
||||
LEAD(id) OVER (ORDER BY order_key) as next_tab_id
|
||||
FROM tab
|
||||
)
|
||||
SELECT next_tab_id
|
||||
FROM ranked_tabs
|
||||
WHERE id = :tab_id;
|
||||
|
||||
@@ -1256,6 +1256,30 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
);
|
||||
}
|
||||
|
||||
Selectable<String> previousTabByTimestamp({required String tabId}) {
|
||||
return customSelect(
|
||||
'WITH ranked_tabs AS (SELECT id, timestamp, LAG(id)OVER (ORDER BY timestamp RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_tab_id FROM tab) SELECT prev_tab_id FROM ranked_tabs WHERE id = ?1',
|
||||
variables: [Variable<String>(tabId)],
|
||||
readsFrom: {tab},
|
||||
).map((QueryRow row) => row.read<String>('prev_tab_id'));
|
||||
}
|
||||
|
||||
Selectable<String> previousTabByOrderKey({required String tabId}) {
|
||||
return customSelect(
|
||||
'WITH ranked_tabs AS (SELECT id, order_key, LAG(id)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_tab_id FROM tab) SELECT prev_tab_id FROM ranked_tabs WHERE id = ?1',
|
||||
variables: [Variable<String>(tabId)],
|
||||
readsFrom: {tab},
|
||||
).map((QueryRow row) => row.read<String>('prev_tab_id'));
|
||||
}
|
||||
|
||||
Selectable<String> nextTabByOrderKey({required String tabId}) {
|
||||
return customSelect(
|
||||
'WITH ranked_tabs AS (SELECT id, order_key, LEAD(id)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_tab_id FROM tab) SELECT next_tab_id FROM ranked_tabs WHERE id = ?1',
|
||||
variables: [Variable<String>(tabId)],
|
||||
readsFrom: {tab},
|
||||
).map((QueryRow row) => row.read<String>('next_tab_id'));
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabDataRepositoryHash() => r'be1bb8a8118c7bfd90d0f1ac89a04c0a2149a0b0';
|
||||
String _$tabDataRepositoryHash() => r'295c1718ecf41202709517fb2c5b31527e885dfa';
|
||||
|
||||
/// See also [TabDataRepository].
|
||||
@ProviderFor(TabDataRepository)
|
||||
|
||||
@@ -386,6 +386,67 @@ class GeneralSettingsScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('Tab Bar Swipe Behavior'),
|
||||
leading: Icon(MdiIcons.gestureSwipeHorizontal),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: generalSettings.tabBarSwipeAction,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveGeneralSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.tabBarSwipeAction(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const RadioListTile.adaptive(
|
||||
value: TabBarSwipeAction.switchLastOpened,
|
||||
title: Text('Switch to Last Used Tab'),
|
||||
subtitle: Text(
|
||||
'Swipe to toggle between current and previously opened tab',
|
||||
),
|
||||
),
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: generalSettings.tabBarSwipeAction,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveGeneralSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.tabBarSwipeAction(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const RadioListTile.adaptive(
|
||||
value: TabBarSwipeAction.navigateOrderedTabs,
|
||||
title: Text('Navigate Sequential Tabs'),
|
||||
subtitle: Text(
|
||||
'Swipe left/right to move through tabs in order',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final size = ref.watch(
|
||||
|
||||
@@ -29,6 +29,8 @@ part 'general_settings.g.dart';
|
||||
const _fallbackSearchProvider = 'wikipedia';
|
||||
const _fallbackAutocompleteProvider = SearchSuggestionProviders.none;
|
||||
|
||||
enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
|
||||
|
||||
enum DeleteBrowsingDataType {
|
||||
tabs('Open tabs'),
|
||||
history('Browsing history'),
|
||||
@@ -59,6 +61,7 @@ class GeneralSettings with FastEquatable {
|
||||
final TabType defaultIntentTabType;
|
||||
final bool proxyPrivateTabsTor;
|
||||
final bool autoHideTabBar;
|
||||
final TabBarSwipeAction tabBarSwipeAction;
|
||||
|
||||
GeneralSettings({
|
||||
required this.themeMode,
|
||||
@@ -74,6 +77,7 @@ class GeneralSettings with FastEquatable {
|
||||
required this.defaultCreateTabType,
|
||||
required this.defaultIntentTabType,
|
||||
required this.autoHideTabBar,
|
||||
required this.tabBarSwipeAction,
|
||||
});
|
||||
|
||||
GeneralSettings.withDefaults({
|
||||
@@ -90,6 +94,7 @@ class GeneralSettings with FastEquatable {
|
||||
TabType? defaultCreateTabType,
|
||||
TabType? defaultIntentTabType,
|
||||
bool? autoHideTabBar,
|
||||
TabBarSwipeAction? tabBarSwipeAction,
|
||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||
enableReadability = enableReadability ?? true,
|
||||
enforceReadability = enforceReadability ?? false,
|
||||
@@ -102,7 +107,9 @@ class GeneralSettings with FastEquatable {
|
||||
enableLocalAiFeatures = enableLocalAiFeatures ?? true,
|
||||
defaultCreateTabType = defaultCreateTabType ?? TabType.regular,
|
||||
defaultIntentTabType = defaultIntentTabType ?? TabType.regular,
|
||||
autoHideTabBar = autoHideTabBar ?? true;
|
||||
autoHideTabBar = autoHideTabBar ?? true,
|
||||
tabBarSwipeAction =
|
||||
tabBarSwipeAction ?? TabBarSwipeAction.switchLastOpened;
|
||||
|
||||
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$GeneralSettingsFromJson(json);
|
||||
@@ -124,5 +131,6 @@ class GeneralSettings with FastEquatable {
|
||||
defaultIntentTabType,
|
||||
proxyPrivateTabsTor,
|
||||
autoHideTabBar,
|
||||
tabBarSwipeAction,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
|
||||
GeneralSettings autoHideTabBar(bool autoHideTabBar);
|
||||
|
||||
GeneralSettings tabBarSwipeAction(TabBarSwipeAction tabBarSwipeAction);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
@@ -57,6 +59,7 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
TabType defaultCreateTabType,
|
||||
TabType defaultIntentTabType,
|
||||
bool autoHideTabBar,
|
||||
TabBarSwipeAction tabBarSwipeAction,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,6 +122,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
GeneralSettings autoHideTabBar(bool autoHideTabBar) =>
|
||||
this(autoHideTabBar: autoHideTabBar);
|
||||
|
||||
@override
|
||||
GeneralSettings tabBarSwipeAction(TabBarSwipeAction tabBarSwipeAction) =>
|
||||
this(tabBarSwipeAction: tabBarSwipeAction);
|
||||
|
||||
@override
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
@@ -140,6 +147,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
Object? defaultCreateTabType = const $CopyWithPlaceholder(),
|
||||
Object? defaultIntentTabType = const $CopyWithPlaceholder(),
|
||||
Object? autoHideTabBar = const $CopyWithPlaceholder(),
|
||||
Object? tabBarSwipeAction = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return GeneralSettings(
|
||||
themeMode: themeMode == const $CopyWithPlaceholder()
|
||||
@@ -200,6 +208,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
? _value.autoHideTabBar
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: autoHideTabBar as bool,
|
||||
tabBarSwipeAction: tabBarSwipeAction == const $CopyWithPlaceholder()
|
||||
? _value.tabBarSwipeAction
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: tabBarSwipeAction as TabBarSwipeAction,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -241,28 +253,34 @@ GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
|
||||
json['defaultIntentTabType'],
|
||||
),
|
||||
autoHideTabBar: json['autoHideTabBar'] as bool?,
|
||||
tabBarSwipeAction: $enumDecodeNullable(
|
||||
_$TabBarSwipeActionEnumMap,
|
||||
json['tabBarSwipeAction'],
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'enableReadability': instance.enableReadability,
|
||||
'enforceReadability': instance.enforceReadability,
|
||||
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
||||
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
|
||||
.toList(),
|
||||
'defaultSearchProvider': instance.defaultSearchProvider,
|
||||
'defaultSearchSuggestionsProvider':
|
||||
_$SearchSuggestionProvidersEnumMap[instance
|
||||
.defaultSearchSuggestionsProvider]!,
|
||||
'createChildTabsOption': instance.createChildTabsOption,
|
||||
'showExtensionShortcut': instance.showExtensionShortcut,
|
||||
'enableLocalAiFeatures': instance.enableLocalAiFeatures,
|
||||
'defaultCreateTabType': _$TabTypeEnumMap[instance.defaultCreateTabType]!,
|
||||
'defaultIntentTabType': _$TabTypeEnumMap[instance.defaultIntentTabType]!,
|
||||
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
|
||||
'autoHideTabBar': instance.autoHideTabBar,
|
||||
};
|
||||
Map<String, dynamic> _$GeneralSettingsToJson(
|
||||
GeneralSettings instance,
|
||||
) => <String, dynamic>{
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'enableReadability': instance.enableReadability,
|
||||
'enforceReadability': instance.enforceReadability,
|
||||
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
||||
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
|
||||
.toList(),
|
||||
'defaultSearchProvider': instance.defaultSearchProvider,
|
||||
'defaultSearchSuggestionsProvider':
|
||||
_$SearchSuggestionProvidersEnumMap[instance
|
||||
.defaultSearchSuggestionsProvider]!,
|
||||
'createChildTabsOption': instance.createChildTabsOption,
|
||||
'showExtensionShortcut': instance.showExtensionShortcut,
|
||||
'enableLocalAiFeatures': instance.enableLocalAiFeatures,
|
||||
'defaultCreateTabType': _$TabTypeEnumMap[instance.defaultCreateTabType]!,
|
||||
'defaultIntentTabType': _$TabTypeEnumMap[instance.defaultIntentTabType]!,
|
||||
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
|
||||
'autoHideTabBar': instance.autoHideTabBar,
|
||||
'tabBarSwipeAction': _$TabBarSwipeActionEnumMap[instance.tabBarSwipeAction]!,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
ThemeMode.system: 'system',
|
||||
@@ -292,3 +310,8 @@ const _$TabTypeEnumMap = {
|
||||
TabType.private: 'private',
|
||||
TabType.child: 'child',
|
||||
};
|
||||
|
||||
const _$TabBarSwipeActionEnumMap = {
|
||||
TabBarSwipeAction.switchLastOpened: 'switchLastOpened',
|
||||
TabBarSwipeAction.navigateOrderedTabs: 'navigateOrderedTabs',
|
||||
};
|
||||
|
||||
@@ -96,6 +96,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
||||
DriftSqlType.bool,
|
||||
db.typeMapping,
|
||||
),
|
||||
'tabBarSwipeAction': settings['tabBarSwipeAction']?.readAs(
|
||||
DriftSqlType.string,
|
||||
db.typeMapping,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ final generalSettingsWithDefaultsProvider =
|
||||
typedef GeneralSettingsWithDefaultsRef =
|
||||
AutoDisposeProviderRef<GeneralSettings>;
|
||||
String _$generalSettingsRepositoryHash() =>
|
||||
r'43f314d1c0d318d8c28afd38fee5f2416a26f423';
|
||||
r'e311760a7773109a90c28275262883fcca348bbb';
|
||||
|
||||
/// See also [GeneralSettingsRepository].
|
||||
@ProviderFor(GeneralSettingsRepository)
|
||||
|
||||
Reference in New Issue
Block a user