From 2d9dc044f92d1921f5b8b9dc93035f82f48bcd5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:42:09 +0800 Subject: [PATCH] new: custom foreground service on Android (#556) --- android/app/build.gradle | 1 - android/app/src/main/AndroidManifest.xml | 14 ++- .../tech/lolli/toolbox/ForegroundService.kt | 31 +++--- .../kotlin/tech/lolli/toolbox/MainActivity.kt | 20 +++- lib/main.dart | 5 + lib/view/page/ssh/tab.dart | 99 +++++++++---------- 6 files changed, 95 insertions(+), 75 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b556ffd2..27376a3f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -53,7 +53,6 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "tech.lolli.toolbox" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 509e98e1..bd4fe308 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - + @@ -15,7 +16,8 @@ android:icon="@mipmap/ic_launcher" android:allowBackup="true" android:hasFragileUserData="true" - android:restoreAnyVersion="true"> + android:restoreAnyVersion="true" + tools:targetApi="q"> - - diff --git a/android/app/src/main/kotlin/tech/lolli/toolbox/ForegroundService.kt b/android/app/src/main/kotlin/tech/lolli/toolbox/ForegroundService.kt index 74a50257..ccff8c40 100644 --- a/android/app/src/main/kotlin/tech/lolli/toolbox/ForegroundService.kt +++ b/android/app/src/main/kotlin/tech/lolli/toolbox/ForegroundService.kt @@ -4,11 +4,9 @@ import android.app.* import android.content.Intent import android.os.Build import android.os.IBinder -import androidx.core.app.NotificationCompat -class MyForegroundService : Service() { - - private val CHANNEL_ID = "ForegroundServiceChannel" +class ForegroundService : Service() { + private val chanId = "ForegroundServiceChannel" override fun onCreate() { super.onCreate() @@ -31,8 +29,8 @@ class MyForegroundService : Service() { private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceChannel = NotificationChannel( - CHANNEL_ID, - CHANNEL_ID, + chanId, + chanId, NotificationManager.IMPORTANCE_DEFAULT ) val manager = getSystemService(NotificationManager::class.java) @@ -49,11 +47,20 @@ class MyForegroundService : Service() { PendingIntent.FLAG_IMMUTABLE ) - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("App is running") - .setContentText("Click to open the app") - .setSmallIcon(R.drawable.ic_notification) - .setContentIntent(pendingIntent) - .build() + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Notification.Builder(this, chanId) + .setContentTitle("App is running") + .setContentText("Click to open the app") + .setSmallIcon(R.mipmap.ic_launcher) + .setContentIntent(pendingIntent) + .build() + } else { + Notification.Builder(this) + .setContentTitle("App is running") + .setContentText("Click to open the app") + .setSmallIcon(R.mipmap.ic_launcher) + .setContentIntent(pendingIntent) + .build() + } } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/tech/lolli/toolbox/MainActivity.kt b/android/app/src/main/kotlin/tech/lolli/toolbox/MainActivity.kt index e3d2d13f..9bc17d37 100644 --- a/android/app/src/main/kotlin/tech/lolli/toolbox/MainActivity.kt +++ b/android/app/src/main/kotlin/tech/lolli/toolbox/MainActivity.kt @@ -1,6 +1,11 @@ package tech.lolli.toolbox import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.Manifest +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel @@ -18,7 +23,8 @@ class MainActivity: FlutterFragmentActivity() { result.success(null) } "startService" -> { - val serviceIntent = Intent(this, ForegroundService::class.java) + reqPerm() + val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent) } else { @@ -32,4 +38,16 @@ class MainActivity: FlutterFragmentActivity() { } } } + + private fun reqPerm() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return + if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + 123, + ) + } + } } diff --git a/lib/main.dart b/lib/main.dart index fbceb053..eb0e1bbf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:logging/logging.dart'; import 'package:server_box/app.dart'; +import 'package:server_box/core/channel/bg_run.dart'; import 'package:server_box/core/utils/sync/icloud.dart'; import 'package:server_box/core/utils/sync/webdav.dart'; import 'package:server_box/data/model/app/menu/server_func.dart'; @@ -109,6 +110,10 @@ void _doPlatformRelated() async { if (isAndroid) { // try switch to highest refresh rate FlutterDisplayMode.setHighRefreshRate(); + if (Stores.setting.bgRun.fetch()) { + Loggers.app.info('Start foreground service'); + BgRunMC.startService(); + } } final serversCount = Stores.server.box.keys.length; diff --git a/lib/view/page/ssh/tab.dart b/lib/view/page/ssh/tab.dart index be1f3fd6..d39484fd 100644 --- a/lib/view/page/ssh/tab.dart +++ b/lib/view/page/ssh/tab.dart @@ -259,11 +259,11 @@ class _AddPage extends StatelessWidget { @override Widget build(BuildContext context) { - const viewPadding = 3.0; + const viewPadding = 7.0; final viewWidth = context.media.size.width - 2 * viewPadding; final itemCount = ServerProvider.servers.length; - const itemPadding = 3.0; + const itemPadding = 1.0; const itemWidth = 150.0; const itemHeight = 50.0; @@ -272,62 +272,55 @@ class _AddPage extends StatelessWidget { max(viewWidth ~/ (visualCrossCount * itemPadding + itemWidth), 1); final mainCount = itemCount ~/ crossCount + 1; - return Center( - key: const Key('sshTabAddServer'), - child: ServerProvider.serverOrder.listenVal((order) { - if (order.isEmpty) { - return Center( - child: Text(libL10n.empty, textAlign: TextAlign.center), - ); - } + return ServerProvider.serverOrder.listenVal((order) { + if (order.isEmpty) { + return Center( + child: Text(libL10n.empty, textAlign: TextAlign.center), + ); + } - // Custom grid - return ListView( - padding: const EdgeInsets.all(viewPadding), - children: List.generate( - mainCount, - (rowIndex) => Row( - children: List.generate(crossCount, (columnIndex) { - final idx = rowIndex * crossCount + columnIndex; - final id = order.elementAtOrNull(idx); - if (id == null) return _placeholder; + // Custom grid + return ListView( + padding: const EdgeInsets.all(viewPadding), + children: List.generate( + mainCount, + (rowIndex) => Row( + children: List.generate(crossCount, (columnIndex) { + final idx = rowIndex * crossCount + columnIndex; + final id = order.elementAtOrNull(idx); + final spi = ServerProvider.pick(id: id)?.value.spi; + if (spi == null) return _placeholder; - final spi = ServerProvider.pick(id: order[idx])?.value.spi; - if (spi == null) return _placeholder; - - return Expanded( - child: Padding( - padding: const EdgeInsets.all(itemPadding), - child: CardX( - child: InkWell( - onTap: () => onTapInitCard(spi), - child: Container( - height: itemHeight, - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 17, right: 7), - child: Row( - children: [ - Expanded( - child: Text( - spi.name, - style: UIs.text18, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - const Icon(Icons.chevron_right) - ], + return Expanded( + child: Padding( + padding: const EdgeInsets.all(itemPadding), + child: InkWell( + onTap: () => onTapInitCard(spi), + child: Container( + height: itemHeight, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 17, right: 7), + child: Row( + children: [ + Expanded( + child: Text( + spi.name, + style: UIs.text18, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), - ), + const Icon(Icons.chevron_right) + ], ), ), - ), - ); - }), - ), + ).cardx, + ), + ); + }), ), - ); - }), - ); + ), + ); + }); } }