Read back locale requests after partial framework failures

This commit is contained in:
evgeny committed 2026-10-28 14:43:27 +01:00
1 parent a541484df1
commit 3dfedc8822
3 files changed
+19 -5

No files matched your search

@@ -16,7 +16,7 @@ import com.utun.chat.data.LogManager
/** Выбор одного языка каталога либо «Как в системе»; название текущего запроса учитывает приоритетный список Android.
* Чтение повторяется при новой Configuration/явной попытке; ошибка не выдаётся за системный выбор.
* Неудачный select оставляет прежний выбор и показывает ошибку, повтор открывает меню без перезапуска служб.
* После select перечитывает фактический запрос Android, включая отказ после частичного применения; ошибка показывается отдельно.
* Успех означает принятие запроса AppCompat; фактические ресурсы/пересоздание Activity применяет Android. */
@Composable
fun LanguageSetting() {
@@ -25,7 +25,8 @@ fun LanguageSetting() {
var selectionFailed by remember { mutableStateOf(false) }
val configuration = LocalConfiguration.current
val state = remember(configuration, retry) {
runCatching { AppLanguage.selectedTag() to AppLanguage.supportedTags }
try { Result.success(AppLanguage.selectedTag() to AppLanguage.supportedTags)
} catch (error: Exception) { Result.failure(error) }
}
val selection = state.getOrNull()
val selected = selection?.first
@@ -58,7 +59,7 @@ fun LanguageSetting() {
DropdownMenuItem(text = { Text(name) }, onClick = {
expanded = false
selectionFailed = !AppLanguage.select(tag)
if (!selectionFailed) retry++
retry++
})
}
}
@@ -2,11 +2,12 @@ package androidx.appcompat.app
import androidx.core.os.LocaleListCompat
/** Host AppCompat: при отказе setApplicationLocales сохранённый выбор не меняется. */
/** Host AppCompat: отказ до/после публикации requested моделируется отдельно; завершение конфигурации не имитируется. */
object AppCompatDelegate {
var requested = LocaleListCompat.forLanguageTags("")
var failRead = false
var failWrite = false
var failWriteAfter = false
val selections = mutableListOf<String>()
fun getApplicationLocales(): LocaleListCompat {
check(!failRead) { "application locales read failed" }
@@ -15,5 +16,6 @@ object AppCompatDelegate {
fun setApplicationLocales(locales: LocaleListCompat) {
check(!failWrite) { "application locales write failed" }
selections.add(locales.toLanguageTags()); requested = locales
check(!failWriteAfter) { "application locales write failed after effect" }
}
}
@@ -25,6 +25,7 @@ class AppLanguageTest {
ContextCompat.failContext = false
AppCompatDelegate.requested = LocaleListCompat.forLanguageTags("")
AppCompatDelegate.failRead = false; AppCompatDelegate.failWrite = false; AppCompatDelegate.selections.clear()
AppCompatDelegate.failWriteAfter = false
HostLanguageNotifications.attempts.clear(); HostLanguageNotifications.failures.clear()
LogManager.entries.clear()
}
@@ -86,7 +87,7 @@ class AppLanguageTest {
error("Unsupported language")
}
@Test fun failedFrameworkWritePreservesSelectionAndIsReportedBeforeSuccessLogging() {
@Test fun failedFrameworkWriteBeforeEffectPreservesSelectionAndIsReportedBeforeSuccessLogging() {
AppCompatDelegate.requested = LocaleListCompat.forLanguageTags("en")
AppCompatDelegate.failWrite = true
assertFalse(AppLanguage.select("ru"))
@@ -97,6 +98,16 @@ class AppLanguageTest {
assertTrue(AppLanguage.select("ru")); assertEquals("ru", AppLanguage.selectedTag())
}
@Test fun partialFrameworkFailureIsReportedWithoutInventingARollback() {
AppCompatDelegate.requested = LocaleListCompat.forLanguageTags("en")
AppCompatDelegate.failWriteAfter = true
assertFalse(AppLanguage.select("ru"))
assertEquals("ru", AppLanguage.selectedTag())
assertEquals(listOf("ru"), AppCompatDelegate.selections)
error("after effect")
assertTrue(LogManager.entries.none { it.level == "INFO" })
}
@Test fun failedSelectionReadIsNotReplacedWithFollowSystem() {
AppCompatDelegate.failRead = true
assertThrows(IllegalStateException::class.java) { AppLanguage.selectedTag() }