diff --git a/app/src/main/java/org/prolibertate/games/settings/SettingsRepository.kt b/app/src/main/java/org/prolibertate/games/settings/SettingsRepository.kt index 64b9375..c086124 100644 --- a/app/src/main/java/org/prolibertate/games/settings/SettingsRepository.kt +++ b/app/src/main/java/org/prolibertate/games/settings/SettingsRepository.kt @@ -25,10 +25,23 @@ data class Settings( val soundEnabled: Boolean = true, val animationSpeed: Float = 1.0f, val checkForUpdatesOnLaunch: Boolean = true, - val playerName: String = "Player", + /** + * Exactly what the user has typed, which may be blank while they are + * mid-edit. Nothing substitutes a default into this value: doing so meant + * that clearing the field wrote "Player" straight back and the text + * snapped back before a real name could be typed. + */ + val playerName: String = "", /** Which builds the updater offers. Production unless deliberately changed. */ val updateChannel: UpdateChannel = UpdateChannel.PRODUCTION, ) { + /** + * The name other players actually see. This is where the default lives, so + * an empty field is a display concern rather than something written back + * into what the user is typing. + */ + val displayName: String get() = playerName.trim().ifBlank { DEFAULT_PLAYER_NAME } + /** Scales a nominal duration by the chosen speed. */ fun scaled(millis: Long): Long = (millis / animationSpeed.coerceIn(MIN_SPEED, MAX_SPEED)).toLong() @@ -36,6 +49,7 @@ data class Settings( companion object { const val MIN_SPEED = 0.5f const val MAX_SPEED = 2.0f + const val DEFAULT_PLAYER_NAME = "Player" } } @@ -54,7 +68,7 @@ class SettingsRepository(private val context: Context) { soundEnabled = prefs[Keys.SOUND] ?: true, animationSpeed = prefs[Keys.SPEED] ?: 1.0f, checkForUpdatesOnLaunch = prefs[Keys.UPDATE_ON_LAUNCH] ?: true, - playerName = prefs[Keys.NAME] ?: "Player", + playerName = prefs[Keys.NAME] ?: "", updateChannel = prefs[Keys.CHANNEL] ?.let { stored -> runCatching { UpdateChannel.valueOf(stored) }.getOrNull() } ?: UpdateChannel.PRODUCTION, @@ -79,7 +93,8 @@ class SettingsRepository(private val context: Context) { context.dataStore.edit { it[Keys.UPDATE_ON_LAUNCH] = enabled } } + /** Stores the name verbatim. The fallback belongs to [Settings.displayName]. */ suspend fun setPlayerName(name: String) { - context.dataStore.edit { it[Keys.NAME] = name.trim().ifBlank { "Player" } } + context.dataStore.edit { it[Keys.NAME] = name } } } diff --git a/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt b/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt index dc17eff..0d596c4 100644 --- a/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt +++ b/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt @@ -107,7 +107,7 @@ fun AppRoot(env: AppEnv) { is Route.Setup -> GameSetupScreen( descriptor = GameCatalog.byId(route.gameId)!!, - playerName = settings.playerName, + playerName = settings.displayName, onBack = { pop() }, onPlayOffline = { config -> push(Route.Play(route.gameId, config, true, 0)) }, onHostOnline = { optionsJson -> @@ -118,7 +118,7 @@ fun AppRoot(env: AppEnv) { is Route.Lobby -> LobbyScreen( env = env, route = route, - playerName = settings.playerName, + playerName = settings.displayName, onBack = { pop() }, onStart = { config, hosting, seat -> push(Route.Play(config.gameId, config, hosting, seat)) diff --git a/app/src/main/java/org/prolibertate/games/ui/SettingsScreen.kt b/app/src/main/java/org/prolibertate/games/ui/SettingsScreen.kt index a85bf6d..5e072a7 100644 --- a/app/src/main/java/org/prolibertate/games/ui/SettingsScreen.kt +++ b/app/src/main/java/org/prolibertate/games/ui/SettingsScreen.kt @@ -36,7 +36,11 @@ fun SettingsScreen( onBack: () -> Unit, ) { val scope = rememberCoroutineScope() - var name by remember(settings.playerName) { mutableStateOf(settings.playerName) } + // Null until the user touches the field, at which point their text takes + // over for good. Keying this off the stored name instead meant every + // keystroke re-seeded the field from what had just been written, so the + // caret jumped and a cleared field refilled itself. + var editedName by remember { mutableStateOf(null) } var updateStatus by remember { mutableStateOf(null) } var pendingRelease by remember { mutableStateOf(null) } @@ -86,14 +90,22 @@ fun SettingsScreen( style = MaterialTheme.typography.bodySmall, ) OutlinedTextField( - value = name, + value = editedName ?: settings.playerName, onValueChange = { entered -> - name = entered + editedName = entered scope.launch { env.settingsRepository.setPlayerName(entered) } }, + placeholder = { Text(Settings.DEFAULT_PLAYER_NAME) }, singleLine = true, modifier = Modifier.fillMaxWidth().padding(top = 8.dp), ) + if ((editedName ?: settings.playerName).isBlank()) { + Text( + text = "Leave it empty and you'll show up as " + + "${Settings.DEFAULT_PLAYER_NAME}.", + style = MaterialTheme.typography.bodySmall, + ) + } } Divider()