Merge pull request #4388 from kiwix/Fixes#4387

Fixed: When changing the app's language, a blank screen appeared instead of the settings screen.
This commit is contained in:
Kelson 2025-08-19 06:14:53 +02:00 committed by GitHub
commit 6bb8512391
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 53 additions and 57 deletions

View File

@ -205,9 +205,13 @@ class DownloadTest : BaseActivityTest() {
clickLanguagePreference(composeTestRule)
assertLanguagePrefDialogDisplayed(composeTestRule)
selectDeviceDefaultLanguage(composeTestRule)
// Advance the main clock to settle the frame of compose.
composeTestRule.mainClock.advanceTimeByFrame()
clickLanguagePreference(composeTestRule)
assertLanguagePrefDialogDisplayed(composeTestRule)
selectAlbanianLanguage(composeTestRule)
// Advance the main clock to settle the frame of compose.
composeTestRule.mainClock.advanceTimeByFrame()
}
}
clickDownloadOnBottomNav(composeTestRule)
@ -226,6 +230,8 @@ class DownloadTest : BaseActivityTest() {
clickLanguagePreference(composeTestRule)
assertLanguagePrefDialogDisplayed(composeTestRule)
selectDeviceDefaultLanguage(composeTestRule)
// Advance the main clock to settle the frame of compose.
composeTestRule.mainClock.advanceTimeByFrame()
// check if the device default language is selected or not.
clickLanguagePreference(composeTestRule)
// close the language dialog.

View File

@ -144,34 +144,36 @@ class LanguageViewModelTest {
}
@Test
fun `observeLanguages uses network when no cache and online`() = runTest {
every { application.getString(any()) } returns ""
val fetchedLanguages = listOf(language(languageCode = "eng"))
LanguageSessionCache.hasFetched = false
languages.value = emptyList()
fun `observeLanguages uses network when no cache and online`() = flakyTest {
runTest {
every { application.getString(any()) } returns ""
val fetchedLanguages = listOf(language(languageCode = "eng"))
LanguageSessionCache.hasFetched = false
languages.value = emptyList()
every { sharedPreferenceUtil.getCachedLanguageList() } returns null
coEvery { kiwixService.getLanguages() } returns LanguageFeed().apply {
entries = fetchedLanguages.map {
LanguageEntry().apply {
languageCode = it.languageCode
count = 1
title = "English"
every { sharedPreferenceUtil.getCachedLanguageList() } returns null
coEvery { kiwixService.getLanguages() } returns LanguageFeed().apply {
entries = fetchedLanguages.map {
LanguageEntry().apply {
languageCode = it.languageCode
count = 1
title = "English"
}
}
}
}
every { sharedPreferenceUtil.selectedOnlineContentLanguage } returns ""
every { sharedPreferenceUtil.saveLanguageList(any()) } just Runs
every { sharedPreferenceUtil.selectedOnlineContentLanguage } returns ""
every { sharedPreferenceUtil.saveLanguageList(any()) } just Runs
testFlow(
languageViewModel.actions,
triggerAction = {},
assert = {
val result = awaitItem()
assertThat(result).isInstanceOf(UpdateLanguages::class.java)
verify { sharedPreferenceUtil.saveLanguageList(any()) }
}
)
testFlow(
languageViewModel.actions,
triggerAction = {},
assert = {
val result = awaitItem()
assertThat(result).isInstanceOf(UpdateLanguages::class.java)
verify { sharedPreferenceUtil.saveLanguageList(any()) }
}
)
}
}
@OptIn(ExperimentalCoroutinesApi::class)

View File

@ -314,7 +314,6 @@ class ZimManageViewModelTest {
version = 100L
)
viewModel.onlineLibraryRequest.test {
skipItems(1)
viewModel.updateOnlineLibraryFilters(newRequest)
assertThat(awaitItem()).isEqualTo(newRequest)
}

View File

@ -35,6 +35,7 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
@ -183,12 +184,27 @@ abstract class CoreSettingsFragment : SettingsContract.View, BaseFragment() {
composeView = it
}
/**
* Restarts the Settings screen by popping it from the back stack and reopening it.
*
* This is useful when we need to refresh the Settings UI (e.g., after a app's language
* change) without fully recreating the activity.
*
* Steps:
* 1. Get the CoreMainActivity reference to access the NavController.
* 2. Pop the Settings fragment from the navigation back stack.
* 3. Wait for one frame so the back stack can settle after the pop operation.
* 4. Navigate back to the Settings fragment route.
*/
private fun restartActivity() {
(activity as CoreMainActivity?)?.let {
it.navController.apply {
popBackStack()
navigate(it.settingsFragmentRoute)
}
val coreMainActivity = activity as? CoreMainActivity ?: return
val navController = coreMainActivity.navController
navController.popBackStack()
coreMainActivity.uiCoroutineScope.launch {
// Wait for one frame to ensure the back stack has settled before navigation
// Bug fix #4387
withFrameNanos { }
navController.navigate(coreMainActivity.settingsFragmentRoute)
}
}

View File

@ -29,7 +29,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenu
@ -44,21 +43,16 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.toPainter
import org.kiwix.kiwixmobile.core.ui.theme.Black
@ -234,23 +228,3 @@ private fun OverflowMenuItems(
}
}
}
@Composable
fun rememberBottomNavigationVisibility(lazyListState: LazyListState?): Boolean {
var isToolbarVisible by remember { mutableStateOf(true) }
var lastScrollIndex by remember { mutableIntStateOf(ZERO) }
val updatedLazyListState = rememberUpdatedState(lazyListState)
LaunchedEffect(updatedLazyListState) {
updatedLazyListState.value?.let { state ->
snapshotFlow { state.firstVisibleItemIndex }
.collect { newScrollIndex ->
if (newScrollIndex != lastScrollIndex) {
isToolbarVisible = newScrollIndex < lastScrollIndex
lastScrollIndex = newScrollIndex
}
}
}
}
return isToolbarVisible
}

View File

@ -215,7 +215,6 @@ internal class CustomDownloadViewModelTest {
triggerAction = { customDownloadViewModel.actions.tryEmit(action) },
assert = {
val items = (1..awaitItemCount).map { awaitItem() }
print("items = $items")
assertThat(items).contains(endState)
}
)