diff --git a/app/lib/core/providers/device_info.dart b/app/lib/core/providers/device_info.dart
new file mode 100644
index 00000000..ade3a7ba
--- /dev/null
+++ b/app/lib/core/providers/device_info.dart
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2024-2025 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 .
+ */
+import 'dart:io';
+
+import 'package:device_info_plus/device_info_plus.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+
+part 'device_info.g.dart';
+
+@Riverpod(keepAlive: true)
+class AndroidDeviceInfo extends _$AndroidDeviceInfo {
+ @override
+ Future build() async {
+ if (!Platform.isAndroid) return null;
+
+ final deviceInfo = DeviceInfoPlugin();
+ final androidInfo = await deviceInfo.androidInfo;
+
+ return AndroidDeviceInfoData(sdkInt: androidInfo.version.sdkInt);
+ }
+}
+
+class AndroidDeviceInfoData {
+ final int sdkInt;
+
+ const AndroidDeviceInfoData({required this.sdkInt});
+}
diff --git a/app/lib/core/providers/device_info.g.dart b/app/lib/core/providers/device_info.g.dart
new file mode 100644
index 00000000..74532d06
--- /dev/null
+++ b/app/lib/core/providers/device_info.g.dart
@@ -0,0 +1,60 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'device_info.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint, type=warning
+
+@ProviderFor(AndroidDeviceInfo)
+final androidDeviceInfoProvider = AndroidDeviceInfoProvider._();
+
+final class AndroidDeviceInfoProvider
+ extends $AsyncNotifierProvider {
+ AndroidDeviceInfoProvider._()
+ : super(
+ from: null,
+ argument: null,
+ retry: null,
+ name: r'androidDeviceInfoProvider',
+ isAutoDispose: false,
+ dependencies: null,
+ $allTransitiveDependencies: null,
+ );
+
+ @override
+ String debugGetCreateSourceHash() => _$androidDeviceInfoHash();
+
+ @$internal
+ @override
+ AndroidDeviceInfo create() => AndroidDeviceInfo();
+}
+
+String _$androidDeviceInfoHash() => r'05c6cc63a6ee34f137aef538d65e8ab94b9cca89';
+
+abstract class _$AndroidDeviceInfo
+ extends $AsyncNotifier {
+ FutureOr build();
+ @$mustCallSuper
+ @override
+ void runBuild() {
+ final ref =
+ this.ref
+ as $Ref, AndroidDeviceInfoData?>;
+ final element =
+ ref.element
+ as $ClassProviderElement<
+ AnyNotifier<
+ AsyncValue,
+ AndroidDeviceInfoData?
+ >,
+ AsyncValue,
+ Object?,
+ Object?
+ >;
+ element.handleCreate(ref, build);
+ }
+}
diff --git a/app/lib/core/providers/router.dart b/app/lib/core/providers/router.dart
index 2aa69b9b..2d7fbc3c 100644
--- a/app/lib/core/providers/router.dart
+++ b/app/lib/core/providers/router.dart
@@ -52,3 +52,26 @@ Future router(Ref ref) async {
initialLocation: initialLocation ?? const BrowserRoute().location,
);
}
+
+@Riverpod(keepAlive: true)
+class CurrentTopRoute extends _$CurrentTopRoute {
+ @override
+ RouteBase? build() {
+ final router = ref.watch(routerProvider).value;
+ if (router == null) return null;
+
+ void update() {
+ final config = router.routerDelegate.currentConfiguration;
+ final match = config.last;
+
+ state = match.route;
+ }
+
+ router.routerDelegate.addListener(update);
+ ref.onDispose(() => router.routerDelegate.removeListener(update));
+
+ update();
+
+ return state;
+ }
+}
diff --git a/app/lib/core/providers/router.g.dart b/app/lib/core/providers/router.g.dart
index fb9e2a43..009adf96 100644
--- a/app/lib/core/providers/router.g.dart
+++ b/app/lib/core/providers/router.g.dart
@@ -42,3 +42,55 @@ final class RouterProvider
}
String _$routerHash() => r'cbaa7e982114942303574f9573f4a75b79955583';
+
+@ProviderFor(CurrentTopRoute)
+final currentTopRouteProvider = CurrentTopRouteProvider._();
+
+final class CurrentTopRouteProvider
+ extends $NotifierProvider {
+ CurrentTopRouteProvider._()
+ : super(
+ from: null,
+ argument: null,
+ retry: null,
+ name: r'currentTopRouteProvider',
+ isAutoDispose: false,
+ dependencies: null,
+ $allTransitiveDependencies: null,
+ );
+
+ @override
+ String debugGetCreateSourceHash() => _$currentTopRouteHash();
+
+ @$internal
+ @override
+ CurrentTopRoute create() => CurrentTopRoute();
+
+ /// {@macro riverpod.override_with_value}
+ Override overrideWithValue(RouteBase? value) {
+ return $ProviderOverride(
+ origin: this,
+ providerOverride: $SyncValueProvider(value),
+ );
+ }
+}
+
+String _$currentTopRouteHash() => r'71a66713adafb5f64359eec88a69344167aae64f';
+
+abstract class _$CurrentTopRoute extends $Notifier {
+ RouteBase? build();
+ @$mustCallSuper
+ @override
+ void runBuild() {
+ final ref = this.ref as $Ref;
+ final element =
+ ref.element
+ as $ClassProviderElement<
+ AnyNotifier,
+ RouteBase?,
+ Object?,
+ Object?
+ >;
+ element.handleCreate(ref, build);
+ }
+}
diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart
index 68a79937..57974a2a 100644
--- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart
+++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart
@@ -28,6 +28,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:weblibre/core/logger.dart';
+import 'package:weblibre/core/providers/device_info.dart';
import 'package:weblibre/core/providers/router.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
@@ -248,6 +249,26 @@ class _BrowserViewState extends ConsumerState
},
);
+ final topRoute = ref.watch(currentTopRouteProvider);
+ final androidInfoAsync = ref.watch(androidDeviceInfoProvider);
+
+ final isGeckoViewVisible = androidInfoAsync.when(
+ data: (androidInfo) {
+ if (androidInfo == null) {
+ // Not Android, always show GeckoView based on route
+ return topRoute is GoRoute && topRoute.name == BrowserRoute.name;
+ }
+ // Android: only apply visibility fix on Android 12 and lower (API <= 31)
+ if (androidInfo.sdkInt <= 31) {
+ return topRoute is GoRoute && topRoute.name == BrowserRoute.name;
+ }
+ // Android 13+: always show GeckoView
+ return true;
+ },
+ loading: () => true, // Show by default while loading
+ error: (_, _) => true, // Show by default on error
+ );
+
return Listener(
behavior: HitTestBehavior.translucent,
onPointerMove: (widget.pointerMoveEventSink != null)
@@ -259,71 +280,74 @@ class _BrowserViewState extends ConsumerState
: null,
child: Stack(
children: [
- GeckoView(
- preInitializationStep: () async {
- await ref
- .read(eventServiceProvider)
- .viewReadyStateEvents
- .firstWhere((state) => state == true)
- .timeout(
- const Duration(seconds: 3),
- onTimeout: () {
- logger.e(
- 'Browser fragement not reported ready, trying to intitialize anyways',
- );
- return true;
- },
- );
- },
- postInitializationStep: () async {
- await widget.postInitializationStep?.call();
+ Visibility(
+ visible: isGeckoViewVisible,
+ child: GeckoView(
+ preInitializationStep: () async {
+ await ref
+ .read(eventServiceProvider)
+ .viewReadyStateEvents
+ .firstWhere((state) => state == true)
+ .timeout(
+ const Duration(seconds: 3),
+ onTimeout: () {
+ logger.e(
+ 'Browser fragement not reported ready, trying to intitialize anyways',
+ );
+ return true;
+ },
+ );
+ },
+ postInitializationStep: () async {
+ await widget.postInitializationStep?.call();
- if (!initializationCompleter.isCompleted) {
- const quickActions = QuickActions();
+ if (!initializationCompleter.isCompleted) {
+ const quickActions = QuickActions();
- //Debounce: https://github.com/flutter/flutter/issues/131121
- DateTime? lastAction;
- await quickActions.initialize((type) async {
- if (lastAction == null ||
- DateTime.now().difference(lastAction!) >
- const Duration(seconds: 5)) {
- if (type == 'new_tab') {
- lastAction = DateTime.now();
+ //Debounce: https://github.com/flutter/flutter/issues/131121
+ DateTime? lastAction;
+ await quickActions.initialize((type) async {
+ if (lastAction == null ||
+ DateTime.now().difference(lastAction!) >
+ const Duration(seconds: 5)) {
+ if (type == 'new_tab') {
+ lastAction = DateTime.now();
- final router = await ref.read(routerProvider.future);
- const route = SearchRoute(tabType: TabType.regular);
+ final router = await ref.read(routerProvider.future);
+ const route = SearchRoute(tabType: TabType.regular);
- await router.push(route.location);
- } else if (type == 'new_private_tab') {
- lastAction = DateTime.now();
+ await router.push(route.location);
+ } else if (type == 'new_private_tab') {
+ lastAction = DateTime.now();
- final router = await ref.read(routerProvider.future);
- const route = SearchRoute(tabType: TabType.private);
+ final router = await ref.read(routerProvider.future);
+ const route = SearchRoute(tabType: TabType.private);
- await router.push(route.location);
- } else {
- throw UnimplementedError(
- 'Unknown quick action shortcut type',
- );
+ await router.push(route.location);
+ } else {
+ throw UnimplementedError(
+ 'Unknown quick action shortcut type',
+ );
+ }
}
- }
- });
+ });
- await quickActions.setShortcutItems([
- //TODO: add icons
- const ShortcutItem(
- type: 'new_tab',
- localizedTitle: 'New Tab',
- ),
- const ShortcutItem(
- type: 'new_private_tab',
- localizedTitle: 'New Private Tab',
- ),
- ]);
+ await quickActions.setShortcutItems([
+ //TODO: add icons
+ const ShortcutItem(
+ type: 'new_tab',
+ localizedTitle: 'New Tab',
+ ),
+ const ShortcutItem(
+ type: 'new_private_tab',
+ localizedTitle: 'New Private Tab',
+ ),
+ ]);
- initializationCompleter.complete();
- }
- },
+ initializationCompleter.complete();
+ }
+ },
+ ),
),
if (!hasTab)
Positioned.fill(
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
index 2d4e47b4..be8949bb 100644
--- a/app/pubspec.yaml
+++ b/app/pubspec.yaml
@@ -16,6 +16,7 @@ dependencies:
country_codes: ^3.3.0
country_flags: ^4.1.1
cryptography_flutter: ^2.3.4
+ device_info_plus: ^12.3.0
drift: ^2.30.1
drift_dev: ^2.30.1
dynamic_color: ^1.8.1