format
This commit is contained in:
@@ -145,8 +145,10 @@ class BangUrlPattern {
|
||||
if (value == null) return null;
|
||||
if (!value.startsWith(prefix)) return null;
|
||||
if (!value.endsWith(suffix)) return null;
|
||||
final captured =
|
||||
value.substring(prefix.length, value.length - suffix.length);
|
||||
final captured = value.substring(
|
||||
prefix.length,
|
||||
value.length - suffix.length,
|
||||
);
|
||||
return captured.isEmpty ? null : captured;
|
||||
}
|
||||
|
||||
@@ -155,8 +157,10 @@ class BangUrlPattern {
|
||||
final segment = segments[pathIndex!];
|
||||
if (!segment.startsWith(prefix)) return null;
|
||||
if (!segment.endsWith(suffix)) return null;
|
||||
final captured =
|
||||
segment.substring(prefix.length, segment.length - suffix.length);
|
||||
final captured = segment.substring(
|
||||
prefix.length,
|
||||
segment.length - suffix.length,
|
||||
);
|
||||
return captured.isEmpty ? null : captured;
|
||||
}
|
||||
|
||||
@@ -238,8 +242,9 @@ class BangUrlPattern {
|
||||
final placeholderValue = scopeComponents.queryParams[sentinelParam]!;
|
||||
final sentinelStart = placeholderValue.indexOf(_sentinel);
|
||||
final prefix = placeholderValue.substring(0, sentinelStart);
|
||||
final suffix =
|
||||
placeholderValue.substring(sentinelStart + _sentinel.length);
|
||||
final suffix = placeholderValue.substring(
|
||||
sentinelStart + _sentinel.length,
|
||||
);
|
||||
|
||||
// The other params in the same scope become required constants.
|
||||
final required = <String, String>{
|
||||
@@ -327,8 +332,9 @@ bool _requiredQueryParamsMatch(
|
||||
/// duplicate-keyed query params (multi-map semantics aren't worth the
|
||||
/// complexity for our use case).
|
||||
_Components? _componentsFromUri(Uri uri) {
|
||||
final segments =
|
||||
uri.pathSegments.where((s) => s.isNotEmpty).toList(growable: false);
|
||||
final segments = uri.pathSegments
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(growable: false);
|
||||
final all = uri.queryParametersAll;
|
||||
final params = <String, String>{};
|
||||
for (final entry in all.entries) {
|
||||
|
||||
@@ -64,14 +64,11 @@ class DesktopMode extends _$DesktopMode {
|
||||
// landing on a ruled host forces desktop on again, and leaving it reverts
|
||||
// to the browser-wide default. Watching only the host keeps in-page
|
||||
// navigations (path/query changes) from clobbering a manual override.
|
||||
ref.listen(
|
||||
tabStateProvider(tabId),
|
||||
(previous, next) {
|
||||
if (previous?.url.host != next?.url.host) {
|
||||
state = _resolveForHost(next?.url);
|
||||
}
|
||||
},
|
||||
);
|
||||
ref.listen(tabStateProvider(tabId), (previous, next) {
|
||||
if (previous?.url.host != next?.url.host) {
|
||||
state = _resolveForHost(next?.url);
|
||||
}
|
||||
});
|
||||
|
||||
// Seed the initial value from the per-site rule (falling back to the
|
||||
// browser-wide default) so a newly opened tab's menu checkbox matches the
|
||||
|
||||
+12
-11
@@ -168,20 +168,21 @@ buildQuickTabSwitcherChipDecoration(
|
||||
null => null,
|
||||
},
|
||||
side: (item, isSelected) => switch (item.color) {
|
||||
final color? when isSelected => thickContainerSelectedBorder
|
||||
? BorderSide(
|
||||
color: ContainerColors.palette(
|
||||
final color? when isSelected =>
|
||||
thickContainerSelectedBorder
|
||||
? BorderSide(
|
||||
color: ContainerColors.palette(
|
||||
context,
|
||||
color,
|
||||
useCustomColor: item.useCustomColor,
|
||||
).outlineColor,
|
||||
width: 2.0,
|
||||
)
|
||||
: ContainerColors.palette(
|
||||
context,
|
||||
color,
|
||||
useCustomColor: item.useCustomColor,
|
||||
).outlineColor,
|
||||
width: 2.0,
|
||||
)
|
||||
: ContainerColors.palette(
|
||||
context,
|
||||
color,
|
||||
useCustomColor: item.useCustomColor,
|
||||
).selectedBorderSide,
|
||||
).selectedBorderSide,
|
||||
final color? => ContainerColors.palette(
|
||||
context,
|
||||
color,
|
||||
|
||||
+13
-13
@@ -90,19 +90,19 @@ class GestureExclusionSection extends HookConsumerWidget {
|
||||
bool enabled,
|
||||
) async {
|
||||
try {
|
||||
await ref.read(gestureSettingsRepositoryProvider.notifier).updateSettings(
|
||||
(current) {
|
||||
final next = current.excludedSites.toList();
|
||||
if (enabled) {
|
||||
// Enabling gestures here => remove the host from the exclusion list.
|
||||
next.remove(host);
|
||||
} else if (!next.contains(host)) {
|
||||
// Disabling gestures here => add the host to the exclusion list.
|
||||
next.add(host);
|
||||
}
|
||||
return current.copyWith.excludedSites(next);
|
||||
},
|
||||
);
|
||||
await ref.read(gestureSettingsRepositoryProvider.notifier).updateSettings((
|
||||
current,
|
||||
) {
|
||||
final next = current.excludedSites.toList();
|
||||
if (enabled) {
|
||||
// Enabling gestures here => remove the host from the exclusion list.
|
||||
next.remove(host);
|
||||
} else if (!next.contains(host)) {
|
||||
// Disabling gestures here => add the host to the exclusion list.
|
||||
next.add(host);
|
||||
}
|
||||
return current.copyWith.excludedSites(next);
|
||||
});
|
||||
} catch (e, s) {
|
||||
logger.e('Failed to toggle gesture exclusion', error: e, stackTrace: s);
|
||||
if (context.mounted) {
|
||||
|
||||
+2
-5
@@ -136,9 +136,7 @@ class _TabParentPickerSheet extends HookConsumerWidget {
|
||||
child: ListTile(
|
||||
leading: const Icon(MdiIcons.fileTreeOutline),
|
||||
title: const Text('Make standalone'),
|
||||
subtitle: const Text(
|
||||
'Detach from current parent',
|
||||
),
|
||||
subtitle: const Text('Detach from current parent'),
|
||||
enabled: movingTab.parentId != null,
|
||||
onTap: () => Navigator.of(
|
||||
context,
|
||||
@@ -173,8 +171,7 @@ class _TabParentPickerSheet extends HookConsumerWidget {
|
||||
// the one you're currently nested under".
|
||||
return ListTabPreview(
|
||||
tabId: candidate.id,
|
||||
isActive:
|
||||
candidate.id == movingTab.parentId,
|
||||
isActive: candidate.id == movingTab.parentId,
|
||||
onTap: () => Navigator.of(
|
||||
context,
|
||||
).pop(_ParentPickerSelected(candidate.id)),
|
||||
|
||||
+9
-13
@@ -296,8 +296,7 @@ class _TabTreesGrid extends HookConsumerWidget {
|
||||
|
||||
final row = index ~/ 2;
|
||||
final tabStart = row * itemSize.height;
|
||||
final viewportDimension =
|
||||
scrollController.position.viewportDimension;
|
||||
final viewportDimension = scrollController.position.viewportDimension;
|
||||
|
||||
final targetOffset =
|
||||
(tabStart - viewportDimension / 2 + itemSize.height / 2).clamp(
|
||||
@@ -324,17 +323,14 @@ class _TabTreesGrid extends HookConsumerWidget {
|
||||
}, [filteredTabEntities, activeTab]);
|
||||
|
||||
final tabs = useMemoized(() {
|
||||
return filteredTabEntities.value
|
||||
.whereType<TabTreeEntity>()
|
||||
.map((entity) {
|
||||
return _TabTreePreview(
|
||||
entity: entity,
|
||||
activeTabId: activeTab,
|
||||
onClose: onClose,
|
||||
stackPadding: const Offset(8, 8),
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
return filteredTabEntities.value.whereType<TabTreeEntity>().map((entity) {
|
||||
return _TabTreePreview(
|
||||
entity: entity,
|
||||
activeTabId: activeTab,
|
||||
onClose: onClose,
|
||||
stackPadding: const Offset(8, 8),
|
||||
);
|
||||
}).toList();
|
||||
}, [filteredTabEntities, activeTab]);
|
||||
|
||||
return Padding(
|
||||
|
||||
+2
-6
@@ -143,9 +143,7 @@ class OpenSharedContent extends HookConsumerWidget {
|
||||
unawaited(
|
||||
Future(() async {
|
||||
ContainerData? resolved;
|
||||
final containerRepo = ref.read(
|
||||
containerRepositoryProvider.notifier,
|
||||
);
|
||||
final containerRepo = ref.read(containerRepositoryProvider.notifier);
|
||||
|
||||
// Priority: explicit intent container (PWA shortcut) > site
|
||||
// assignment for the URL > mode default.
|
||||
@@ -160,9 +158,7 @@ class OpenSharedContent extends HookConsumerWidget {
|
||||
final siteAssignedId = await containerRepo
|
||||
.siteAssignedContainerId(Uri.parse(selectionUrlKey));
|
||||
if (siteAssignedId != null) {
|
||||
resolved = await containerRepo.getContainerData(
|
||||
siteAssignedId,
|
||||
);
|
||||
resolved = await containerRepo.getContainerData(siteAssignedId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -631,8 +631,7 @@ class SearchScreen extends HookConsumerWidget {
|
||||
// Whether to surface an in-app close button so the page can be dismissed
|
||||
// without a system back button/gesture (opt-in, e.g. for e-ink devices).
|
||||
// Only meaningful when there is a route to pop back to.
|
||||
final showCloseButton =
|
||||
context.canPop() && settings.showSearchCloseButton;
|
||||
final showCloseButton = context.canPop() && settings.showSearchCloseButton;
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
|
||||
+1
-4
@@ -62,10 +62,7 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
return math.max(
|
||||
math.min(
|
||||
calculatedCount,
|
||||
selectedContainer.value?.tabIds.length ?? 0,
|
||||
),
|
||||
math.min(calculatedCount, selectedContainer.value?.tabIds.length ?? 0),
|
||||
2,
|
||||
);
|
||||
}, [screenWidth, selectedContainer.value?.tabIds.length]);
|
||||
|
||||
+5
-9
@@ -77,10 +77,10 @@ class ColorPickerDialog extends HookWidget {
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop<ColorPickerResult?>(
|
||||
context,
|
||||
(color: selectedColor.value, useCustomColor: useCustom.value),
|
||||
),
|
||||
onPressed: () => Navigator.pop<ColorPickerResult?>(context, (
|
||||
color: selectedColor.value,
|
||||
useCustomColor: useCustom.value,
|
||||
)),
|
||||
child: const Text('Select'),
|
||||
),
|
||||
],
|
||||
@@ -191,11 +191,7 @@ class _CustomSwatch extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final palette = isSelected
|
||||
? ContainerColors.palette(
|
||||
context,
|
||||
selectedColor,
|
||||
useCustomColor: true,
|
||||
)
|
||||
? ContainerColors.palette(context, selectedColor, useCustomColor: true)
|
||||
: null;
|
||||
return InkResponse(
|
||||
onTap: onTap,
|
||||
|
||||
+3
-1
@@ -220,7 +220,9 @@ class _CooldownSection extends HookConsumerWidget {
|
||||
value: settings.intervalMs
|
||||
.clamp(minGestureIntervalMs, maxGestureIntervalMs)
|
||||
.toDouble(),
|
||||
label: settings.intervalMs == 0 ? 'Off' : '${settings.intervalMs} ms',
|
||||
label: settings.intervalMs == 0
|
||||
? 'Off'
|
||||
: '${settings.intervalMs} ms',
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(gestureSettingsRepositoryProvider.notifier)
|
||||
|
||||
+3
-2
@@ -141,8 +141,9 @@ class _SuggestAfterSection extends HookConsumerWidget {
|
||||
await ref
|
||||
.read(gestureSettingsRepositoryProvider.notifier)
|
||||
.updateSettings(
|
||||
(current) =>
|
||||
current.copyWith.minSuggestionStroke(value.round()),
|
||||
(current) => current.copyWith.minSuggestionStroke(
|
||||
value.round(),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
|
||||
@@ -78,10 +78,7 @@ class _GestureActionPicker extends StatelessWidget {
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Choose action',
|
||||
style: theme.textTheme.titleLarge,
|
||||
),
|
||||
child: Text('Choose action', style: theme.textTheme.titleLarge),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
||||
@@ -65,11 +65,7 @@ class GestureStrokeView extends StatelessWidget {
|
||||
if (showQualifiers) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Icon(
|
||||
stroke.startPosition.icon,
|
||||
size: 18,
|
||||
color: foreground,
|
||||
),
|
||||
child: Icon(stroke.startPosition.icon, size: 18, color: foreground),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
|
||||
+5
-6
@@ -334,9 +334,7 @@ class _TabBarStackingModeSection extends HookConsumerWidget {
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('Tab Stacking'),
|
||||
subtitle: Text(
|
||||
'How the quick tab switcher bar arranges its tabs',
|
||||
),
|
||||
subtitle: Text('How the quick tab switcher bar arranges its tabs'),
|
||||
leading: Icon(MdiIcons.folderSettings),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
@@ -505,8 +503,7 @@ class _QuickTabSwitcherTitleWidthTile extends HookConsumerWidget {
|
||||
onChangeEnd: enabled
|
||||
? (value) async {
|
||||
final normalized =
|
||||
(value / quickTabSwitcherTitleWidthStep)
|
||||
.round() *
|
||||
(value / quickTabSwitcherTitleWidthStep).round() *
|
||||
quickTabSwitcherTitleWidthStep;
|
||||
sliderValue.value = normalized;
|
||||
await ref
|
||||
@@ -683,7 +680,9 @@ class _QuickTabSwitcherHierarchyGlyphsTile extends HookConsumerWidget {
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.quickTabSwitcherHierarchyGlyphs(normalized),
|
||||
.quickTabSwitcherHierarchyGlyphs(
|
||||
normalized,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
|
||||
@@ -64,8 +64,7 @@ class TabBarPreviewHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
return height +
|
||||
BrowserTabBar.quickTabSwitcherHeight * quickTabSwitcherRows;
|
||||
return height + BrowserTabBar.quickTabSwitcherHeight * quickTabSwitcherRows;
|
||||
}
|
||||
|
||||
double get _baseHeight =>
|
||||
|
||||
+7
-2
@@ -58,7 +58,9 @@ class WebSearchInfoboxCard extends HookConsumerWidget {
|
||||
|
||||
void toggle() {
|
||||
ref
|
||||
.read(persistedBoolProvider(PersistedBoolKey.infoboxExpanded).notifier)
|
||||
.read(
|
||||
persistedBoolProvider(PersistedBoolKey.infoboxExpanded).notifier,
|
||||
)
|
||||
.toggle();
|
||||
}
|
||||
|
||||
@@ -347,7 +349,10 @@ class _InfoboxLinks extends StatelessWidget {
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: colorScheme.primary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -59,9 +59,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('path segment: captures search term', () {
|
||||
final p = BangUrlPattern.parse(
|
||||
'https://en.wikipedia.org/wiki/{{{s}}}',
|
||||
);
|
||||
final p = BangUrlPattern.parse('https://en.wikipedia.org/wiki/{{{s}}}');
|
||||
expect(
|
||||
p!.match(Uri.parse('https://en.wikipedia.org/wiki/Flutter')),
|
||||
'Flutter',
|
||||
@@ -69,9 +67,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('path segment: enforces non-placeholder segments', () {
|
||||
final p = BangUrlPattern.parse(
|
||||
'https://www.ebay.com/sch/{{{s}}}/m.html',
|
||||
);
|
||||
final p = BangUrlPattern.parse('https://www.ebay.com/sch/{{{s}}}/m.html');
|
||||
expect(
|
||||
p!.match(Uri.parse('https://www.ebay.com/sch/toys/m.html')),
|
||||
'toys',
|
||||
@@ -90,26 +86,17 @@ void main() {
|
||||
p!.match(Uri.parse('https://site.com/find/prefix-Foo-end')),
|
||||
'Foo',
|
||||
);
|
||||
expect(
|
||||
p.match(Uri.parse('https://site.com/find/Foo-end')),
|
||||
isNull,
|
||||
);
|
||||
expect(p.match(Uri.parse('https://site.com/find/Foo-end')), isNull);
|
||||
});
|
||||
|
||||
test('host mismatch rejects', () {
|
||||
final p = BangUrlPattern.parse('https://google.com/search?q={{{s}}}');
|
||||
expect(
|
||||
p!.match(Uri.parse('https://bing.com/search?q=foo')),
|
||||
isNull,
|
||||
);
|
||||
expect(p!.match(Uri.parse('https://bing.com/search?q=foo')), isNull);
|
||||
});
|
||||
|
||||
test('path length mismatch rejects', () {
|
||||
final p = BangUrlPattern.parse('https://x.com/a?q={{{s}}}');
|
||||
expect(
|
||||
p!.match(Uri.parse('https://x.com/a/b?q=foo')),
|
||||
isNull,
|
||||
);
|
||||
expect(p!.match(Uri.parse('https://x.com/a/b?q=foo')), isNull);
|
||||
});
|
||||
|
||||
test('empty captured query is rejected', () {
|
||||
@@ -152,7 +139,9 @@ void main() {
|
||||
});
|
||||
|
||||
test('non-generic subdomains still must match', () {
|
||||
final p = BangUrlPattern.parse('https://cn.bing.com/dict/search?q={{{s}}}');
|
||||
final p = BangUrlPattern.parse(
|
||||
'https://cn.bing.com/dict/search?q={{{s}}}',
|
||||
);
|
||||
expect(
|
||||
p!.match(Uri.parse('https://www.bing.com/dict/search?q=foo')),
|
||||
isNull,
|
||||
@@ -178,9 +167,7 @@ void main() {
|
||||
);
|
||||
expect(p, isNotNull);
|
||||
expect(
|
||||
p!.match(
|
||||
Uri.parse('https://research.lensai.eu/#/s/search/quantum'),
|
||||
),
|
||||
p!.match(Uri.parse('https://research.lensai.eu/#/s/search/quantum')),
|
||||
'quantum',
|
||||
);
|
||||
});
|
||||
@@ -190,31 +177,21 @@ void main() {
|
||||
'https://research.lensai.eu/#/s/search/{{{s}}}',
|
||||
);
|
||||
expect(
|
||||
p!.match(
|
||||
Uri.parse('https://research.lensai.eu/#/s/answer/quantum'),
|
||||
),
|
||||
p!.match(Uri.parse('https://research.lensai.eu/#/s/answer/quantum')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('hash query: tolerates extra fragment params', () {
|
||||
final p = BangUrlPattern.parse(
|
||||
'https://site.example/#s={{{s}}}',
|
||||
);
|
||||
expect(
|
||||
p!.match(Uri.parse('https://site.example/#s=foo&page=2')),
|
||||
'foo',
|
||||
);
|
||||
final p = BangUrlPattern.parse('https://site.example/#s={{{s}}}');
|
||||
expect(p!.match(Uri.parse('https://site.example/#s=foo&page=2')), 'foo');
|
||||
});
|
||||
|
||||
test('fragment-required template rejects URLs without fragment', () {
|
||||
final p = BangUrlPattern.parse(
|
||||
'https://boards.4chan.org/g/catalog#s={{{s}}}',
|
||||
);
|
||||
expect(
|
||||
p!.match(Uri.parse('https://boards.4chan.org/g/catalog')),
|
||||
isNull,
|
||||
);
|
||||
expect(p!.match(Uri.parse('https://boards.4chan.org/g/catalog')), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,9 +215,7 @@ void main() {
|
||||
group('BangUrlPattern - tie-break helpers', () {
|
||||
test('more required constants score higher', () {
|
||||
final plain = BangUrlPattern.parse('https://x.com/?q={{{s}}}')!;
|
||||
final imgs = BangUrlPattern.parse(
|
||||
'https://x.com/?q={{{s}}}&tbm=isch',
|
||||
)!;
|
||||
final imgs = BangUrlPattern.parse('https://x.com/?q={{{s}}}&tbm=isch')!;
|
||||
expect(imgs.constraintCount, greaterThan(plain.constraintCount));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,8 +60,7 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
ValueStream<bool> get engineReadyStateEvents => _engineStateSubject.stream;
|
||||
ValueStream<List<String>> get tabListEvents => _tabListSubject.stream;
|
||||
ValueStream<String?> get selectedTabEvents => _selectedTabSubject.stream;
|
||||
ValueStream<bool> get restoreCompleteEvents =>
|
||||
_restoreCompleteSubject.stream;
|
||||
ValueStream<bool> get restoreCompleteEvents => _restoreCompleteSubject.stream;
|
||||
|
||||
Stream<TabContentState> get tabContentEvents => _tabContentSubject.stream;
|
||||
Stream<HistoryEvent> get historyEvents => _historySubject.stream;
|
||||
|
||||
Reference in New Issue
Block a user