diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadTest.kt index d32dc8e61..ba711f69b 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadTest.kt @@ -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. diff --git a/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt index 23c952908..d2f10ccad 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModelTest.kt @@ -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) diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt index 47ac8d5b7..5522efe4a 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt @@ -314,7 +314,6 @@ class ZimManageViewModelTest { version = 100L ) viewModel.onlineLibraryRequest.test { - skipItems(1) viewModel.updateOnlineLibraryFilters(newRequest) assertThat(awaitItem()).isEqualTo(newRequest) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CoreSettingsFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CoreSettingsFragment.kt index 2e41ad9d1..9dce63794 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CoreSettingsFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CoreSettingsFragment.kt @@ -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) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt index d06f657a7..193b220c3 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/KiwixAppBar.kt @@ -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 -} diff --git a/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt b/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt index e754807c3..ef0fed93d 100644 --- a/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt +++ b/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt @@ -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) } )