improve tab (and other lexorank entities) sorting, ordering and reordering
This commit is contained in:
@@ -44,9 +44,24 @@ class ToolbarButtonConfigDao extends DatabaseAccessor<UserDatabase>
|
||||
.write(ToolbarButtonConfigsCompanion(orderKey: Value(orderKey)));
|
||||
|
||||
Future<void> assignVisibility(String buttonId, {required bool visible}) =>
|
||||
(update(db.toolbarButtonConfigs)
|
||||
..where((t) => t.buttonId.equals(buttonId)))
|
||||
.write(ToolbarButtonConfigsCompanion(isVisible: Value(visible)));
|
||||
transaction(() async {
|
||||
// Land the toggled button at the trailing edge of its *new* section
|
||||
// so its order_key always stays inside the visibility partition.
|
||||
// Without this, a button keeps the key it was assigned in the other
|
||||
// section and lands at an arbitrary position after the toggle.
|
||||
final orderKey = await generateTrailingOrderKey(
|
||||
isVisible: visible,
|
||||
).getSingle();
|
||||
|
||||
await (update(
|
||||
db.toolbarButtonConfigs,
|
||||
)..where((t) => t.buttonId.equals(buttonId))).write(
|
||||
ToolbarButtonConfigsCompanion(
|
||||
isVisible: Value(visible),
|
||||
orderKey: Value(orderKey),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
Future<void> assignFallback(String buttonId, String? fallbackId) =>
|
||||
(update(db.toolbarButtonConfigs)
|
||||
@@ -74,7 +89,9 @@ class ToolbarButtonConfigDao extends DatabaseAccessor<UserDatabase>
|
||||
await transaction(() async {
|
||||
final inserted = <ToolbarButtonConfig>[];
|
||||
for (final def in missing) {
|
||||
final orderKey = await generateTrailingOrderKey().getSingle();
|
||||
final orderKey = await generateTrailingOrderKey(
|
||||
isVisible: def.defaultVisible,
|
||||
).getSingle();
|
||||
inserted.add(
|
||||
ToolbarButtonConfig(
|
||||
buttonId: def.buttonId,
|
||||
@@ -96,18 +113,37 @@ class ToolbarButtonConfigDao extends DatabaseAccessor<UserDatabase>
|
||||
});
|
||||
}
|
||||
|
||||
SingleSelectable<String> generateLeadingOrderKey({int bucket = 0}) =>
|
||||
db.definitionsDrift.toolbarLeadingOrderKey(bucket: bucket);
|
||||
SingleSelectable<String> generateLeadingOrderKey({
|
||||
int bucket = 0,
|
||||
required bool isVisible,
|
||||
}) => db.definitionsDrift.toolbarLeadingOrderKey(
|
||||
bucket: bucket,
|
||||
isVisible: isVisible,
|
||||
);
|
||||
|
||||
SingleSelectable<String> generateTrailingOrderKey({int bucket = 0}) =>
|
||||
db.definitionsDrift.toolbarTrailingOrderKey(bucket: bucket);
|
||||
SingleSelectable<String> generateTrailingOrderKey({
|
||||
int bucket = 0,
|
||||
required bool isVisible,
|
||||
}) => db.definitionsDrift.toolbarTrailingOrderKey(
|
||||
bucket: bucket,
|
||||
isVisible: isVisible,
|
||||
);
|
||||
|
||||
SingleOrNullSelectable<String> generateOrderKeyAfterButtonId(
|
||||
String buttonId,
|
||||
) => db.definitionsDrift.toolbarOrderKeyAfterButton(buttonId: buttonId);
|
||||
String buttonId, {
|
||||
required bool isVisible,
|
||||
}) => db.definitionsDrift.toolbarOrderKeyAfterButton(
|
||||
buttonId: buttonId,
|
||||
isVisible: isVisible,
|
||||
);
|
||||
|
||||
SingleSelectable<String> generateOrderKeyBeforeButtonId(String buttonId) =>
|
||||
db.definitionsDrift.toolbarOrderKeyBeforeButton(buttonId: buttonId);
|
||||
SingleSelectable<String> generateOrderKeyBeforeButtonId(
|
||||
String buttonId, {
|
||||
required bool isVisible,
|
||||
}) => db.definitionsDrift.toolbarOrderKeyBeforeButton(
|
||||
buttonId: buttonId,
|
||||
isVisible: isVisible,
|
||||
);
|
||||
|
||||
Future<void> _insertWithDeferredFallbacks(
|
||||
List<ToolbarButtonConfig> configs,
|
||||
|
||||
@@ -31,47 +31,56 @@ CREATE TABLE toolbar_button_configs (
|
||||
|
||||
CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs(order_key);
|
||||
|
||||
toolbarLeadingOrderKey(:bucket AS INTEGER):
|
||||
-- All four queries are scoped to a visibility partition (`is_visible`) so the
|
||||
-- generated key falls strictly within the targeted section. The Customize
|
||||
-- Toolbar UI renders Enabled and Disabled as two independent reorderable
|
||||
-- lists; without the partition, leading/trailing keys would cross the section
|
||||
-- boundary and surface as buttons "jumping" after a visibility toggle.
|
||||
toolbarLeadingOrderKey(:bucket AS INTEGER, :is_visible AS BOOL):
|
||||
SELECT lexo_rank_previous(
|
||||
:bucket,
|
||||
(
|
||||
SELECT order_key
|
||||
FROM toolbar_button_configs
|
||||
WHERE is_visible = :is_visible
|
||||
ORDER BY order_key
|
||||
LIMIT 1
|
||||
)
|
||||
);
|
||||
|
||||
toolbarTrailingOrderKey(:bucket AS INTEGER):
|
||||
toolbarTrailingOrderKey(:bucket AS INTEGER, :is_visible AS BOOL):
|
||||
SELECT lexo_rank_next(
|
||||
:bucket,
|
||||
(
|
||||
SELECT order_key
|
||||
FROM toolbar_button_configs
|
||||
WHERE is_visible = :is_visible
|
||||
ORDER BY order_key DESC
|
||||
LIMIT 1
|
||||
)
|
||||
);
|
||||
|
||||
toolbarOrderKeyAfterButton(:button_id AS TEXT):
|
||||
toolbarOrderKeyAfterButton(:button_id AS TEXT, :is_visible AS BOOL):
|
||||
WITH ordered_table AS (
|
||||
SELECT
|
||||
button_id,
|
||||
order_key,
|
||||
LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key
|
||||
FROM toolbar_button_configs
|
||||
WHERE is_visible = :is_visible
|
||||
)
|
||||
SELECT lexo_rank_reorder_after(order_key, next_order_key)
|
||||
FROM ordered_table
|
||||
WHERE button_id = :button_id;
|
||||
|
||||
toolbarOrderKeyBeforeButton(:button_id AS TEXT):
|
||||
toolbarOrderKeyBeforeButton(:button_id AS TEXT, :is_visible AS BOOL):
|
||||
WITH ordered_table AS (
|
||||
SELECT
|
||||
button_id,
|
||||
order_key,
|
||||
LAG(order_key) OVER (ORDER BY order_key) AS prev_order_key
|
||||
FROM toolbar_button_configs
|
||||
WHERE is_visible = :is_visible
|
||||
)
|
||||
SELECT lexo_rank_reorder_before(order_key, prev_order_key)
|
||||
FROM ordered_table
|
||||
|
||||
@@ -2024,36 +2024,46 @@ i0.Index get idxToolbarOrderKey => i0.Index(
|
||||
|
||||
class DefinitionsDrift extends i3.ModularAccessor {
|
||||
DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
|
||||
i0.Selectable<String> toolbarLeadingOrderKey({required int bucket}) {
|
||||
i0.Selectable<String> toolbarLeadingOrderKey({
|
||||
required int bucket,
|
||||
required bool isVisible,
|
||||
}) {
|
||||
return customSelect(
|
||||
'SELECT lexo_rank_previous(?1, (SELECT order_key FROM toolbar_button_configs ORDER BY order_key LIMIT 1)) AS _c0',
|
||||
variables: [i0.Variable<int>(bucket)],
|
||||
'SELECT lexo_rank_previous(?1, (SELECT order_key FROM toolbar_button_configs WHERE is_visible = ?2 ORDER BY order_key LIMIT 1)) AS _c0',
|
||||
variables: [i0.Variable<int>(bucket), i0.Variable<bool>(isVisible)],
|
||||
readsFrom: {toolbarButtonConfigs},
|
||||
).map((i0.QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
i0.Selectable<String> toolbarTrailingOrderKey({required int bucket}) {
|
||||
i0.Selectable<String> toolbarTrailingOrderKey({
|
||||
required int bucket,
|
||||
required bool isVisible,
|
||||
}) {
|
||||
return customSelect(
|
||||
'SELECT lexo_rank_next(?1, (SELECT order_key FROM toolbar_button_configs ORDER BY order_key DESC LIMIT 1)) AS _c0',
|
||||
variables: [i0.Variable<int>(bucket)],
|
||||
'SELECT lexo_rank_next(?1, (SELECT order_key FROM toolbar_button_configs WHERE is_visible = ?2 ORDER BY order_key DESC LIMIT 1)) AS _c0',
|
||||
variables: [i0.Variable<int>(bucket), i0.Variable<bool>(isVisible)],
|
||||
readsFrom: {toolbarButtonConfigs},
|
||||
).map((i0.QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
i0.Selectable<String> toolbarOrderKeyAfterButton({required String buttonId}) {
|
||||
i0.Selectable<String> toolbarOrderKeyAfterButton({
|
||||
required bool isVisible,
|
||||
required String buttonId,
|
||||
}) {
|
||||
return customSelect(
|
||||
'WITH ordered_table AS (SELECT button_id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM toolbar_button_configs) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE button_id = ?1',
|
||||
variables: [i0.Variable<String>(buttonId)],
|
||||
'WITH ordered_table AS (SELECT button_id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM toolbar_button_configs WHERE is_visible = ?1) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE button_id = ?2',
|
||||
variables: [i0.Variable<bool>(isVisible), i0.Variable<String>(buttonId)],
|
||||
readsFrom: {toolbarButtonConfigs},
|
||||
).map((i0.QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
i0.Selectable<String> toolbarOrderKeyBeforeButton({
|
||||
required bool isVisible,
|
||||
required String buttonId,
|
||||
}) {
|
||||
return customSelect(
|
||||
'WITH ordered_table AS (SELECT button_id, order_key, LAG(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_order_key FROM toolbar_button_configs) SELECT lexo_rank_reorder_before(order_key, prev_order_key) AS _c0 FROM ordered_table WHERE button_id = ?1',
|
||||
variables: [i0.Variable<String>(buttonId)],
|
||||
'WITH ordered_table AS (SELECT button_id, order_key, LAG(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_order_key FROM toolbar_button_configs WHERE is_visible = ?1) SELECT lexo_rank_reorder_before(order_key, prev_order_key) AS _c0 FROM ordered_table WHERE button_id = ?2',
|
||||
variables: [i0.Variable<bool>(isVisible), i0.Variable<String>(buttonId)],
|
||||
readsFrom: {toolbarButtonConfigs},
|
||||
).map((i0.QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ enum QuickTabSwitcherMode { lastUsedTabs, containerTabs }
|
||||
|
||||
enum TabIntentOpenSetting { regular, private, ask }
|
||||
|
||||
enum NewTabPosition { first, end }
|
||||
enum TabListDirection { newestFirst, oldestFirst }
|
||||
|
||||
enum TabBarDirection { newestFirst, oldestFirst }
|
||||
|
||||
enum TabBarPosition { top, bottom }
|
||||
|
||||
@@ -86,7 +88,8 @@ class GeneralSettings with FastEquatable {
|
||||
final bool showIsolatedTabUi;
|
||||
@JsonKey(name: 'defaultCreateTabType')
|
||||
final TabType storedDefaultCreateTabType;
|
||||
final NewTabPosition newTabPosition;
|
||||
final TabListDirection tabListDirection;
|
||||
final TabBarDirection tabBarDirection;
|
||||
final TabIntentOpenSetting tabIntentOpenSetting;
|
||||
final bool autoHideTabBar;
|
||||
final TabBarSwipeAction tabBarSwipeAction;
|
||||
@@ -140,7 +143,8 @@ class GeneralSettings with FastEquatable {
|
||||
required this.showContainerUi,
|
||||
required this.showIsolatedTabUi,
|
||||
required this.storedDefaultCreateTabType,
|
||||
required this.newTabPosition,
|
||||
required this.tabListDirection,
|
||||
required this.tabBarDirection,
|
||||
required this.tabIntentOpenSetting,
|
||||
required this.autoHideTabBar,
|
||||
required this.tabBarSwipeAction,
|
||||
@@ -195,7 +199,8 @@ class GeneralSettings with FastEquatable {
|
||||
bool? showContainerUi,
|
||||
bool? showIsolatedTabUi,
|
||||
TabType? storedDefaultCreateTabType,
|
||||
NewTabPosition? newTabPosition,
|
||||
TabListDirection? tabListDirection,
|
||||
TabBarDirection? tabBarDirection,
|
||||
TabIntentOpenSetting? tabIntentOpenSetting,
|
||||
bool? autoHideTabBar,
|
||||
TabBarSwipeAction? tabBarSwipeAction,
|
||||
@@ -248,7 +253,8 @@ class GeneralSettings with FastEquatable {
|
||||
showIsolatedTabUi = showIsolatedTabUi ?? true,
|
||||
storedDefaultCreateTabType =
|
||||
storedDefaultCreateTabType ?? TabType.regular,
|
||||
newTabPosition = newTabPosition ?? NewTabPosition.first,
|
||||
tabListDirection = tabListDirection ?? TabListDirection.newestFirst,
|
||||
tabBarDirection = tabBarDirection ?? TabBarDirection.newestFirst,
|
||||
tabIntentOpenSetting = tabIntentOpenSetting ?? TabIntentOpenSetting.ask,
|
||||
autoHideTabBar = autoHideTabBar ?? true,
|
||||
tabBarSwipeAction =
|
||||
@@ -295,8 +301,23 @@ class GeneralSettings with FastEquatable {
|
||||
blockExternalAppsEnabled = blockExternalAppsEnabled ?? false,
|
||||
externalAppIntentPolicies = externalAppIntentPolicies ?? const {};
|
||||
|
||||
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$GeneralSettingsFromJson(json);
|
||||
factory GeneralSettings.fromJson(Map<String, dynamic> json) {
|
||||
// Migrate legacy `newTabPosition` setting to direction settings.
|
||||
// Old `first` (new tabs at top) → newestFirst; `end` → oldestFirst.
|
||||
// TODO: Drop this fallback (and the `newTabPosition` row in the user
|
||||
// settings DB) once enough releases have shipped that rolling back to a
|
||||
// version without `tabListDirection`/`tabBarDirection` is no longer a
|
||||
// concern.
|
||||
final legacyNewTabPosition = json['newTabPosition'];
|
||||
if (legacyNewTabPosition != null) {
|
||||
final mapped = legacyNewTabPosition == 'end'
|
||||
? 'oldestFirst'
|
||||
: 'newestFirst';
|
||||
json.putIfAbsent('tabListDirection', () => mapped);
|
||||
json.putIfAbsent('tabBarDirection', () => mapped);
|
||||
}
|
||||
return _$GeneralSettingsFromJson(json);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => _$GeneralSettingsToJson(this);
|
||||
|
||||
@@ -332,7 +353,8 @@ class GeneralSettings with FastEquatable {
|
||||
showContainerUi,
|
||||
showIsolatedTabUi,
|
||||
storedDefaultCreateTabType,
|
||||
newTabPosition,
|
||||
tabListDirection,
|
||||
tabBarDirection,
|
||||
tabIntentOpenSetting,
|
||||
autoHideTabBar,
|
||||
tabBarSwipeAction,
|
||||
|
||||
@@ -43,7 +43,9 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
TabType storedDefaultCreateTabType,
|
||||
);
|
||||
|
||||
GeneralSettings newTabPosition(NewTabPosition newTabPosition);
|
||||
GeneralSettings tabListDirection(TabListDirection tabListDirection);
|
||||
|
||||
GeneralSettings tabBarDirection(TabBarDirection tabBarDirection);
|
||||
|
||||
GeneralSettings tabIntentOpenSetting(
|
||||
TabIntentOpenSetting tabIntentOpenSetting,
|
||||
@@ -154,7 +156,8 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
bool showContainerUi,
|
||||
bool showIsolatedTabUi,
|
||||
TabType storedDefaultCreateTabType,
|
||||
NewTabPosition newTabPosition,
|
||||
TabListDirection tabListDirection,
|
||||
TabBarDirection tabBarDirection,
|
||||
TabIntentOpenSetting tabIntentOpenSetting,
|
||||
bool autoHideTabBar,
|
||||
TabBarSwipeAction tabBarSwipeAction,
|
||||
@@ -265,8 +268,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
) => call(storedDefaultCreateTabType: storedDefaultCreateTabType);
|
||||
|
||||
@override
|
||||
GeneralSettings newTabPosition(NewTabPosition newTabPosition) =>
|
||||
call(newTabPosition: newTabPosition);
|
||||
GeneralSettings tabListDirection(TabListDirection tabListDirection) =>
|
||||
call(tabListDirection: tabListDirection);
|
||||
|
||||
@override
|
||||
GeneralSettings tabBarDirection(TabBarDirection tabBarDirection) =>
|
||||
call(tabBarDirection: tabBarDirection);
|
||||
|
||||
@override
|
||||
GeneralSettings tabIntentOpenSetting(
|
||||
@@ -447,7 +454,8 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
Object? showContainerUi = const $CopyWithPlaceholder(),
|
||||
Object? showIsolatedTabUi = const $CopyWithPlaceholder(),
|
||||
Object? storedDefaultCreateTabType = const $CopyWithPlaceholder(),
|
||||
Object? newTabPosition = const $CopyWithPlaceholder(),
|
||||
Object? tabListDirection = const $CopyWithPlaceholder(),
|
||||
Object? tabBarDirection = const $CopyWithPlaceholder(),
|
||||
Object? tabIntentOpenSetting = const $CopyWithPlaceholder(),
|
||||
Object? autoHideTabBar = const $CopyWithPlaceholder(),
|
||||
Object? tabBarSwipeAction = const $CopyWithPlaceholder(),
|
||||
@@ -572,12 +580,18 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
? _value.storedDefaultCreateTabType
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: storedDefaultCreateTabType as TabType,
|
||||
newTabPosition:
|
||||
newTabPosition == const $CopyWithPlaceholder() ||
|
||||
newTabPosition == null
|
||||
? _value.newTabPosition
|
||||
tabListDirection:
|
||||
tabListDirection == const $CopyWithPlaceholder() ||
|
||||
tabListDirection == null
|
||||
? _value.tabListDirection
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: newTabPosition as NewTabPosition,
|
||||
: tabListDirection as TabListDirection,
|
||||
tabBarDirection:
|
||||
tabBarDirection == const $CopyWithPlaceholder() ||
|
||||
tabBarDirection == null
|
||||
? _value.tabBarDirection
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: tabBarDirection as TabBarDirection,
|
||||
tabIntentOpenSetting:
|
||||
tabIntentOpenSetting == const $CopyWithPlaceholder() ||
|
||||
tabIntentOpenSetting == null
|
||||
@@ -836,9 +850,13 @@ GeneralSettings _$GeneralSettingsFromJson(
|
||||
_$TabTypeEnumMap,
|
||||
json['defaultCreateTabType'],
|
||||
),
|
||||
newTabPosition: $enumDecodeNullable(
|
||||
_$NewTabPositionEnumMap,
|
||||
json['newTabPosition'],
|
||||
tabListDirection: $enumDecodeNullable(
|
||||
_$TabListDirectionEnumMap,
|
||||
json['tabListDirection'],
|
||||
),
|
||||
tabBarDirection: $enumDecodeNullable(
|
||||
_$TabBarDirectionEnumMap,
|
||||
json['tabBarDirection'],
|
||||
),
|
||||
tabIntentOpenSetting: $enumDecodeNullable(
|
||||
_$TabIntentOpenSettingEnumMap,
|
||||
@@ -937,7 +955,8 @@ Map<String, dynamic> _$GeneralSettingsToJson(
|
||||
'showIsolatedTabUi': instance.showIsolatedTabUi,
|
||||
'defaultCreateTabType':
|
||||
_$TabTypeEnumMap[instance.storedDefaultCreateTabType]!,
|
||||
'newTabPosition': _$NewTabPositionEnumMap[instance.newTabPosition]!,
|
||||
'tabListDirection': _$TabListDirectionEnumMap[instance.tabListDirection]!,
|
||||
'tabBarDirection': _$TabBarDirectionEnumMap[instance.tabBarDirection]!,
|
||||
'tabIntentOpenSetting':
|
||||
_$TabIntentOpenSettingEnumMap[instance.tabIntentOpenSetting]!,
|
||||
'autoHideTabBar': instance.autoHideTabBar,
|
||||
@@ -1012,9 +1031,14 @@ const _$TabTypeEnumMap = {
|
||||
TabType.isolated: 'isolated',
|
||||
};
|
||||
|
||||
const _$NewTabPositionEnumMap = {
|
||||
NewTabPosition.first: 'first',
|
||||
NewTabPosition.end: 'end',
|
||||
const _$TabListDirectionEnumMap = {
|
||||
TabListDirection.newestFirst: 'newestFirst',
|
||||
TabListDirection.oldestFirst: 'oldestFirst',
|
||||
};
|
||||
|
||||
const _$TabBarDirectionEnumMap = {
|
||||
TabBarDirection.newestFirst: 'newestFirst',
|
||||
TabBarDirection.oldestFirst: 'oldestFirst',
|
||||
};
|
||||
|
||||
const _$TabIntentOpenSettingEnumMap = {
|
||||
|
||||
Reference in New Issue
Block a user