@ -19,10 +19,14 @@ import com.utun.chat.ChatApplication
import com.utun.chat.data.NativeLib
import com.utun.chat.data.ServerEntry
import com.utun.chat.data.LocalSocketState
import com.utun.chat.headless.HeadlessService
import com.utun.chat.viewmodel.ChatViewModel
import android.content.Intent
import androidx.core.content.ContextCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.util.UUID
@ -47,7 +51,7 @@ fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = {
val tabs = listOf ( " Settings " , " Audio " , " Storage " )
val handleBack = {
if ( dirty ) {
if ( dirty && NativeLib . isReady ( ) ) {
scope . launch ( Dispatchers . IO ) {
val path = provider . getString ( " db.path " ) ?: " utun_chat.db "
val port = provider . getInt ( " control.port " )
@ -115,6 +119,7 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
var connectionMode by remember { mutableStateOf ( " manual " ) }
val localSocketStates by vm . localSockets . collectAsState ( )
val serviceEnabled by provider . serviceEnabledFlow . collectAsState ( initial = true )
LaunchedEffect ( Unit ) {
nodeName = provider . getString ( " node.name " ) ?: provider . getDeviceName ( )
@ -144,6 +149,7 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
connectionMode = mode ; onDirty ( true )
scope . launch ( Dispatchers . IO ) {
provider . setConnectionMode ( mode )
if ( NativeLib . isReady ( ) )
NativeLib . restart ( provider . getString ( " db.path " ) ?: " " ,
provider . getInt ( " control.port " ) , provider . buildConfigText ( ) )
}
@ -360,16 +366,43 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
}
Spacer ( Modifier . height ( 16. dp ) )
/* ── Background service ── */
HorizontalDivider ( Modifier . padding ( vertical = 8. dp ) )
Text ( " Service " , style = MaterialTheme . typography . titleMedium )
Row ( verticalAlignment = Alignment . CenterVertically , horizontalArrangement = Arrangement . spacedBy ( 8. dp ) ) {
Text ( if ( serviceEnabled ) " Running " else " Stopped " , modifier = Modifier . weight ( 1f ) ,
color = if ( serviceEnabled ) Color ( 0xFF27AE60 ) else MaterialTheme . colorScheme . error ,
style = MaterialTheme . typography . bodyMedium )
Button ( onClick = {
runBlocking { provider . setServiceEnabled ( true ) }
ContextCompat . startForegroundService ( ChatApplication . instance ,
Intent ( ChatApplication . instance , HeadlessService :: class . java ) )
} , enabled = ! serviceEnabled ) { Text ( " Start service " ) }
Button ( onClick = {
runBlocking { provider . setServiceEnabled ( false ) }
ChatApplication . instance . stopService ( Intent ( ChatApplication . instance , HeadlessService :: class . java ) )
} , enabled = serviceEnabled ,
colors = ButtonDefaults . buttonColors ( containerColor = MaterialTheme . colorScheme . error ) ) { Text ( " Stop service " ) }
}
HorizontalDivider ( Modifier . padding ( vertical = 8. dp ) )
Button ( onClick = {
scope . launch ( Dispatchers . IO ) {
if ( NativeLib . isReady ( ) ) {
val path = provider . getString ( " db.path " ) ?: " " ; val port = provider . getInt ( " control.port " )
NativeLib . restart ( path , port , provider . buildConfigText ( ) )
}
}
} , modifier = Modifier . fillMaxWidth ( ) , colors = ButtonDefaults . buttonColors ( containerColor = MaterialTheme . colorScheme . secondary ) ) {
Icon ( Icons . Default . Refresh , null , modifier = Modifier . size ( 18. dp ) ) ; Spacer ( Modifier . width ( 8. dp ) ) ; Text ( " Restart instance " )
}
Spacer ( Modifier . height ( 8. dp ) )
Button ( onClick = { NativeLib . stop ( ) ; onExit ( ) } , modifier = Modifier . fillMaxWidth ( ) ,
Button ( onClick = {
runBlocking { provider . setServiceEnabled ( false ) }
ChatApplication . instance . stopService ( Intent ( ChatApplication . instance , HeadlessService :: class . java ) )
onExit ( )
} , modifier = Modifier . fillMaxWidth ( ) ,
colors = ButtonDefaults . buttonColors ( containerColor = MaterialTheme . colorScheme . error ) ) {
Icon ( Icons . Default . PowerSettingsNew , null , modifier = Modifier . size ( 18. dp ) ) ; Spacer ( Modifier . width ( 8. dp ) ) ; Text ( " Stop All & Exit " )
}
@ -389,9 +422,13 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
confirmButton = { Button ( onClick = {
showClearDb = false ; clearingDb = true
scope . launch ( Dispatchers . IO ) {
vm . clearState ( ) ; NativeLib . stop ( ) ; NativeLib . clearDatabase ( )
vm . clearState ( )
val wasRunning = NativeLib . isReady ( )
NativeLib . stop ( ) ; NativeLib . clearDatabase ( )
if ( wasRunning ) {
val path = provider . getString ( " db.path " ) ?: " " ; val port = provider . getInt ( " control.port " )
NativeLib . restart ( path , port , provider . buildConfigText ( ) )
}
withContext ( Dispatchers . Main ) { clearingDb = false }
}
} , enabled = ! clearingDb , colors = ButtonDefaults . buttonColors ( containerColor = MaterialTheme . colorScheme . error ) ) { Text ( " Delete All " ) } } ,