improved quick tab switcher & general tab design/logic;
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/restore_complete.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||
|
||||
part 'pending_tab_selection.g.dart';
|
||||
|
||||
/// Queues the selection of a tab that exists in the local DB but has not
|
||||
/// been delivered by the native session restore yet (placeholder chips at
|
||||
/// cold start). The queued tab is selected as soon as its native state
|
||||
/// arrives; the queue clears itself if restore finishes without it.
|
||||
@Riverpod(keepAlive: true)
|
||||
class PendingTabSelection extends _$PendingTabSelection {
|
||||
void queue(String tabId) {
|
||||
if (state != tabId) {
|
||||
state = tabId;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
state = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String? build() {
|
||||
ref.listen(
|
||||
tabStatesProvider,
|
||||
(previous, next) {
|
||||
final pendingTabId = state;
|
||||
if (pendingTabId != null && next.containsKey(pendingTabId)) {
|
||||
state = null;
|
||||
unawaited(
|
||||
ref.read(tabRepositoryProvider.notifier).selectTab(pendingTabId),
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (error, stackTrace) {
|
||||
logger.e(
|
||||
'Error listening to tabStatesProvider',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Stale-tab safety: when restore finishes and the queued id never
|
||||
// appeared, the tab no longer exists.
|
||||
ref.listen(browserRestoreCompleteProvider, (previous, next) {
|
||||
if (next &&
|
||||
state != null &&
|
||||
!ref.read(tabStatesProvider).containsKey(state)) {
|
||||
state = null;
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'pending_tab_selection.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Queues the selection of a tab that exists in the local DB but has not
|
||||
/// been delivered by the native session restore yet (placeholder chips at
|
||||
/// cold start). The queued tab is selected as soon as its native state
|
||||
/// arrives; the queue clears itself if restore finishes without it.
|
||||
|
||||
@ProviderFor(PendingTabSelection)
|
||||
final pendingTabSelectionProvider = PendingTabSelectionProvider._();
|
||||
|
||||
/// Queues the selection of a tab that exists in the local DB but has not
|
||||
/// been delivered by the native session restore yet (placeholder chips at
|
||||
/// cold start). The queued tab is selected as soon as its native state
|
||||
/// arrives; the queue clears itself if restore finishes without it.
|
||||
final class PendingTabSelectionProvider
|
||||
extends $NotifierProvider<PendingTabSelection, String?> {
|
||||
/// Queues the selection of a tab that exists in the local DB but has not
|
||||
/// been delivered by the native session restore yet (placeholder chips at
|
||||
/// cold start). The queued tab is selected as soon as its native state
|
||||
/// arrives; the queue clears itself if restore finishes without it.
|
||||
PendingTabSelectionProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'pendingTabSelectionProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$pendingTabSelectionHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
PendingTabSelection create() => PendingTabSelection();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(String? value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<String?>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$pendingTabSelectionHash() =>
|
||||
r'761d5814b40b1003b59db09a0bb7ef370530b317';
|
||||
|
||||
/// Queues the selection of a tab that exists in the local DB but has not
|
||||
/// been delivered by the native session restore yet (placeholder chips at
|
||||
/// cold start). The queued tab is selected as soon as its native state
|
||||
/// arrives; the queue clears itself if restore finishes without it.
|
||||
|
||||
abstract class _$PendingTabSelection extends $Notifier<String?> {
|
||||
String? build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<String?, String?>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<String?, String?>,
|
||||
String?,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||
|
||||
part 'restore_complete.g.dart';
|
||||
|
||||
/// Mirrors the native `BrowserState.restoreComplete` flag: false until the
|
||||
/// session restore has dispatched all persisted tabs into the BrowserStore.
|
||||
/// While false, DB-cached tabs without a native state are rendered as
|
||||
/// placeholders and the destructive tab DB sync is deferred.
|
||||
@Riverpod(keepAlive: true)
|
||||
class BrowserRestoreComplete extends _$BrowserRestoreComplete {
|
||||
@override
|
||||
bool build() {
|
||||
final eventService = ref.watch(eventServiceProvider);
|
||||
|
||||
ref.listen(
|
||||
fireImmediately: true,
|
||||
engineReadyStateProvider,
|
||||
(previous, next) async {
|
||||
if (next) {
|
||||
await GeckoTabService().syncEvents(onRestoreComplete: true);
|
||||
}
|
||||
},
|
||||
onError: (error, stackTrace) {
|
||||
logger.e(
|
||||
'Error listening to engineReadyStateProvider',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final restoreCompleteSub = eventService.restoreCompleteEvents.listen(
|
||||
(restoreComplete) {
|
||||
if (restoreComplete != state) {
|
||||
state = restoreComplete;
|
||||
}
|
||||
},
|
||||
onError: (Object error, StackTrace stackTrace) {
|
||||
logger.e(
|
||||
'Error in restore complete events',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ref.onDispose(() async {
|
||||
await restoreCompleteSub.cancel();
|
||||
});
|
||||
|
||||
return eventService.restoreCompleteEvents.valueOrNull ?? false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'restore_complete.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Mirrors the native `BrowserState.restoreComplete` flag: false until the
|
||||
/// session restore has dispatched all persisted tabs into the BrowserStore.
|
||||
/// While false, DB-cached tabs without a native state are rendered as
|
||||
/// placeholders and the destructive tab DB sync is deferred.
|
||||
|
||||
@ProviderFor(BrowserRestoreComplete)
|
||||
final browserRestoreCompleteProvider = BrowserRestoreCompleteProvider._();
|
||||
|
||||
/// Mirrors the native `BrowserState.restoreComplete` flag: false until the
|
||||
/// session restore has dispatched all persisted tabs into the BrowserStore.
|
||||
/// While false, DB-cached tabs without a native state are rendered as
|
||||
/// placeholders and the destructive tab DB sync is deferred.
|
||||
final class BrowserRestoreCompleteProvider
|
||||
extends $NotifierProvider<BrowserRestoreComplete, bool> {
|
||||
/// Mirrors the native `BrowserState.restoreComplete` flag: false until the
|
||||
/// session restore has dispatched all persisted tabs into the BrowserStore.
|
||||
/// While false, DB-cached tabs without a native state are rendered as
|
||||
/// placeholders and the destructive tab DB sync is deferred.
|
||||
BrowserRestoreCompleteProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'browserRestoreCompleteProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$browserRestoreCompleteHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
BrowserRestoreComplete create() => BrowserRestoreComplete();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(bool value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<bool>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$browserRestoreCompleteHash() =>
|
||||
r'971ce7722781b3cb581cfa9ada0884e91f723b85';
|
||||
|
||||
/// Mirrors the native `BrowserState.restoreComplete` flag: false until the
|
||||
/// session restore has dispatched all persisted tabs into the BrowserStore.
|
||||
/// While false, DB-cached tabs without a native state are rendered as
|
||||
/// placeholders and the destructive tab DB sync is deferred.
|
||||
|
||||
abstract class _$BrowserRestoreComplete extends $Notifier<bool> {
|
||||
bool build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<bool, bool>,
|
||||
bool,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user