improved quick tab switcher & general tab design/logic;

This commit is contained in:
Fabian Freund
2026-06-12 03:47:20 +02:00
parent 83c78b53d3
commit 3c5f472bdf
35 changed files with 2715 additions and 768 deletions
@@ -178,6 +178,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
override fun syncEvents(
onSelectedTabChange: Boolean,
onTabListChange: Boolean,
onRestoreComplete: Boolean,
onTabContentStateChange: Boolean,
onIconChange: Boolean,
onSecurityInfoStateChange: Boolean,
@@ -208,6 +209,13 @@ class GeckoTabsApiImpl : GeckoTabsApi {
tabs.map { it.id }) { }
}
if (onRestoreComplete) {
components.flutterEvents.onRestoreCompleteChange(
EventSequence.next(),
components.core.store.state.restoreComplete
) { }
}
tabs.forEach { tab ->
if (onTabContentStateChange) {
components.flutterEvents.onTabContentStateChange(
@@ -64,6 +64,17 @@ class Events(
}
}
stateFlow.flowScoped(dispatcher = Dispatchers.Main) { flow ->
flow.map { state -> state.restoreComplete }
.distinctUntilChanged()
.collect { restoreComplete ->
flutterEvents.onRestoreCompleteChange(
EventSequence.next(),
restoreComplete
) { _ -> }
}
}
stateFlow.flowScoped(dispatcher = Dispatchers.Main) { flow ->
var previousTabs = emptySet<String>()
flow.mapNotNull { state -> state.tabs.map { tab -> tab.id } }
@@ -7829,7 +7829,7 @@ interface GeckoSessionApi {
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface GeckoTabsApi {
fun syncEvents(onSelectedTabChange: Boolean, onTabListChange: Boolean, onTabContentStateChange: Boolean, onIconChange: Boolean, onSecurityInfoStateChange: Boolean, onReaderableStateChange: Boolean, onHistoryStateChange: Boolean, onFindResults: Boolean, onThumbnailChange: Boolean, onBrowserExtensionsChange: Boolean, onPageExtensionsChange: Boolean, onBrowserExtensionIcons: Boolean, onPageExtensionIcons: Boolean, onTranslationStateChange: Boolean)
fun syncEvents(onSelectedTabChange: Boolean, onTabListChange: Boolean, onRestoreComplete: Boolean, onTabContentStateChange: Boolean, onIconChange: Boolean, onSecurityInfoStateChange: Boolean, onReaderableStateChange: Boolean, onHistoryStateChange: Boolean, onFindResults: Boolean, onThumbnailChange: Boolean, onBrowserExtensionsChange: Boolean, onPageExtensionsChange: Boolean, onBrowserExtensionIcons: Boolean, onPageExtensionIcons: Boolean, onTranslationStateChange: Boolean)
fun selectTab(tabId: String)
fun removeTab(tabId: String)
fun addTab(url: String, selectTab: Boolean, startLoading: Boolean, parentId: String?, flags: LoadUrlFlagsValue, contextId: String?, source: SourceValue, private: Boolean, historyMetadata: HistoryMetadataKey?, additionalHeaders: Map<String, String>?): String
@@ -7867,20 +7867,21 @@ interface GeckoTabsApi {
val args = message as List<Any?>
val onSelectedTabChangeArg = args[0] as Boolean
val onTabListChangeArg = args[1] as Boolean
val onTabContentStateChangeArg = args[2] as Boolean
val onIconChangeArg = args[3] as Boolean
val onSecurityInfoStateChangeArg = args[4] as Boolean
val onReaderableStateChangeArg = args[5] as Boolean
val onHistoryStateChangeArg = args[6] as Boolean
val onFindResultsArg = args[7] as Boolean
val onThumbnailChangeArg = args[8] as Boolean
val onBrowserExtensionsChangeArg = args[9] as Boolean
val onPageExtensionsChangeArg = args[10] as Boolean
val onBrowserExtensionIconsArg = args[11] as Boolean
val onPageExtensionIconsArg = args[12] as Boolean
val onTranslationStateChangeArg = args[13] as Boolean
val onRestoreCompleteArg = args[2] as Boolean
val onTabContentStateChangeArg = args[3] as Boolean
val onIconChangeArg = args[4] as Boolean
val onSecurityInfoStateChangeArg = args[5] as Boolean
val onReaderableStateChangeArg = args[6] as Boolean
val onHistoryStateChangeArg = args[7] as Boolean
val onFindResultsArg = args[8] as Boolean
val onThumbnailChangeArg = args[9] as Boolean
val onBrowserExtensionsChangeArg = args[10] as Boolean
val onPageExtensionsChangeArg = args[11] as Boolean
val onBrowserExtensionIconsArg = args[12] as Boolean
val onPageExtensionIconsArg = args[13] as Boolean
val onTranslationStateChangeArg = args[14] as Boolean
val wrapped: List<Any?> = try {
api.syncEvents(onSelectedTabChangeArg, onTabListChangeArg, onTabContentStateChangeArg, onIconChangeArg, onSecurityInfoStateChangeArg, onReaderableStateChangeArg, onHistoryStateChangeArg, onFindResultsArg, onThumbnailChangeArg, onBrowserExtensionsChangeArg, onPageExtensionsChangeArg, onBrowserExtensionIconsArg, onPageExtensionIconsArg, onTranslationStateChangeArg)
api.syncEvents(onSelectedTabChangeArg, onTabListChangeArg, onRestoreCompleteArg, onTabContentStateChangeArg, onIconChangeArg, onSecurityInfoStateChangeArg, onReaderableStateChangeArg, onHistoryStateChangeArg, onFindResultsArg, onThumbnailChangeArg, onBrowserExtensionsChangeArg, onPageExtensionsChangeArg, onBrowserExtensionIconsArg, onPageExtensionIconsArg, onTranslationStateChangeArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
@@ -9004,6 +9005,27 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
/**
* Mirrors `BrowserState.restoreComplete`: false until the native session
* restore has dispatched all persisted tabs into the store.
*/
fun onRestoreCompleteChange(sequenceArg: Long, restoreCompleteArg: Boolean, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onRestoreCompleteChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(sequenceArg, restoreCompleteArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(GeckoPigeonUtils.createConnectionError(channelName)))
}
}
}
fun onTabContentStateChange(sequenceArg: Long, stateArg: TabContentState, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
@@ -31,6 +31,7 @@ class GeckoEventService extends GeckoStateEvents {
final _engineStateSubject = BehaviorSubject.seeded(false);
final _tabListSubject = BehaviorSubject<List<String>>();
final _selectedTabSubject = BehaviorSubject<String?>();
final _restoreCompleteSubject = BehaviorSubject.seeded(false);
final _tabContentSubject = ReplaySubject<TabContentState>();
final _historySubject = ReplaySubject<HistoryEvent>();
@@ -59,6 +60,8 @@ 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;
Stream<TabContentState> get tabContentEvents => _tabContentSubject.stream;
Stream<HistoryEvent> get historyEvents => _historySubject.stream;
@@ -109,6 +112,11 @@ class GeckoEventService extends GeckoStateEvents {
_selectedTabSubject.addWhenMoreRecent(sequence, id, id);
}
@override
void onRestoreCompleteChange(int sequence, bool restoreComplete) {
_restoreCompleteSubject.addWhenMoreRecent(sequence, null, restoreComplete);
}
@override
void onTabContentStateChange(int sequence, TabContentState state) {
_tabContentSubject.addWhenMoreRecent(sequence, state.id, state);
@@ -19,6 +19,7 @@ class GeckoTabService {
Future<void> syncEvents({
bool onSelectedTabChange = false,
bool onTabListChange = false,
bool onRestoreComplete = false,
bool onTabContentStateChange = false,
bool onIconChange = false,
bool onSecurityInfoStateChange = false,
@@ -34,6 +35,7 @@ class GeckoTabService {
return _api.syncEvents(
onSelectedTabChange: onSelectedTabChange,
onTabListChange: onTabListChange,
onRestoreComplete: onRestoreComplete,
onTabContentStateChange: onTabContentStateChange,
onIconChange: onIconChange,
onSecurityInfoStateChange: onSecurityInfoStateChange,
@@ -7740,14 +7740,14 @@ class GeckoTabsApi {
final String pigeonVar_messageChannelSuffix;
Future<void> syncEvents({required bool onSelectedTabChange, required bool onTabListChange, required bool onTabContentStateChange, required bool onIconChange, required bool onSecurityInfoStateChange, required bool onReaderableStateChange, required bool onHistoryStateChange, required bool onFindResults, required bool onThumbnailChange, required bool onBrowserExtensionsChange, required bool onPageExtensionsChange, required bool onBrowserExtensionIcons, required bool onPageExtensionIcons, required bool onTranslationStateChange, }) async {
Future<void> syncEvents({required bool onSelectedTabChange, required bool onTabListChange, required bool onRestoreComplete, required bool onTabContentStateChange, required bool onIconChange, required bool onSecurityInfoStateChange, required bool onReaderableStateChange, required bool onHistoryStateChange, required bool onFindResults, required bool onThumbnailChange, required bool onBrowserExtensionsChange, required bool onPageExtensionsChange, required bool onBrowserExtensionIcons, required bool onPageExtensionIcons, required bool onTranslationStateChange, }) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.syncEvents$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[onSelectedTabChange, onTabListChange, onTabContentStateChange, onIconChange, onSecurityInfoStateChange, onReaderableStateChange, onHistoryStateChange, onFindResults, onThumbnailChange, onBrowserExtensionsChange, onPageExtensionsChange, onBrowserExtensionIcons, onPageExtensionIcons, onTranslationStateChange]);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[onSelectedTabChange, onTabListChange, onRestoreComplete, onTabContentStateChange, onIconChange, onSecurityInfoStateChange, onReaderableStateChange, onHistoryStateChange, onFindResults, onThumbnailChange, onBrowserExtensionsChange, onPageExtensionsChange, onBrowserExtensionIcons, onPageExtensionIcons, onTranslationStateChange]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
_extractReplyValueOrThrow(
@@ -8700,6 +8700,10 @@ abstract class GeckoStateEvents {
void onSelectedTabChange(int sequence, String? id);
/// Mirrors `BrowserState.restoreComplete`: false until the native session
/// restore has dispatched all persisted tabs into the store.
void onRestoreCompleteChange(int sequence, bool restoreComplete);
void onTabContentStateChange(int sequence, TabContentState state);
void onHistoryStateChange(int sequence, String id, HistoryState state);
@@ -8867,6 +8871,28 @@ abstract class GeckoStateEvents {
});
}
}
{
final pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onRestoreCompleteChange$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
final List<Object?> args = message! as List<Object?>;
final int arg_sequence = args[0]! as int;
final bool arg_restoreComplete = args[1]! as bool;
try {
api.onRestoreCompleteChange(arg_sequence, arg_restoreComplete);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange$messageChannelSuffix', pigeonChannelCodec,
@@ -1661,6 +1661,7 @@ abstract class GeckoTabsApi {
void syncEvents({
required bool onSelectedTabChange,
required bool onTabListChange,
required bool onRestoreComplete,
required bool onTabContentStateChange,
required bool onIconChange,
required bool onSecurityInfoStateChange,
@@ -1997,6 +1998,10 @@ abstract class GeckoStateEvents {
void onTabListChange(int sequence, List<String> tabIds);
void onSelectedTabChange(int sequence, String? id);
/// Mirrors `BrowserState.restoreComplete`: false until the native session
/// restore has dispatched all persisted tabs into the store.
void onRestoreCompleteChange(int sequence, bool restoreComplete);
void onTabContentStateChange(int sequence, TabContentState state);
void onHistoryStateChange(int sequence, String id, HistoryState state);
void onReaderableStateChange(int sequence, String id, ReaderableState state);