diff --git a/app/build.gradle b/app/build.gradle index 1a07129..e39e7fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,6 +61,7 @@ dependencies { // Kordophone lib implementation project(':backend') + implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha06' // Navigation def nav_version = "2.6.0" diff --git a/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsScreen.kt b/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsScreen.kt index b70ee44..4c44e00 100644 --- a/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsScreen.kt @@ -98,8 +98,8 @@ fun SettingsFormView( }) } - var usernameInput by remember { mutableStateOf(TextFieldValue()) } - var passwordInput by remember { mutableStateOf(TextFieldValue()) } + var usernameInput by remember { mutableStateOf(TextFieldValue(userName.value)) } + var passwordInput by remember { mutableStateOf(TextFieldValue(password.value)) } SettingsTextField( name = "Authentication", icon = R.drawable.account_circle, diff --git a/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsViewModel.kt b/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsViewModel.kt index c12eb5b..2c809dc 100644 --- a/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/net/buzzert/kordophonedroid/ui/settings/SettingsViewModel.kt @@ -23,6 +23,15 @@ class SettingsViewModel @Inject constructor( private val _passwordPreference: MutableStateFlow = MutableStateFlow("") var passwordPreference = _passwordPreference.asStateFlow() + init { + val serverConfig = serverConfigRepository.serverConfig.value + serverConfig.serverName?.let { _serverPreference.value = it } + serverConfig.authentication?.let { + _usernamePreference.value = it.username + _passwordPreference.value = it.password + } + } + fun saveServerPreference(serverName: String) { _serverPreference.value = serverName @@ -36,7 +45,7 @@ class SettingsViewModel @Inject constructor( _passwordPreference.value = password serverConfigRepository.applyConfig { - authentication = ServerAuthentication(username, password) + this.authentication = ServerAuthentication(username, password) } } } \ No newline at end of file diff --git a/app/src/main/java/net/buzzert/kordophonedroid/ui/shared/ServerConfigRepository.kt b/app/src/main/java/net/buzzert/kordophonedroid/ui/shared/ServerConfigRepository.kt index 0f1147c..cd7fb2f 100644 --- a/app/src/main/java/net/buzzert/kordophonedroid/ui/shared/ServerConfigRepository.kt +++ b/app/src/main/java/net/buzzert/kordophonedroid/ui/shared/ServerConfigRepository.kt @@ -1,28 +1,102 @@ package net.buzzert.kordophonedroid.ui.shared +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import java.lang.reflect.Constructor import javax.inject.Inject import javax.inject.Singleton + data class ServerConfig( var serverName: String? = null, var authentication: ServerAuthentication? = null, -) +) { + companion object { + private const val SHARED_PREF_NAME = "KordophonePreferences" + + fun loadFromSettings(context: Context): ServerConfig { + val prefs = getSharedPreferences(context) + return ServerConfig( + serverName = prefs.getString("serverName", null), + authentication = ServerAuthentication.loadFromEncryptedSettings(context) + ) + } + + private fun getSharedPreferences(context: Context): SharedPreferences { + return context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + } + } + + fun saveToSettings(context: Context) { + val prefs = getSharedPreferences(context) + prefs.edit { + putString("serverName", serverName) + apply() + } + + authentication?.saveToEncryptedSettings(context) + } +} data class ServerAuthentication( val username: String, val password: String, -) +) { + companion object { + fun loadFromEncryptedSettings(context: Context): ServerAuthentication? { + val prefs = getEncryptedSharedPreferences(context) + + val username = prefs.getString("username", null) + val password = prefs.getString("password", null) + if (username != null && password != null) { + return ServerAuthentication(username, password) + } + + return null + } + + private fun getEncryptedSharedPreferences(context: Context): SharedPreferences { + val masterKey = MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + + return EncryptedSharedPreferences( + context, + "secrets", + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + } + + fun saveToEncryptedSettings(context: Context) { + val prefs = getEncryptedSharedPreferences(context) + prefs.edit { + putString("username", username) + putString("password", password) + apply() + } + } +} @Singleton -class ServerConfigRepository @Inject constructor() { +class ServerConfigRepository @Inject constructor( + @ApplicationContext val context: Context, +) { // TODO: Initial config should be loaded from device settings. - private val _serverConfig = MutableStateFlow(ServerConfig()) // Initial config + private val _serverConfig = MutableStateFlow(ServerConfig.loadFromSettings(context)) // Initial config val serverConfig: StateFlow = _serverConfig fun applyConfig(applicator: ServerConfig.() -> Unit) { val config = _serverConfig.value.copy() _serverConfig.value = config.apply(applicator) + _serverConfig.value.saveToSettings(context) } }