Fix missing notification settings

This commit is contained in:
世界
2026-02-13 22:16:50 +08:00
parent 6491eff61e
commit b083930fa6
6 changed files with 178 additions and 1 deletions

View File

@@ -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<String?>(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,

View File

@@ -199,6 +199,11 @@
<string name="disable_deprecated_warnings">غیرفعال‌کردن هشدارهای منسوخ</string>
<string name="ignore_memory_limit">نادیده گرفتن محدودیت حافظه</string>
<string name="ignore_memory_limit_description">محدودیت حافظه روی sing-box اعمال نشود.</string>
<string name="notification_settings">اعلان‌ها</string>
<string name="enable_notification">فعال‌کردن اعلان</string>
<string name="dynamic_notification">نمایش سرعت بلادرنگ در اعلان</string>
<string name="disable_notification_description">به دلیل محدودیت‌های اندروید، ابتدا باید مجوز اعلان را بدهید، سپس دسته‌بندی اعلان را در تنظیمات غیرفعال کنید.</string>
<string name="disable_notification_description_legacy">به دلیل محدودیت‌های اندروید، ابتدا باید مجوز اعلان را بدهید، سپس اعلان‌ها را در اطلاعات برنامه غیرفعال کنید.</string>
<string name="auto_redirect">تغییر مسیر خودکار</string>
<string name="auto_redirect_description">نیازمند دسترسی ROOT</string>
<string name="system_http_proxy">پراکسی HTTP سیستم</string>

View File

@@ -199,6 +199,11 @@
<string name="disable_deprecated_warnings">Отключить предупреждения об устаревании</string>
<string name="ignore_memory_limit">Игнорировать ограничение памяти</string>
<string name="ignore_memory_limit_description">Не применять ограничения по памяти для sing-box.</string>
<string name="notification_settings">Уведомления</string>
<string name="enable_notification">Включить уведомления</string>
<string name="dynamic_notification">Отображать скорость в реальном времени в уведомлении</string>
<string name="disable_notification_description">Из-за ограничений Android необходимо сначала предоставить разрешение на уведомления, а затем отключить категорию уведомлений в настройках.</string>
<string name="disable_notification_description_legacy">Из-за ограничений Android необходимо сначала предоставить разрешение на уведомления, а затем отключить уведомления в сведениях о приложении.</string>
<string name="auto_redirect">Автоматическое перенаправление</string>
<string name="auto_redirect_description">Требуются права ROOT</string>
<string name="system_http_proxy">Системный HTTP-прокси</string>

View File

@@ -199,6 +199,11 @@
<string name="disable_deprecated_warnings">禁用弃用警告</string>
<string name="ignore_memory_limit">忽略内存限制</string>
<string name="ignore_memory_limit_description">不对 sing-box 强制执行内存限制。</string>
<string name="notification_settings">通知</string>
<string name="enable_notification">启用通知</string>
<string name="dynamic_notification">在通知中显示实时网速</string>
<string name="disable_notification_description">由于 Android 限制,您需要先授权通知权限,然后前往系统设置中关闭通知类别。</string>
<string name="disable_notification_description_legacy">由于 Android 限制,您需要先授权通知权限,然后前往应用信息中关闭通知。</string>
<string name="auto_redirect">自动重定向</string>
<string name="auto_redirect_description">需要 ROOT 权限</string>
<string name="system_http_proxy">系统 HTTP 代理</string>

View File

@@ -199,6 +199,11 @@
<string name="disable_deprecated_warnings">停用過時警告</string>
<string name="ignore_memory_limit">忽略記憶體限制</string>
<string name="ignore_memory_limit_description">不對 sing-box 強制執行記憶體限制。</string>
<string name="notification_settings">通知</string>
<string name="enable_notification">啟用通知</string>
<string name="dynamic_notification">在通知中顯示即時網速</string>
<string name="disable_notification_description">由於 Android 限制,您需要先授權通知權限,然後前往系統設定中關閉通知類別。</string>
<string name="disable_notification_description_legacy">由於 Android 限制,您需要先授權通知權限,然後前往應用程式資訊中關閉通知。</string>
<string name="auto_redirect">自動重定向</string>
<string name="auto_redirect_description">需要 ROOT 權限</string>
<string name="system_http_proxy">系統 HTTP 代理</string>

View File

@@ -199,6 +199,11 @@
<string name="disable_deprecated_warnings">Disable Deprecated Warnings</string>
<string name="ignore_memory_limit">Ignore Memory Limit</string>
<string name="ignore_memory_limit_description">Do not enforce memory limits on sing-box.</string>
<string name="notification_settings">Notification</string>
<string name="enable_notification">Enable Notification</string>
<string name="dynamic_notification">Display realtime speed in notification</string>
<string name="disable_notification_description">Due to Android restrictions, you must first grant notification permission, then go to Settings to disable the notification category.</string>
<string name="disable_notification_description_legacy">Due to Android restrictions, you must first grant notification permission, then go to App Info to disable notifications.</string>
<string name="auto_redirect">Auto Redirect</string>
<string name="auto_redirect_description">ROOT permission required</string>
<string name="system_http_proxy">System HTTP Proxy</string>