From af64efec00256fd68e7189ac72f3fd27cecb4efe Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Thu, 14 Dec 2023 14:50:10 +0330 Subject: [PATCH] Add android dynamic notification --- .../kotlin/com/hiddify/hiddify/Application.kt | 1 + .../com/hiddify/hiddify/MethodHandler.kt | 24 ++-- .../kotlin/com/hiddify/hiddify/Settings.kt | 9 ++ .../com/hiddify/hiddify/bg/BoxService.kt | 65 +++++---- .../hiddify/hiddify/bg/ServiceNotification.kt | 133 +++++++++++++----- .../hiddify/hiddify/constant/SettingsKey.kt | 2 + assets/translations/strings_en.i18n.json | 3 +- assets/translations/strings_fa.i18n.json | 3 +- assets/translations/strings_ru.i18n.json | 3 +- assets/translations/strings_tr.i18n.json | 3 +- assets/translations/strings_zh-CN.i18n.json | 3 +- lib/core/preferences/general_preferences.dart | 17 +++ .../data/connection_repository.dart | 6 + .../notifier/connection_notifier.dart | 19 ++- .../profile/notifier/profile_notifier.dart | 2 +- .../widgets/general_setting_tiles.dart | 13 ++ lib/singbox/service/ffi_singbox_service.dart | 12 +- .../service/platform_singbox_service.dart | 16 ++- lib/singbox/service/singbox_service.dart | 12 +- 19 files changed, 251 insertions(+), 95 deletions(-) diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/Application.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/Application.kt index ef2ce64a..939c25f1 100644 --- a/android/app/src/main/kotlin/com/hiddify/hiddify/Application.kt +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/Application.kt @@ -37,6 +37,7 @@ class Application : Application() { val connectivity by lazy { application.getSystemService()!! } val packageManager by lazy { application.packageManager } val powerManager by lazy { application.getSystemService()!! } + val notificationManager by lazy { application.getSystemService()!! } } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt index f2656c25..6a7f4761 100644 --- a/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/MethodHandler.kt @@ -2,12 +2,10 @@ package com.hiddify.hiddify import android.util.Log import com.hiddify.hiddify.bg.BoxService -import com.hiddify.hiddify.constant.Alert import com.hiddify.hiddify.constant.Status import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.StandardMethodCodec import io.nekohasekai.libbox.Libbox import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -15,7 +13,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin, - MethodChannel.MethodCallHandler { + MethodChannel.MethodCallHandler { private var channel: MethodChannel? = null companion object { @@ -37,8 +35,8 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin, override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel( - flutterPluginBinding.binaryMessenger, - channelName, + flutterPluginBinding.binaryMessenger, + channelName, ) channel!!.setMethodCallHandler(this) } @@ -92,6 +90,7 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin, result.runCatching { val args = call.arguments as Map<*, *> Settings.activeConfigPath = args["path"] as String? ?: "" + Settings.activeProfileName = args["name"] as String? ?: "" val mainActivity = MainActivity.instance val started = mainActivity.serviceStatus.value == Status.Started if (started) { @@ -124,6 +123,7 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin, result.runCatching { val args = call.arguments as Map<*, *> Settings.activeConfigPath = args["path"] as String? ?: "" + Settings.activeProfileName = args["name"] as String? ?: "" val mainActivity = MainActivity.instance val started = mainActivity.serviceStatus.value == Status.Started if (!started) return@launch success(true) @@ -150,10 +150,10 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin, result.runCatching { val args = call.arguments as Map<*, *> Libbox.newStandaloneCommandClient() - .selectOutbound( - args["groupTag"] as String, - args["outboundTag"] as String - ) + .selectOutbound( + args["groupTag"] as String, + args["outboundTag"] as String + ) success(true) } } @@ -164,9 +164,9 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin, result.runCatching { val args = call.arguments as Map<*, *> Libbox.newStandaloneCommandClient() - .urlTest( - args["groupTag"] as String - ) + .urlTest( + args["groupTag"] as String + ) success(true) } } diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/Settings.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/Settings.kt index b5e64c2f..c841ddef 100644 --- a/android/app/src/main/kotlin/com/hiddify/hiddify/Settings.kt +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/Settings.kt @@ -51,6 +51,10 @@ object Settings { get() = preferences.getString(SettingsKey.ACTIVE_CONFIG_PATH, "")!! set(value) = preferences.edit().putString(SettingsKey.ACTIVE_CONFIG_PATH, value).apply() + var activeProfileName: String + get() = preferences.getString(SettingsKey.ACTIVE_PROFILE_NAME, "")!! + set(value) = preferences.edit().putString(SettingsKey.ACTIVE_PROFILE_NAME, value).apply() + var serviceMode: String get() = preferences.getString(SettingsKey.SERVICE_MODE, ServiceMode.NORMAL)!! set(value) = preferences.edit().putString(SettingsKey.SERVICE_MODE, value).apply() @@ -71,6 +75,11 @@ object Settings { set(value) = preferences.edit().putBoolean(SettingsKey.DISABLE_MEMORY_LIMIT, value).apply() + var dynamicNotification: Boolean + get() = preferences.getBoolean(SettingsKey.DYNAMIC_NOTIFICATION, true) + set(value) = + preferences.edit().putBoolean(SettingsKey.DYNAMIC_NOTIFICATION, value).apply() + var systemProxyEnabled: Boolean get() = preferences.getBoolean(SettingsKey.SYSTEM_PROXY_ENABLED, true) set(value) = diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt index 6268b739..115280d4 100644 --- a/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt @@ -23,7 +23,6 @@ import io.nekohasekai.libbox.BoxService import io.nekohasekai.libbox.CommandServer import io.nekohasekai.libbox.CommandServerHandler import io.nekohasekai.libbox.Libbox -import io.nekohasekai.libbox.PProfServer import io.nekohasekai.libbox.PlatformInterface import io.nekohasekai.libbox.SystemProxyStatus import io.nekohasekai.mobile.Mobile @@ -36,8 +35,8 @@ import kotlinx.coroutines.withContext import java.io.File class BoxService( - private val service: Service, - private val platformInterface: PlatformInterface + private val service: Service, + private val platformInterface: PlatformInterface ) : CommandServerHandler { companion object { @@ -72,8 +71,8 @@ class BoxService( } } - fun buildConfig(path: String, options: String):String { - return Mobile.buildConfig(path, options) + fun buildConfig(path: String, options: String): String { + return Mobile.buildConfig(path, options) } fun start() { @@ -87,17 +86,17 @@ class BoxService( fun stop() { Application.application.sendBroadcast( - Intent(Action.SERVICE_CLOSE).setPackage( - Application.application.packageName - ) + Intent(Action.SERVICE_CLOSE).setPackage( + Application.application.packageName + ) ) } fun reload() { Application.application.sendBroadcast( - Intent(Action.SERVICE_RELOAD).setPackage( - Application.application.packageName - ) + Intent(Action.SERVICE_RELOAD).setPackage( + Application.application.packageName + ) ) } } @@ -106,10 +105,9 @@ class BoxService( private val status = MutableLiveData(Status.Stopped) private val binder = ServiceBinder(status) - private val notification = ServiceNotification(service) + private val notification = ServiceNotification(status, service) private var boxService: BoxService? = null private var commandServer: CommandServer? = null - private var pprofServer: PProfServer? = null private var receiverRegistered = false private val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -133,11 +131,12 @@ class BoxService( private fun startCommandServer() { val commandServer = - CommandServer(this, 300) + CommandServer(this, 300) commandServer.start() this.commandServer = commandServer } + private var activeProfileName = "" private suspend fun startService(delayStart: Boolean = false) { try { Log.d(TAG, "starting service") @@ -148,6 +147,8 @@ class BoxService( return } + activeProfileName = Settings.activeProfileName + val configOptions = Settings.configOptions if (configOptions.isBlank()) { stopAndAlert(Alert.EmptyConfiguration) @@ -191,6 +192,10 @@ class BoxService( boxService = newService commandServer?.setService(boxService) status.postValue(Status.Started) + + withContext(Dispatchers.Main) { + notification.show(activeProfileName) + } } catch (e: Exception) { stopAndAlert(Alert.StartService, e.message) return @@ -198,23 +203,24 @@ class BoxService( } override fun serviceReload() { + notification.close() status.postValue(Status.Starting) + val pfd = fileDescriptor + if (pfd != null) { + pfd.close() + fileDescriptor = null + } + commandServer?.setService(null) + boxService?.apply { + runCatching { + close() + }.onFailure { + writeLog("service: error when closing: $it") + } + Seq.destroyRef(refnum) + } + boxService = null runBlocking { - val pfd = fileDescriptor - if (pfd != null) { - pfd.close() - fileDescriptor = null - } - commandServer?.setService(null) - boxService?.apply { - runCatching { - close() - }.onFailure { - writeLog("service: error when closing: $it") - } - Seq.destroyRef(refnum) - } - boxService = null startService(true) } } @@ -311,7 +317,6 @@ class BoxService( receiverRegistered = true } - notification.show() GlobalScope.launch(Dispatchers.IO) { Settings.startedByUser = true initialize() diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/bg/ServiceNotification.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/bg/ServiceNotification.kt index d899cc82..4dcdd67e 100644 --- a/android/app/src/main/kotlin/com/hiddify/hiddify/bg/ServiceNotification.kt +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/bg/ServiceNotification.kt @@ -4,21 +4,33 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat +import androidx.lifecycle.MutableLiveData import com.hiddify.hiddify.Application import com.hiddify.hiddify.MainActivity import com.hiddify.hiddify.R +import com.hiddify.hiddify.Settings import com.hiddify.hiddify.constant.Action +import com.hiddify.hiddify.constant.Status +import com.hiddify.hiddify.utils.CommandClient +import io.nekohasekai.libbox.Libbox +import io.nekohasekai.libbox.StatusMessage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.withContext -class ServiceNotification(private val service: Service) { +class ServiceNotification(private val status: MutableLiveData, private val service: Service) : BroadcastReceiver(), CommandClient.Handler { companion object { private const val notificationId = 1 private const val notificationChannel = "service" private val flags = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 fun checkPermission(): Boolean { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { @@ -29,49 +41,102 @@ class ServiceNotification(private val service: Service) { } - private val notification by lazy { - NotificationCompat.Builder(service, notificationChannel).setWhen(0) - .setContentTitle("Hiddify Next") - .setContentText("service running").setOnlyAlertOnce(true) - .setSmallIcon(R.drawable.ic_stat_logo) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setContentIntent( - PendingIntent.getActivity( - service, - 0, - Intent( - service, - MainActivity::class.java - ).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), - flags - ) - ) - .setPriority(NotificationCompat.PRIORITY_LOW).apply { - addAction( - NotificationCompat.Action.Builder( - 0, service.getText(R.string.stop), PendingIntent.getBroadcast( - service, - 0, - Intent(Action.SERVICE_CLOSE).setPackage(service.packageName), - flags + private val commandClient = + CommandClient(GlobalScope, CommandClient.ConnectionType.Status, this) + private var receiverRegistered = false + + + private val notificationBuilder by lazy { + NotificationCompat.Builder(service, notificationChannel) + .setShowWhen(false) + .setOngoing(true) + .setContentTitle("Hiddify Next") + .setOnlyAlertOnce(true) + .setSmallIcon(R.drawable.ic_stat_logo) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setContentIntent( + PendingIntent.getActivity( + service, + 0, + Intent( + service, + MainActivity::class.java + ).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), + flags ) - ).build() ) - } + .setPriority(NotificationCompat.PRIORITY_LOW).apply { + addAction( + NotificationCompat.Action.Builder( + 0, service.getText(R.string.stop), PendingIntent.getBroadcast( + service, + 0, + Intent(Action.SERVICE_CLOSE).setPackage(service.packageName), + flags + ) + ).build() + ) + } } - fun show() { + suspend fun show(profileName: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Application.notification.createNotificationChannel( - NotificationChannel( - notificationChannel, "hiddify service", NotificationManager.IMPORTANCE_LOW - ) + NotificationChannel( + notificationChannel, "hiddify service", NotificationManager.IMPORTANCE_LOW + ) ) } - service.startForeground(notificationId, notification.build()) + service.startForeground( + notificationId, notificationBuilder + .setContentTitle(profileName.takeIf { it.isNotBlank() } ?: "Hiddify Next") + .setContentText("service started").build() + ) + withContext(Dispatchers.IO) { + if (Settings.dynamicNotification) { + commandClient.connect() + withContext(Dispatchers.Main) { + registerReceiver() + } + } + } + } + + private fun registerReceiver() { + service.registerReceiver(this, IntentFilter().apply { + addAction(Intent.ACTION_SCREEN_ON) + addAction(Intent.ACTION_SCREEN_OFF) + }) + receiverRegistered = true + } + + override fun updateStatus(status: StatusMessage) { + val content = + Libbox.formatBytes(status.uplink) + "/s ↑\t" + Libbox.formatBytes(status.downlink) + "/s ↓" + Application.notificationManager.notify( + notificationId, + notificationBuilder.setContentText(content).build() + ) + } + + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + Intent.ACTION_SCREEN_ON -> { + commandClient.connect() + } + + Intent.ACTION_SCREEN_OFF -> { + commandClient.disconnect() + } + } } fun close() { + commandClient.disconnect() ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE) + if (receiverRegistered) { + service.unregisterReceiver(this) + receiverRegistered = false + } } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/constant/SettingsKey.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/constant/SettingsKey.kt index 21e7dd0f..96782ad8 100644 --- a/android/app/src/main/kotlin/com/hiddify/hiddify/constant/SettingsKey.kt +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/constant/SettingsKey.kt @@ -4,6 +4,7 @@ object SettingsKey { private const val KEY_PREFIX = "flutter." const val ACTIVE_CONFIG_PATH = "${KEY_PREFIX}active_config_path" + const val ACTIVE_PROFILE_NAME = "${KEY_PREFIX}active_profile_name" const val SERVICE_MODE = "${KEY_PREFIX}service_mode" const val CONFIG_OPTIONS = "config_options_json" @@ -15,6 +16,7 @@ object SettingsKey { const val DEBUG_MODE = "${KEY_PREFIX}debug_mode" const val ENABLE_TUN = "${KEY_PREFIX}enable-tun" const val DISABLE_MEMORY_LIMIT = "${KEY_PREFIX}disable_memory_limit" + const val DYNAMIC_NOTIFICATION = "${KEY_PREFIX}dynamic_notification" const val SYSTEM_PROXY_ENABLED = "${KEY_PREFIX}system_proxy_enabled" // cache diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json index 3bee715c..27061da4 100644 --- a/assets/translations/strings_en.i18n.json +++ b/assets/translations/strings_en.i18n.json @@ -155,7 +155,8 @@ "silentStart": "Silent Start", "openWorkingDir": "Open Working Directory", "ignoreBatteryOptimizations": "Disable Battery Optimization", - "ignoreBatteryOptimizationsMsg": "Remove restrictions for optimal VPN performance" + "ignoreBatteryOptimizationsMsg": "Remove restrictions for optimal VPN performance", + "dynamicNotification": "Display speed in notification" }, "advanced": { "sectionTitle": "Advanced", diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index 40bb8a96..a569367c 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -155,7 +155,8 @@ "silentStart": "اجرای ساکت", "openWorkingDir": "باز کردن دایرکتوری کاری", "ignoreBatteryOptimizations": "غیرفعال کردن بهینه‌سازی باتری", - "ignoreBatteryOptimizationsMsg": "حذف محدودیت‌ها برای عملکرد بهتر VPN" + "ignoreBatteryOptimizationsMsg": "حذف محدودیت‌ها برای عملکرد بهتر VPN", + "dynamicNotification": "نمایش سرعت در نوتیفیکیشن" }, "advanced": { "sectionTitle": "پیشرفته", diff --git a/assets/translations/strings_ru.i18n.json b/assets/translations/strings_ru.i18n.json index 19662b03..abc7af4f 100644 --- a/assets/translations/strings_ru.i18n.json +++ b/assets/translations/strings_ru.i18n.json @@ -155,7 +155,8 @@ "silentStart": "Тихий запуск", "openWorkingDir": "Открыть рабочую папку", "ignoreBatteryOptimizations": "Отключить оптимизацию батареи", - "ignoreBatteryOptimizationsMsg": "Отключение ограничений для оптимальной производительности VPN." + "ignoreBatteryOptimizationsMsg": "Отключение ограничений для оптимальной производительности VPN.", + "dynamicNotification": "Отображение скорости в уведомлении" }, "advanced": { "sectionTitle": "Расширенные", diff --git a/assets/translations/strings_tr.i18n.json b/assets/translations/strings_tr.i18n.json index 1114ae24..bd7f4d29 100644 --- a/assets/translations/strings_tr.i18n.json +++ b/assets/translations/strings_tr.i18n.json @@ -155,7 +155,8 @@ "silentStart": "Sessiz Başlatma", "openWorkingDir": "Çalışma Dizinini Aç", "ignoreBatteryOptimizations": "Pil Optimizasyonunu Devre Dışı Bırak", - "ignoreBatteryOptimizationsMsg": "Optimum VPN performansı için kısıtlamaları kaldırın" + "ignoreBatteryOptimizationsMsg": "Optimum VPN performansı için kısıtlamaları kaldırın", + "dynamicNotification": "Bildirimde hızı göster" }, "advanced": { "sectionTitle": "Gelişmiş", diff --git a/assets/translations/strings_zh-CN.i18n.json b/assets/translations/strings_zh-CN.i18n.json index 73b45786..15391fbd 100644 --- a/assets/translations/strings_zh-CN.i18n.json +++ b/assets/translations/strings_zh-CN.i18n.json @@ -155,7 +155,8 @@ "silentStart": "静默启动", "openWorkingDir": "打开工作目录", "ignoreBatteryOptimizations": "禁用电池优化", - "ignoreBatteryOptimizationsMsg": "消除限制以获得最佳 VPN 性能" + "ignoreBatteryOptimizationsMsg": "消除限制以获得最佳 VPN 性能", + "dynamicNotification": "在通知中显示速度" }, "advanced": { "sectionTitle": "高级选项", diff --git a/lib/core/preferences/general_preferences.dart b/lib/core/preferences/general_preferences.dart index af0a8dbe..bf401046 100644 --- a/lib/core/preferences/general_preferences.dart +++ b/lib/core/preferences/general_preferences.dart @@ -184,3 +184,20 @@ class MarkNewProfileActive extends _$MarkNewProfileActive { return _pref.update(value); } } + +@riverpod +class DynamicNotification extends _$DynamicNotification { + late final _pref = Pref( + ref.watch(sharedPreferencesProvider).requireValue, + "dynamic_notification", + true, + ); + + @override + bool build() => _pref.getValue(); + + Future update(bool value) { + state = value; + return _pref.update(value); + } +} diff --git a/lib/features/connection/data/connection_repository.dart b/lib/features/connection/data/connection_repository.dart index 8561253a..5271b364 100644 --- a/lib/features/connection/data/connection_repository.dart +++ b/lib/features/connection/data/connection_repository.dart @@ -19,11 +19,13 @@ abstract interface class ConnectionRepository { Stream watchConnectionStatus(); TaskEither connect( String fileName, + String profileName, bool disableMemoryLimit, ); TaskEither disconnect(); TaskEither reconnect( String fileName, + String profileName, bool disableMemoryLimit, ); } @@ -144,6 +146,7 @@ class ConnectionRepositoryImpl @override TaskEither connect( String fileName, + String profileName, bool disableMemoryLimit, ) { return TaskEither.Do( @@ -173,6 +176,7 @@ class ConnectionRepositoryImpl singbox .start( profilePathResolver.file(fileName).path, + profileName, disableMemoryLimit, ) .mapLeft(UnexpectedConnectionFailure.new), @@ -192,6 +196,7 @@ class ConnectionRepositoryImpl @override TaskEither reconnect( String fileName, + String profileName, bool disableMemoryLimit, ) { return exceptionHandler( @@ -202,6 +207,7 @@ class ConnectionRepositoryImpl () => singbox .restart( profilePathResolver.file(fileName).path, + profileName, disableMemoryLimit, ) .mapLeft(UnexpectedConnectionFailure.new), diff --git a/lib/features/connection/notifier/connection_notifier.dart b/lib/features/connection/notifier/connection_notifier.dart index 21c22339..c1d3f1ec 100644 --- a/lib/features/connection/notifier/connection_notifier.dart +++ b/lib/features/connection/notifier/connection_notifier.dart @@ -3,6 +3,7 @@ import 'package:hiddify/core/preferences/service_preferences.dart'; import 'package:hiddify/features/connection/data/connection_data_providers.dart'; import 'package:hiddify/features/connection/data/connection_repository.dart'; import 'package:hiddify/features/connection/model/connection_status.dart'; +import 'package:hiddify/features/profile/model/profile_entity.dart'; import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -20,7 +21,7 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger { if (previous == null) return; final shouldReconnect = next == null || previous.id != next.id; if (shouldReconnect) { - await reconnect(next?.id); + await reconnect(next); } }, ); @@ -59,16 +60,20 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger { } } - Future reconnect(String? profileId) async { + Future reconnect(ProfileEntity? profile) async { if (state case AsyncData(:final value) when value == const Connected()) { - if (profileId == null) { + if (profile == null) { loggy.info("no active profile, disconnecting"); return _disconnect(); } loggy.info("active profile changed, reconnecting"); await ref.read(startedByUserProvider.notifier).update(true); await _connectionRepo - .reconnect(profileId, ref.read(disableMemoryLimitProvider)) + .reconnect( + profile.id, + profile.name, + ref.read(disableMemoryLimitProvider), + ) .mapLeft((err) { loggy.warning("error reconnecting", err); state = AsyncError(err, StackTrace.current); @@ -90,7 +95,11 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger { Future _connect() async { final activeProfile = await ref.read(activeProfileProvider.future); await _connectionRepo - .connect(activeProfile!.id, ref.read(disableMemoryLimitProvider)) + .connect( + activeProfile!.id, + activeProfile.name, + ref.read(disableMemoryLimitProvider), + ) .mapLeft((err) async { loggy.warning("error connecting", err); await ref.read(startedByUserProvider.notifier).update(false); diff --git a/lib/features/profile/notifier/profile_notifier.dart b/lib/features/profile/notifier/profile_notifier.dart index 7514b24b..e9255f75 100644 --- a/lib/features/profile/notifier/profile_notifier.dart +++ b/lib/features/profile/notifier/profile_notifier.dart @@ -128,7 +128,7 @@ class UpdateProfile extends _$UpdateProfile with AppLogger { if (active != null && active.id == profile.id) { await ref .read(connectionNotifierProvider.notifier) - .reconnect(profile.id); + .reconnect(profile); } }); return unit; diff --git a/lib/features/settings/widgets/general_setting_tiles.dart b/lib/features/settings/widgets/general_setting_tiles.dart index 6c246c43..d3f45807 100644 --- a/lib/features/settings/widgets/general_setting_tiles.dart +++ b/lib/features/settings/widgets/general_setting_tiles.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/localization/translations.dart'; @@ -73,6 +75,17 @@ class GeneralSettingTiles extends HookConsumerWidget { } }, ), + if (Platform.isAndroid) + SwitchListTile( + title: Text(t.settings.general.dynamicNotification), + secondary: const Icon(Icons.speed), + value: ref.watch(dynamicNotificationProvider), + onChanged: (value) async { + await ref + .read(dynamicNotificationProvider.notifier) + .update(value); + }, + ), if (PlatformUtils.isDesktop) ...[ SwitchListTile( title: Text(t.settings.general.autoStart), diff --git a/lib/singbox/service/ffi_singbox_service.dart b/lib/singbox/service/ffi_singbox_service.dart index e7bbeede..a3a695bc 100644 --- a/lib/singbox/service/ffi_singbox_service.dart +++ b/lib/singbox/service/ffi_singbox_service.dart @@ -158,7 +158,11 @@ class FFISingboxService with InfraLogger implements SingboxService { } @override - TaskEither start(String configPath, bool disableMemoryLimit) { + TaskEither start( + String configPath, + String name, + bool disableMemoryLimit, + ) { loggy.debug("starting, memory limit: [${!disableMemoryLimit}]"); return TaskEither( () => CombineWorker().execute( @@ -195,7 +199,11 @@ class FFISingboxService with InfraLogger implements SingboxService { } @override - TaskEither restart(String configPath, bool disableMemoryLimit) { + TaskEither restart( + String configPath, + String name, + bool disableMemoryLimit, + ) { loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]"); return TaskEither( () => CombineWorker().execute( diff --git a/lib/singbox/service/platform_singbox_service.dart b/lib/singbox/service/platform_singbox_service.dart index 536128a7..e5daaa76 100644 --- a/lib/singbox/service/platform_singbox_service.dart +++ b/lib/singbox/service/platform_singbox_service.dart @@ -89,13 +89,17 @@ class PlatformSingboxService with InfraLogger implements SingboxService { } @override - TaskEither start(String path, bool disableMemoryLimit) { + TaskEither start( + String path, + String name, + bool disableMemoryLimit, + ) { return TaskEither( () async { loggy.debug("starting"); await _methodChannel.invokeMethod( "start", - {"path": path}, + {"path": path, "name": name}, ); return right(unit); }, @@ -114,13 +118,17 @@ class PlatformSingboxService with InfraLogger implements SingboxService { } @override - TaskEither restart(String path, bool disableMemoryLimit) { + TaskEither restart( + String path, + String name, + bool disableMemoryLimit, + ) { return TaskEither( () async { loggy.debug("restarting"); await _methodChannel.invokeMethod( "restart", - {"path": path}, + {"path": path, "name": name}, ); return right(unit); }, diff --git a/lib/singbox/service/singbox_service.dart b/lib/singbox/service/singbox_service.dart index 3ad2d8c5..bc21718b 100644 --- a/lib/singbox/service/singbox_service.dart +++ b/lib/singbox/service/singbox_service.dart @@ -38,11 +38,19 @@ abstract interface class SingboxService { String path, ); - TaskEither start(String path, bool disableMemoryLimit); + TaskEither start( + String path, + String name, + bool disableMemoryLimit, + ); TaskEither stop(); - TaskEither restart(String path, bool disableMemoryLimit); + TaskEither restart( + String path, + String name, + bool disableMemoryLimit, + ); Stream> watchOutbounds();