improve back navigation and ask tabs to close when originated from intent
This commit is contained in:
@@ -27,19 +27,30 @@ import io.flutter.embedding.engine.dart.DartExecutor
|
|||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class MainActivity: FlutterFragmentActivity() {
|
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 val ENGINE_ID = "engine_id"
|
||||||
private var channel: MethodChannel? = null
|
private var trimMemoryChannel: MethodChannel? = null
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(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) {
|
override fun onTrimMemory(level: Int) {
|
||||||
super.onTrimMemory(level)
|
super.onTrimMemory(level)
|
||||||
channel?.invokeMethod("onTrimMemory", level)
|
trimMemoryChannel?.invokeMethod("onTrimMemory", level)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun provideFlutterEngine(context: Context): FlutterEngine {
|
override fun provideFlutterEngine(context: Context): FlutterEngine {
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ class TabRepository extends _$TabRepository {
|
|||||||
return _tabFromIntent.contains(tabId);
|
return _tabFromIntent.contains(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearLaunchedFromIntent(String tabId) {
|
||||||
|
_tabFromIntent.remove(tabId);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> _resolveParentIdForContext({
|
Future<String?> _resolveParentIdForContext({
|
||||||
required String? parentId,
|
required String? parentId,
|
||||||
required String? targetContextId,
|
required String? targetContextId,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$tabRepositoryHash() => r'f0ff983df4115bfed7ff3dbc8994592bc46a1daa';
|
String _$tabRepositoryHash() => r'b300b88bc7817b533be46d8d0d933786b10212fb';
|
||||||
|
|
||||||
abstract class _$TabRepository extends $Notifier<void> {
|
abstract class _$TabRepository extends $Notifier<void> {
|
||||||
void build();
|
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 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/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_bar_dismissable.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.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/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_fab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.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/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/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;
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
/// Callback for toolbar animation progress updates.
|
/// Callback for toolbar animation progress updates.
|
||||||
@@ -810,9 +811,26 @@ class _Browser extends HookConsumerWidget {
|
|||||||
if (ref
|
if (ref
|
||||||
.read(tabRepositoryProvider.notifier)
|
.read(tabRepositoryProvider.notifier)
|
||||||
.hasLaunchedFromIntent(tabState?.id)) {
|
.hasLaunchedFromIntent(tabState?.id)) {
|
||||||
//Mark back as unhandled and navigator will pop
|
if (!context.mounted) return false;
|
||||||
await SystemNavigator.pop();
|
|
||||||
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)
|
// Handle double back to close (if enabled)
|
||||||
@@ -836,9 +854,8 @@ class _Browser extends HookConsumerWidget {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
//Mark back as unhandled and navigator will pop
|
await moveToBackground();
|
||||||
await SystemNavigator.pop();
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lastBackButtonPress.value = DateTime.now();
|
lastBackButtonPress.value = DateTime.now();
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user