From b083930fa693f45aefae874bbc040748e9306cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 13 Feb 2026 22:16:50 +0800 Subject: [PATCH] Fix missing notification settings --- .../screen/settings/AppSettingsScreen.kt | 154 +++++++++++++++++- app/src/main/res/values-fa/strings.xml | 5 + app/src/main/res/values-ru-rRU/strings.xml | 5 + app/src/main/res/values-zh-rCN/strings.xml | 5 + app/src/main/res/values-zh-rTW/strings.xml | 5 + app/src/main/res/values/strings.xml | 5 + 6 files changed, 178 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt index 3785c43..959311a 100644 --- a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt +++ b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt @@ -1,5 +1,7 @@ package io.nekohasekai.sfa.compose.screen.settings +import android.app.NotificationChannel +import android.app.NotificationManager import android.content.Intent import android.net.Uri import android.os.Build @@ -25,8 +27,10 @@ import androidx.compose.material.icons.outlined.Autorenew import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.NewReleases +import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.Speed import androidx.compose.material.icons.outlined.SystemUpdateAlt import androidx.compose.material3.AlertDialog import androidx.compose.material3.Badge @@ -62,6 +66,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect import androidx.navigation.NavController +import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.BuildConfig import io.nekohasekai.sfa.R import io.nekohasekai.sfa.compose.component.UpdateAvailableDialog @@ -121,13 +126,30 @@ fun AppSettingsScreen(navController: NavController) { var downloadError by remember { mutableStateOf(null) } var showUpdateAvailableDialog by remember { mutableStateOf(false) } + var notificationEnabled by remember { mutableStateOf(true) } + var dynamicNotification by remember { mutableStateOf(Settings.dynamicNotification) } + var showDisableNotificationDialog by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { HookStatusClient.refresh() } - // Re-check method availability when returning from background (e.g., after granting permission) + // Re-check states when returning from background (e.g., after granting permission) LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { HookStatusClient.refresh() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Application.notification.createNotificationChannel( + NotificationChannel( + "service", + "Service Notifications", + NotificationManager.IMPORTANCE_LOW, + ), + ) + val channel = Application.notification.getNotificationChannel("service") + notificationEnabled = channel?.importance != NotificationManager.IMPORTANCE_NONE + } else { + notificationEnabled = Application.notification.areNotificationsEnabled() + } if (silentInstallEnabled) { scope.launch { val success = withContext(Dispatchers.IO) { @@ -239,6 +261,51 @@ fun AppSettingsScreen(navController: NavController) { ) } + if (showDisableNotificationDialog) { + AlertDialog( + onDismissRequest = { showDisableNotificationDialog = false }, + title = { Text(stringResource(R.string.enable_notification)) }, + text = { + Text( + stringResource( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + R.string.disable_notification_description + } else { + R.string.disable_notification_description_legacy + }, + ), + ) + }, + confirmButton = { + TextButton(onClick = { + showDisableNotificationDialog = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startActivity( + Intent(AndroidSettings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { + putExtra(AndroidSettings.EXTRA_APP_PACKAGE, context.packageName) + putExtra(AndroidSettings.EXTRA_CHANNEL_ID, "service") + }, + ) + } else { + context.startActivity( + Intent( + AndroidSettings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.parse("package:${context.packageName}"), + ), + ) + } + }) { + Text(stringResource(R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = { showDisableNotificationDialog = false }) { + Text(stringResource(android.R.string.cancel)) + } + }, + ) + } + if (showUpdateAvailableDialog && updateInfo != null) { UpdateAvailableDialog( updateInfo = updateInfo!!, @@ -319,6 +386,91 @@ fun AppSettingsScreen(navController: NavController) { Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.notification_settings), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp), + ) + + Card( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + colors = + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + ), + ) { + Column { + ListItem( + headlineContent = { + Text( + stringResource(R.string.enable_notification), + style = MaterialTheme.typography.bodyLarge, + ) + }, + leadingContent = { + Icon( + imageVector = Icons.Outlined.Notifications, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + }, + trailingContent = { + Switch( + checked = notificationEnabled, + onCheckedChange = null, + ) + }, + modifier = + Modifier + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) + .clickable { showDisableNotificationDialog = true }, + colors = + ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + ListItem( + headlineContent = { + Text( + stringResource(R.string.dynamic_notification), + style = MaterialTheme.typography.bodyLarge, + ) + }, + leadingContent = { + Icon( + imageVector = Icons.Outlined.Speed, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + }, + trailingContent = { + Switch( + checked = dynamicNotification, + onCheckedChange = { checked -> + dynamicNotification = checked + scope.launch(Dispatchers.IO) { + Settings.dynamicNotification = checked + } + }, + ) + }, + modifier = + Modifier + .clip(RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp)), + colors = + ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + Text( text = stringResource(R.string.update_settings), style = MaterialTheme.typography.labelLarge, diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 95be3b3..900a08d 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -199,6 +199,11 @@ غیرفعال‌کردن هشدارهای منسوخ نادیده گرفتن محدودیت حافظه محدودیت حافظه روی sing-box اعمال نشود. + اعلان‌ها + فعال‌کردن اعلان + نمایش سرعت بلادرنگ در اعلان + به دلیل محدودیت‌های اندروید، ابتدا باید مجوز اعلان را بدهید، سپس دسته‌بندی اعلان را در تنظیمات غیرفعال کنید. + به دلیل محدودیت‌های اندروید، ابتدا باید مجوز اعلان را بدهید، سپس اعلان‌ها را در اطلاعات برنامه غیرفعال کنید. تغییر مسیر خودکار نیازمند دسترسی ROOT پراکسی HTTP سیستم diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 82de1f0..f22ea02 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -199,6 +199,11 @@ Отключить предупреждения об устаревании Игнорировать ограничение памяти Не применять ограничения по памяти для sing-box. + Уведомления + Включить уведомления + Отображать скорость в реальном времени в уведомлении + Из-за ограничений Android необходимо сначала предоставить разрешение на уведомления, а затем отключить категорию уведомлений в настройках. + Из-за ограничений Android необходимо сначала предоставить разрешение на уведомления, а затем отключить уведомления в сведениях о приложении. Автоматическое перенаправление Требуются права ROOT Системный HTTP-прокси diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 060cafe..8ff79b2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -199,6 +199,11 @@ 禁用弃用警告 忽略内存限制 不对 sing-box 强制执行内存限制。 + 通知 + 启用通知 + 在通知中显示实时网速 + 由于 Android 限制,您需要先授权通知权限,然后前往系统设置中关闭通知类别。 + 由于 Android 限制,您需要先授权通知权限,然后前往应用信息中关闭通知。 自动重定向 需要 ROOT 权限 系统 HTTP 代理 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 01f0c19..eba6123 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -199,6 +199,11 @@ 停用過時警告 忽略記憶體限制 不對 sing-box 強制執行記憶體限制。 + 通知 + 啟用通知 + 在通知中顯示即時網速 + 由於 Android 限制,您需要先授權通知權限,然後前往系統設定中關閉通知類別。 + 由於 Android 限制,您需要先授權通知權限,然後前往應用程式資訊中關閉通知。 自動重定向 需要 ROOT 權限 系統 HTTP 代理 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fce2024..258b05f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -199,6 +199,11 @@ Disable Deprecated Warnings Ignore Memory Limit Do not enforce memory limits on sing-box. + Notification + Enable Notification + Display realtime speed in notification + Due to Android restrictions, you must first grant notification permission, then go to Settings to disable the notification category. + Due to Android restrictions, you must first grant notification permission, then go to App Info to disable notifications. Auto Redirect ROOT permission required System HTTP Proxy