fix: deep link hidden handling on MacOS (#1262)

This commit is contained in:
Levi authored and GitHub committed 2026-01-28 18:57:09 +08:00
1 parent 640e798d70
commit fe90c1d261
4 files changed
+82 -60

No files matched your search

@@ -62,6 +62,11 @@ final allTrackerSubscribeUrlCdns = {
class AppController extends GetxController with WindowListener, TrayListener {
static StartConfig? _defaultStartConfig;
/// Command line --hidden flag passed from main.dart
final bool hiddenFromArgs;
AppController({this.hiddenFromArgs = false});
final autoStartup = false.obs;
final startConfig = StartConfig().obs;
final runningPort = 0.obs;
@@ -156,24 +161,44 @@ class AppController extends GetxController with WindowListener, TrayListener {
Future<void> _initDeepLinks() async {
if (Util.isWeb()) {
// For web, just show window
return;
}
// Handle deep link
() async {
_appLinks = AppLinks();
_appLinks = AppLinks();
// Handle link when app is in warm state (front or background)
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
await _handleDeepLink(uri);
});
// Handle link when app is in warm state (front or background)
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
await _handleDeepLink(uri);
});
// Check initial link if app was in cold state (terminated)
final uri = await _appLinks.getInitialLink();
if (uri != null) {
await _handleDeepLink(uri);
}
}();
// Get initial link for cold start - this works after runApp()
Uri? initialLink;
try {
initialLink = await _appLinks.getInitialLink();
} catch (e) {
// ignore errors
}
// Determine if window should be hidden
// Priority 1: gopeed: URL scheme hidden parameter
// Priority 2: Command line --hidden flag
bool shouldHide = hiddenFromArgs;
if (initialLink?.scheme == "gopeed") {
shouldHide = initialLink!.queryParameters["hidden"] == "true";
}
// Show window if not hidden (desktop only)
if (Util.isDesktop() && !shouldHide) {
await windowManager.show();
await windowManager.focus();
}
// Handle initial link for deep link navigation
if (initialLink != null) {
_handleDeepLink(initialLink);
}
// Handle shared media, e.g. shared link from browser
if (Util.isMobile()) {
@@ -638,7 +663,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
launchAtStartup.setup(
appName: packageInfo.appName,
appPath: Platform.resolvedExecutable,
args: ['--${Args.flagHidden}']);
args: ['--${StartupArgs.flagHidden}']);
autoStartup.value = await launchAtStartup.isEnabled();
}
@@ -44,7 +44,11 @@ class AppView extends GetView<AppController> {
// Set the title bar to use the actual brightness of the UI
windowManager.setBrightness(brightness);
}
return child!;
// Fix for GetX Overlay issue with Flutter 3.38.1+
// Reference: https://github.com/jonataslaw/getx/issues/3425
return Overlay(
initialEntries: [OverlayEntry(builder: (_) => child!)],
);
},
),
);
@@ -38,7 +38,7 @@ class ExtensionView extends GetView<ExtensionController> {
_installBtnController.start();
try {
await installExtension(InstallExtension(url: _installUrlController.text));
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
showMessage('tip'.tr, 'extensionInstallSuccess'.tr);
await controller.load();
} catch (e) {
showErrorMessage(e);
@@ -109,7 +109,7 @@ class ExtensionView extends GetView<ExtensionController> {
try {
await installExtension(
InstallExtension(devMode: true, url: dir));
Get.snackbar(
showMessage(
'tip'.tr, 'extensionInstallSuccess'.tr);
await controller.load();
} catch (e) {
@@ -290,7 +290,7 @@ class ExtensionView extends GetView<ExtensionController> {
return showDialog<void>(
context: Get.context!,
barrierDismissible: false,
builder: (_) => AlertDialog(
builder: (dialogContext) => AlertDialog(
content: Builder(builder: (context) {
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
@@ -351,7 +351,7 @@ class ExtensionView extends GetView<ExtensionController> {
backgroundColor: MaterialStateProperty.all(
Get.theme.colorScheme.background)),
onPressed: () {
Get.back();
Navigator.of(dialogContext).pop();
},
child: Text('cancel'.tr),
),
@@ -372,7 +372,9 @@ class ExtensionView extends GetView<ExtensionController> {
UpdateExtensionSettings(
settings: formKey.currentState!.value));
await controller.load();
Get.back();
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop();
}
}
} catch (e) {
showErrorMessage(e);
@@ -453,12 +455,12 @@ class ExtensionView extends GetView<ExtensionController> {
showDialog(
context: Get.context!,
barrierDismissible: false,
builder: (_) => AlertDialog(
builder: (dialogContext) => AlertDialog(
title: Text('extensionDelete'.tr),
actions: [
TextButton(
child: Text('cancel'.tr),
onPressed: () => Get.back(),
onPressed: () => Navigator.of(dialogContext).pop(),
),
TextButton(
child: Text(
@@ -469,7 +471,9 @@ class ExtensionView extends GetView<ExtensionController> {
try {
await deleteExtension(extension.identity);
await controller.load();
Get.back();
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop();
}
} catch (e) {
showErrorMessage(e);
}
@@ -484,14 +488,14 @@ class ExtensionView extends GetView<ExtensionController> {
showDialog(
context: Get.context!,
builder: (context) => AlertDialog(
builder: (dialogContext) => AlertDialog(
content: Text('newVersionTitle'.trParams({
'version': 'v${controller.updateFlags[extension.identity]!}'
})),
actions: [
TextButton(
onPressed: () {
Get.back();
Navigator.of(dialogContext).pop();
},
child: Text('newVersionLater'.tr),
),
@@ -503,7 +507,9 @@ class ExtensionView extends GetView<ExtensionController> {
await updateExtension(extension.identity);
await controller.load();
controller.updateFlags.remove(extension.identity);
Get.back();
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop();
}
showMessage('tip'.tr, 'extensionUpdateSuccess'.tr);
} catch (e) {
showErrorMessage(e);
+22 -35
View File
@@ -22,37 +22,32 @@ import 'util/scheme_register/scheme_register.dart';
import 'util/updater.dart';
import 'util/util.dart';
class Args {
class StartupArgs {
static const flagHidden = "hidden";
bool hidden = false;
/// Command line --hidden flag (for auto-start)
bool hiddenFromArgs = false;
Args();
StartupArgs._();
Args.parse(List<String> args) {
final parser = ArgParser();
parser.addFlag(flagHidden);
final results = parser.parse(args);
hidden = results.flag(flagHidden);
/// Parse from command line arguments only
static StartupArgs parse(List<String> arguments) {
final args = StartupArgs._();
try {
final parser = ArgParser()..addFlag(flagHidden);
final results = parser.parse(arguments);
args.hiddenFromArgs = results.flag(flagHidden);
} catch (e) {
// ignore parse errors
}
return args;
}
}
void main(List<String> arguments) async {
Args args;
// Parse url scheme arguments, e.g. gopeed:?hidden=true
// TODO: macos open url handle
// TODO: macos updater test
if (arguments.firstOrNull?.startsWith("gopeed:") == true) {
try {
final uri = Uri.parse(arguments.first);
args = Args()..hidden = uri.queryParameters["hidden"] == "true";
} catch (e) {
// ignore
args = Args.parse([]);
}
} else {
args = Args.parse(arguments);
}
WidgetsFlutterBinding.ensureInitialized();
final args = StartupArgs.parse(arguments);
await init(args);
onStart();
@@ -60,8 +55,8 @@ void main(List<String> arguments) async {
runApp(const AppView());
}
Future<void> init(Args args) async {
WidgetsFlutterBinding.ensureInitialized();
Future<void> init(StartupArgs args) async {
// Note: WidgetsFlutterBinding.ensureInitialized() is already called in main()
if (Util.isMobile()) {
FlutterForegroundTask.initCommunicationPort();
}
@@ -81,16 +76,7 @@ Future<void> init(Args args) async {
skipTaskbar: runAsMenubarApp,
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
if (!args.hidden) {
await windowManager.show();
await windowManager.focus();
}
await windowManager.setPreventClose(true);
// windows_manager has a bug where when window to be maximized, it will be unmaximized immediately, so can't implement this feature currently.
// https://github.com/leanflutter/window_manager/issues/412
// if (windowState.isMaximized) {
// await windowManager.maximize();
// }
});
// Register Cmd+W hotkey on macOS to close window
@@ -118,7 +104,8 @@ Future<void> init(Args args) async {
logger.e("init package info fail", e);
}
final controller = Get.put(AppController());
final controller =
Get.put(AppController(hiddenFromArgs: args.hiddenFromArgs));
try {
await controller.loadStartConfig();
final startCfg = controller.startConfig.value;