mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-06 03:18:23 +00:00
Merge pull request #393 from greenart7c3/claude/disable-service-boot-startup-fPFHI
Add service settings and stop service functionality
This commit is contained in:
@@ -39,6 +39,10 @@
|
||||
android:name=".service.ReconnectReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<receiver
|
||||
android:name=".service.StopServiceReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<receiver
|
||||
android:name=".service.BootReceiver"
|
||||
android:enabled="true"
|
||||
|
||||
@@ -60,6 +60,7 @@ private enum class SettingsKeys(val key: String) {
|
||||
AUTO_CHECK_UPDATES("auto_check_updates"),
|
||||
UPDATE_CHECK_FREQUENCY("update_check_frequency"),
|
||||
LAST_UPDATE_CHECK_TIME("last_update_check_time"),
|
||||
START_SERVICE_ON_BOOT("start_service_on_boot"),
|
||||
WEBDAV_URL("webdav_url"),
|
||||
WEBDAV_USERNAME("webdav_username"),
|
||||
WEBDAV_PASSWORD_ENCRYPTED("webdav_password_encrypted"),
|
||||
@@ -139,6 +140,7 @@ object LocalPreferences {
|
||||
putBoolean(SettingsKeys.KILL_SWITCH.key, settings.killSwitch.value)
|
||||
putString(SettingsKeys.LANGUAGE_PREFS.key, settings.language)
|
||||
putStringSet(SettingsKeys.AUTH_WHITELIST.key, settings.authWhitelist.toSet())
|
||||
putBoolean(SettingsKeys.START_SERVICE_ON_BOOT.key, settings.startServiceOnBoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,6 +256,7 @@ object LocalPreferences {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
UpdateCheckFrequency.DAILY
|
||||
},
|
||||
startServiceOnBoot = getBoolean(SettingsKeys.START_SERVICE_ON_BOOT.key, true),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -503,6 +506,15 @@ object LocalPreferences {
|
||||
Amber.instance.settings = loadSettingsFromEncryptedStorage()
|
||||
}
|
||||
|
||||
fun updateStartServiceOnBoot(context: Context, enabled: Boolean) {
|
||||
sharedPrefs(context).edit {
|
||||
apply {
|
||||
putBoolean(SettingsKeys.START_SERVICE_ON_BOOT.key, enabled)
|
||||
}
|
||||
}
|
||||
Amber.instance.settings = loadSettingsFromEncryptedStorage()
|
||||
}
|
||||
|
||||
fun updateUpdateCheckFrequency(context: Context, frequency: UpdateCheckFrequency) {
|
||||
sharedPrefs(context).edit {
|
||||
apply {
|
||||
|
||||
@@ -32,6 +32,7 @@ data class AmberSettings(
|
||||
val authWhitelist: List<String> = emptyList(),
|
||||
val autoCheckUpdates: Boolean = true,
|
||||
val updateCheckFrequency: UpdateCheckFrequency = UpdateCheckFrequency.DAILY,
|
||||
val startServiceOnBoot: Boolean = true,
|
||||
) {
|
||||
val useProxy: Boolean get() = torMode != TorMode.DISABLED
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.greenart7c3.nostrsigner.MainActivity
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.KillSwitchReceiver
|
||||
import com.greenart7c3.nostrsigner.service.ReconnectReceiver
|
||||
import com.greenart7c3.nostrsigner.service.StopServiceReceiver
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
@@ -121,6 +122,14 @@ class AmberRelayStats(
|
||||
PendingIntent.FLAG_MUTABLE,
|
||||
)
|
||||
|
||||
val stopServiceIntent = Intent(appContext, StopServiceReceiver::class.java)
|
||||
val stopServicePendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
0,
|
||||
stopServiceIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
|
||||
)
|
||||
|
||||
return NotificationCompat.Builder(appContext, channel.id)
|
||||
.setContentTitle(appContext.getString(R.string.service))
|
||||
.setContentText(appContext.getString(R.string.amber_is_running_in_background))
|
||||
@@ -130,6 +139,11 @@ class AmberRelayStats(
|
||||
.setGroup(group.id)
|
||||
.setGroupSummary(true)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.addAction(
|
||||
R.drawable.ic_notification,
|
||||
appContext.getString(R.string.stop_service),
|
||||
stopServicePendingIntent,
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,12 @@ class BootReceiver : BroadcastReceiver() {
|
||||
Log.d(Amber.TAG, "Received ${intent.action}")
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
Amber.instance.isStartingAppState.first { !it }
|
||||
if (intent.action == Intent.ACTION_BOOT_COMPLETED &&
|
||||
!Amber.instance.settings.startServiceOnBoot
|
||||
) {
|
||||
Log.d(Amber.TAG, "Skipping service start on boot (disabled in settings)")
|
||||
return@launch
|
||||
}
|
||||
Amber.instance.startService()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class StopServiceReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Log.d(Amber.TAG, "StopServiceReceiver: stopping service and killing process")
|
||||
|
||||
try {
|
||||
Amber.instance.client.disconnect()
|
||||
} catch (e: Exception) {
|
||||
Log.e(Amber.TAG, "Failed to disconnect client on stop", e)
|
||||
}
|
||||
|
||||
try {
|
||||
context.stopService(Intent(context, ConnectivityService::class.java))
|
||||
} catch (e: Exception) {
|
||||
Log.e(Amber.TAG, "Failed to stop ConnectivityService", e)
|
||||
}
|
||||
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
notificationManager.cancelAll()
|
||||
|
||||
Process.killProcess(Process.myPid())
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
@@ -1018,6 +1018,22 @@ fun MainScreen(
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.ServiceSettings.route,
|
||||
content = {
|
||||
val scrollState = rememberScrollState()
|
||||
ServiceSettingsScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.verticalScrollbar(scrollState)
|
||||
.verticalScroll(scrollState)
|
||||
.padding(horizontal = verticalPadding)
|
||||
.padding(top = verticalPadding * 1.5f),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ServiceSettingsScreen(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var startServiceOnBoot by remember { mutableStateOf(Amber.instance.settings.startServiceOnBoot) }
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp)
|
||||
.clickable {
|
||||
val newValue = !startServiceOnBoot
|
||||
startServiceOnBoot = newValue
|
||||
scope.launch(Dispatchers.IO) {
|
||||
LocalPreferences.updateStartServiceOnBoot(context, newValue)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(text = stringResource(R.string.start_service_on_boot))
|
||||
Text(
|
||||
text = stringResource(R.string.start_service_on_boot_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = startServiceOnBoot,
|
||||
onCheckedChange = { enabled ->
|
||||
startServiceOnBoot = enabled
|
||||
scope.launch(Dispatchers.IO) {
|
||||
LocalPreferences.updateStartServiceOnBoot(context, enabled)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.material.icons.filled.Feedback
|
||||
import androidx.compose.material.icons.filled.FilterList
|
||||
import androidx.compose.material.icons.filled.Key
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.PowerSettingsNew
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.SystemUpdate
|
||||
import androidx.compose.material3.AlertDialog
|
||||
@@ -197,6 +198,20 @@ fun SettingsScreen(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(vertical = 8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = stringResource(R.string.service_settings),
|
||||
icon = Icons.Default.PowerSettingsNew,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
onNav(Route.ServiceSettings.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
|
||||
@@ -214,6 +214,12 @@ sealed class Route(
|
||||
route = "CloudBackup",
|
||||
icon = R.drawable.settings,
|
||||
)
|
||||
|
||||
data object ServiceSettings : Route(
|
||||
title = Amber.instance.getString(R.string.service_settings),
|
||||
route = "ServiceSettings",
|
||||
icon = R.drawable.settings,
|
||||
)
|
||||
}
|
||||
|
||||
val routes = listOf(
|
||||
@@ -248,4 +254,5 @@ val routes = listOf(
|
||||
Route.CrashReport,
|
||||
Route.UpdateSettings,
|
||||
Route.CloudBackup,
|
||||
Route.ServiceSettings,
|
||||
)
|
||||
|
||||
@@ -615,6 +615,10 @@
|
||||
<string name="update_download_failed">Download failed. Tap to retry.</string>
|
||||
<string name="download_and_install_update">Download and install update</string>
|
||||
<string name="auto_check_updates">Automatically check for updates</string>
|
||||
<string name="service_settings">Service settings</string>
|
||||
<string name="stop_service">Stop service</string>
|
||||
<string name="start_service_on_boot">Start service on boot</string>
|
||||
<string name="start_service_on_boot_description">When enabled, Amber will start its background service automatically after the device boots so remote login and NIP-46 requests keep working.</string>
|
||||
<string name="update_check_frequency">Check frequency</string>
|
||||
<string name="update_frequency_on_startup">Every app start</string>
|
||||
<string name="update_frequency_daily">Daily</string>
|
||||
|
||||
Reference in New Issue
Block a user