improve back navigation and ask tabs to close when originated from intent

This commit is contained in:
Fabian Freund
2026-02-15 18:13:17 +01:00
parent cfc7dd4757
commit 30c1fd8dd5
6 changed files with 118 additions and 12 deletions
@@ -27,19 +27,30 @@ import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterFragmentActivity() {
private val CHANNEL = "eu.weblibre.flutter_mozilla_components/trim_memory"
private val TRIM_MEMORY_CHANNEL = "eu.weblibre.flutter_mozilla_components/trim_memory"
private val ACTIVITY_CHANNEL = "eu.weblibre.gecko/activity"
private val ENGINE_ID = "engine_id"
private var channel: MethodChannel? = null
private var trimMemoryChannel: MethodChannel? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
trimMemoryChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, TRIM_MEMORY_CHANNEL)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, ACTIVITY_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"moveTaskToBack" -> {
moveTaskToBack(true)
result.success(null)
}
else -> result.notImplemented()
}
}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
channel?.invokeMethod("onTrimMemory", level)
trimMemoryChannel?.invokeMethod("onTrimMemory", level)
}
override fun provideFlutterEngine(context: Context): FlutterEngine {
@@ -54,6 +54,10 @@ class TabRepository extends _$TabRepository {
return _tabFromIntent.contains(tabId);
}
void clearLaunchedFromIntent(String tabId) {
_tabFromIntent.remove(tabId);
}
Future<String?> _resolveParentIdForContext({
required String? parentId,
required String? targetContextId,
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
}
}
String _$tabRepositoryHash() => r'f0ff983df4115bfed7ff3dbc8994592bc46a1daa';
String _$tabRepositoryHash() => r'b300b88bc7817b533be46d8d0d933786b10212fb';
abstract class _$TabRepository extends $Notifier<void> {
void build();
@@ -0,0 +1,45 @@
/*
* 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/material.dart';
/// Shows a dialog asking the user whether to keep a tab that was opened from another app.
///
/// Returns true if the user wants to keep the tab, false if they want to discard it.
Future<bool?> showKeepTabDialog(BuildContext context) {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Keep tab?'),
content: const Text(
'This tab was opened from another app. Do you want to keep it or discard it?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Discard'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Keep'),
),
],
),
);
}
@@ -21,7 +21,6 @@ import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -42,6 +41,7 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_bar_dismissable.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/keep_tab_dialog.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_fab.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart';
@@ -57,6 +57,7 @@ import 'package:weblibre/features/geckoview/features/find_in_page/presentation/w
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/utils/move_to_background.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
/// Callback for toolbar animation progress updates.
@@ -810,9 +811,26 @@ class _Browser extends HookConsumerWidget {
if (ref
.read(tabRepositoryProvider.notifier)
.hasLaunchedFromIntent(tabState?.id)) {
//Mark back as unhandled and navigator will pop
await SystemNavigator.pop();
return false;
if (!context.mounted) return false;
final keep = await showKeepTabDialog(context);
if (keep == true) {
ref
.read(tabRepositoryProvider.notifier)
.clearLaunchedFromIntent(tabState!.id);
await moveToBackground();
return true;
}
if (tabState != null) {
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(tabState.id);
}
await moveToBackground();
return true;
}
// Handle double back to close (if enabled)
@@ -836,9 +854,8 @@ class _Browser extends HookConsumerWidget {
return true;
} else {
//Mark back as unhandled and navigator will pop
await SystemNavigator.pop();
return false;
await moveToBackground();
return true;
}
} else {
lastBackButtonPress.value = DateTime.now();
+29
View File
@@ -0,0 +1,29 @@
/*
* 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/services.dart';
const _channel = MethodChannel('eu.weblibre.gecko/activity');
/// Moves the app to the background without finishing the activity.
/// Unlike [SystemNavigator.pop], this keeps the Flutter engine attached,
/// avoiding crashes when the user returns to the app.
Future<void> moveToBackground() async {
await _channel.invokeMethod('moveTaskToBack');
}