Skip to content

Commit c01d113

Browse files
authored
feat: add Android dialog mode for external downloads (#1362)
1 parent def54f8 commit c01d113

9 files changed

Lines changed: 427 additions & 21 deletions

File tree

ui/flutter/android/app/src/main/AndroidManifest.xml

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@
2727
android:theme="@style/LaunchTheme"
2828
android:windowSoftInputMode="adjustResize">
2929

30+
<!-- Specifies an Android theme to apply to this Activity as soon as
31+
the Android process has started. This theme is visible to the user
32+
while the Flutter UI initializes. After that, this theme continues
33+
to determine the Window background behind the Flutter UI. -->
34+
<meta-data
35+
android:name="io.flutter.embedding.android.NormalTheme"
36+
android:resource="@style/NormalTheme" />
37+
<intent-filter>
38+
<action android:name="android.intent.action.MAIN" />
39+
<category android:name="android.intent.category.LAUNCHER" />
40+
</intent-filter>
41+
</activity>
42+
43+
<activity
44+
android:name=".CreateDialogActivity"
45+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
46+
android:excludeFromRecents="true"
47+
android:exported="true"
48+
android:finishOnTaskLaunch="true"
49+
android:hardwareAccelerated="true"
50+
android:launchMode="singleTask"
51+
android:theme="@style/DialogLaunchTheme"
52+
android:windowSoftInputMode="adjustResize">
53+
3054
<intent-filter>
3155
<action android:name="android.intent.action.VIEW" />
3256
<category android:name="android.intent.category.DEFAULT" />
@@ -83,25 +107,17 @@
83107
<action android:name="android.intent.action.VIEW_DOWNLOADS" />
84108
<category android:name="android.intent.category.DEFAULT" />
85109
</intent-filter>
86-
87-
<!-- Specifies an Android theme to apply to this Activity as soon as
88-
the Android process has started. This theme is visible to the user
89-
while the Flutter UI initializes. After that, this theme continues
90-
to determine the Window background behind the Flutter UI. -->
91-
<meta-data
92-
android:name="io.flutter.embedding.android.NormalTheme"
93-
android:resource="@style/NormalTheme" />
94-
<intent-filter>
95-
<action android:name="android.intent.action.MAIN" />
96-
<category android:name="android.intent.category.LAUNCHER" />
97-
</intent-filter>
98110
<intent-filter>
99111
<action android:name="android.intent.action.SEND" />
100112
<action android:name="android.intent.action.SEND_MULTIPLE" />
101113

102114
<category android:name="android.intent.category.DEFAULT" />
103115
<data android:mimeType="*/*" />
104116
</intent-filter>
117+
118+
<meta-data
119+
android:name="io.flutter.embedding.android.NormalTheme"
120+
android:resource="@style/DialogNormalTheme" />
105121
</activity>
106122

107123
<!-- Add android:stopWithTask option only when necessary. -->
@@ -116,4 +132,4 @@
116132
android:name="flutterEmbedding"
117133
android:value="2" />
118134
</application>
119-
</manifest>
135+
</manifest>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.gopeed.gopeed
2+
3+
import android.os.Bundle
4+
import android.view.Gravity
5+
import android.view.WindowManager
6+
import io.flutter.embedding.android.FlutterActivityLaunchConfigs
7+
8+
class CreateDialogActivity : MainActivity() {
9+
override fun isDialogMode(): Boolean = true
10+
11+
override fun getBackgroundMode(): FlutterActivityLaunchConfigs.BackgroundMode {
12+
return FlutterActivityLaunchConfigs.BackgroundMode.transparent
13+
}
14+
15+
override fun onCreate(savedInstanceState: Bundle?) {
16+
super.onCreate(savedInstanceState)
17+
18+
val metrics = resources.displayMetrics
19+
val width = (metrics.widthPixels * 0.92f).toInt()
20+
val maxHeight = (metrics.heightPixels * 0.62f).toInt()
21+
val preferredHeight = (420 * metrics.density).toInt()
22+
23+
window.setGravity(Gravity.CENTER)
24+
window.setDimAmount(0.28f)
25+
window.setLayout(width, minOf(maxHeight, preferredHeight))
26+
27+
val attrs = window.attributes
28+
attrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND
29+
window.attributes = attrs
30+
}
31+
}

ui/flutter/android/app/src/main/kotlin/com/gopeed/gopeed/MainActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import io.flutter.embedding.engine.FlutterEngine
77
import io.flutter.plugin.common.MethodChannel
88
import io.flutter.plugin.common.StandardMethodCodec
99

10-
class MainActivity : FlutterActivity() {
10+
open class MainActivity : FlutterActivity() {
1111
private val CHANNEL = "gopeed.com/libgopeed"
1212

13+
protected open fun isDialogMode(): Boolean = false
14+
1315
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
1416
super.configureFlutterEngine(flutterEngine)
1517
val taskQueue =
@@ -34,6 +36,9 @@ class MainActivity : FlutterActivity() {
3436
Libgopeed.stop()
3537
result.success(null)
3638
}
39+
"isDialogMode" -> {
40+
result.success(isDialogMode())
41+
}
3742
else -> {
3843
result.notImplemented()
3944
}

ui/flutter/android/app/src/main/res/values-night/styles.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,18 @@
1515
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
1616
<item name="android:windowBackground">?android:colorBackground</item>
1717
</style>
18+
<style name="DialogLaunchTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
19+
<item name="android:windowIsTranslucent">true</item>
20+
<item name="android:windowIsFloating">true</item>
21+
<item name="android:windowBackground">@android:color/transparent</item>
22+
<item name="android:backgroundDimEnabled">true</item>
23+
<item name="android:windowNoTitle">true</item>
24+
</style>
25+
<style name="DialogNormalTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
26+
<item name="android:windowIsTranslucent">true</item>
27+
<item name="android:windowIsFloating">true</item>
28+
<item name="android:windowBackground">@android:color/transparent</item>
29+
<item name="android:backgroundDimEnabled">true</item>
30+
<item name="android:windowNoTitle">true</item>
31+
</style>
1832
</resources>

ui/flutter/android/app/src/main/res/values/styles.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,18 @@
1515
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
1616
<item name="android:windowBackground">?android:colorBackground</item>
1717
</style>
18+
<style name="DialogLaunchTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
19+
<item name="android:windowIsTranslucent">true</item>
20+
<item name="android:windowIsFloating">true</item>
21+
<item name="android:windowBackground">@android:color/transparent</item>
22+
<item name="android:backgroundDimEnabled">true</item>
23+
<item name="android:windowNoTitle">true</item>
24+
</style>
25+
<style name="DialogNormalTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
26+
<item name="android:windowIsTranslucent">true</item>
27+
<item name="android:windowIsFloating">true</item>
28+
<item name="android:windowBackground">@android:color/transparent</item>
29+
<item name="android:backgroundDimEnabled">true</item>
30+
<item name="android:windowNoTitle">true</item>
31+
</style>
1832
</resources>

ui/flutter/lib/app/modules/app/controllers/app_controller.dart

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'dart:ui';
55

66
import 'package:app_links/app_links.dart';
77
import 'package:flutter/material.dart';
8+
import 'package:flutter/services.dart';
89
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
910
import 'package:get/get.dart';
1011
import 'package:launch_at_startup/launch_at_startup.dart';
@@ -69,6 +70,7 @@ class PendingUpdateTask {
6970

7071
class AppController extends GetxController with WindowListener, TrayListener {
7172
static StartConfig? _defaultStartConfig;
73+
static const _nativeChannel = MethodChannel('gopeed.com/libgopeed');
7274

7375
/// Command line --hidden flag passed from main.dart
7476
final bool hiddenFromArgs;
@@ -79,6 +81,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
7981
final startConfig = StartConfig().obs;
8082
final runningPort = 0.obs;
8183
final downloaderConfig = DownloaderConfig().obs;
84+
var androidDialogMode = false;
8285

8386
/// The task that is pending URL update via listen mode.
8487
/// Stored here in AppController to persist across page navigations.
@@ -124,9 +127,11 @@ class AppController extends GetxController with WindowListener, TrayListener {
124127
void onClose() {
125128
_linkSubscription?.cancel();
126129
trayManager.removeListener(this);
127-
HostRpcService.instance.stop();
128-
WebViewRpcService.instance.stop();
129-
LibgopeedBoot.instance.stop();
130+
if (!androidDialogMode) {
131+
HostRpcService.instance.stop();
132+
WebViewRpcService.instance.stop();
133+
LibgopeedBoot.instance.stop();
134+
}
130135
}
131136

132137
@override
@@ -182,6 +187,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
182187
// For web, just show window
183188
return;
184189
}
190+
androidDialogMode = await _isAndroidDialogMode();
185191

186192
// Handle deep link
187193
_appLinks = AppLinks();
@@ -241,6 +247,18 @@ class AppController extends GetxController with WindowListener, TrayListener {
241247
}
242248
}
243249

250+
Future<bool> _isAndroidDialogMode() async {
251+
if (!Util.isAndroid()) {
252+
return false;
253+
}
254+
try {
255+
return await _nativeChannel.invokeMethod<bool>('isDialogMode') ?? false;
256+
} catch (e) {
257+
logger.w("get android dialog mode fail", e);
258+
return false;
259+
}
260+
}
261+
244262
Future<void> _initWindows() async {
245263
if (!Util.isDesktop()) {
246264
return;
@@ -440,9 +458,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
440458
} else {
441459
path = (await toFile(uri.toString())).path;
442460
}
443-
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
444-
arguments: RedirectArgs(Routes.CREATE,
445-
arguments: CreateTask(req: Request(url: path))));
461+
_handleToCreate0(CreateTask(req: Request(url: path)));
446462
}
447463

448464
String runningAddress() {
@@ -697,8 +713,10 @@ class AppController extends GetxController with WindowListener, TrayListener {
697713
}
698714

699715
_handleToCreate0(CreateTask createTaskParams) {
716+
final targetRoute =
717+
androidDialogMode ? Routes.QUICK_CREATE : Routes.CREATE;
700718
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
701-
arguments: RedirectArgs(Routes.CREATE, arguments: createTaskParams));
719+
arguments: RedirectArgs(targetRoute, arguments: createTaskParams));
702720
}
703721

704722
_handleToExtension(String params) {

0 commit comments

Comments
 (0)