diff --git a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt index 1b5ec78d3..4ef0610ad 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt @@ -52,6 +52,7 @@ import org.kiwix.kiwixmobile.core.R.string import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE +import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.extensions.applyEdgeToEdgeInsets import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.main.ACTION_NEW_TAB @@ -68,6 +69,7 @@ const val NAVIGATE_TO_ZIM_HOST_FRAGMENT = "navigate_to_zim_host_fragment" const val ACTION_GET_CONTENT = "GET_CONTENT" const val OPENING_ZIM_FILE_DELAY = 300L const val GET_CONTENT_SHORTCUT_ID = "get_content_shortcut" +const val KIWIX_BOTTOM_BAR_ANIMATION_DURATION = 250L class KiwixMainActivity : CoreMainActivity() { private var actionMode: ActionMode? = null @@ -223,6 +225,25 @@ class KiwixMainActivity : CoreMainActivity() { setDefaultDeviceLanguage() } + /** + * This is for manually showing/hiding the BottomNavigationView with animation from compose + * screens until we migrate the BottomNavigationView to compose. Once we migrate we will remove it. + * + * TODO Remove this once we migrate to compose. + */ + fun toggleBottomNavigation(isVisible: Boolean) { + activityKiwixMainBinding.bottomNavView.animate() + ?.translationY( + if (isVisible) { + ZERO.toFloat() + } else { + activityKiwixMainBinding.bottomNavView.height.toFloat() + } + ) + ?.setDuration(KIWIX_BOTTOM_BAR_ANIMATION_DURATION) + ?.start() + } + private fun setDefaultDeviceLanguage() { if (sharedPreferenceUtil.prefDeviceDefaultLanguage.isEmpty()) { ConfigurationCompat.getLocales( diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt index 0b4192cab..39457c532 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt @@ -36,9 +36,11 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.core.app.ActivityCompat @@ -79,6 +81,7 @@ import org.kiwix.kiwixmobile.core.navigateToSettings import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon +import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem import org.kiwix.kiwixmobile.core.ui.models.IconItem import org.kiwix.kiwixmobile.core.utils.EXTERNAL_SELECT_POSITION @@ -173,14 +176,19 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal ): View? { LanguageUtils(requireActivity()) .changeFont(requireActivity(), sharedPreferenceUtil) - - val composeView = ComposeView(requireContext()).apply { + return ComposeView(requireContext()).apply { setContent { + val lazyListState = rememberLazyListState() + val isBottomNavVisible = rememberBottomNavigationVisibility(lazyListState) + LaunchedEffect(isBottomNavVisible) { + (requireActivity() as KiwixMainActivity).toggleBottomNavigation(isBottomNavVisible) + } updateLibraryScreenState( bottomNavigationHeight = getBottomNavigationHeight(), actionMenuItems = actionMenuItems() ) LocalLibraryScreen( + listState = lazyListState, state = libraryScreenState.value, fabButtonClick = { filePickerButtonClick() }, onClick = { onBookItemClick(it) }, @@ -192,25 +200,25 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal NavigationIcon( iconItem = IconItem.Vector(Icons.Filled.Menu), contentDescription = string.open_drawer, - onClick = { - // Manually handle the navigation open/close. - // Since currently we are using the view based navigation drawer in other screens. - // Once we fully migrate to jetpack compose we will refactor this code to use the - // compose navigation. - // TODO Replace with compose based navigation when migration is done. - val activity = activity as CoreMainActivity - if (activity.navigationDrawerIsOpen()) { - activity.closeNavigationDrawer() - } else { - activity.openNavigationDrawer() - } - } + onClick = { navigationIconClick() } ) } } } + } - return composeView + private fun navigationIconClick() { + // Manually handle the navigation open/close. + // Since currently we are using the view based navigation drawer in other screens. + // Once we fully migrate to jetpack compose we will refactor this code to use the + // compose navigation. + // TODO Replace with compose based navigation when migration is done. + val activity = activity as CoreMainActivity + if (activity.navigationDrawerIsOpen()) { + activity.closeNavigationDrawer() + } else { + activity.openNavigationDrawer() + } } private fun actionMenuItems() = listOf( diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt index e428f8812..c73e4e7a8 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt @@ -34,12 +34,14 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -61,8 +63,7 @@ import org.kiwix.kiwixmobile.core.ui.theme.Black import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme import org.kiwix.kiwixmobile.core.ui.theme.White import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP -import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP -import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TEN_DP +import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FAB_ICON_BOTTOM_MARGIN import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.ui.BookItem @@ -74,6 +75,7 @@ import org.kiwix.kiwixmobile.zimManager.fileselectView.FileSelectListState @Composable fun LocalLibraryScreen( state: LocalLibraryScreenState, + listState: LazyListState, onRefresh: () -> Unit, onDownloadButtonClick: () -> Unit, fabButtonClick: () -> Unit, @@ -82,14 +84,18 @@ fun LocalLibraryScreen( onMultiSelect: ((BookOnDisk) -> Unit)? = null, navigationIcon: @Composable () -> Unit ) { - val (bottomNavHeight, lazyListState) = rememberScrollBehavior(state) + val (bottomNavHeight, lazyListState) = rememberScrollBehavior(state, listState) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() KiwixTheme { Scaffold( snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) }, topBar = { - KiwixAppBar(R.string.library, navigationIcon, state.actionMenuItems, lazyListState) + KiwixAppBar(R.string.library, navigationIcon, state.actionMenuItems, scrollBehavior) }, - modifier = Modifier.systemBarsPadding() + floatingActionButton = { SelectFileButton(fabButtonClick) }, + modifier = Modifier + .systemBarsPadding() + .nestedScroll(scrollBehavior.nestedScrollConnection) ) { contentPadding -> SwipeRefreshLayout( isRefreshing = state.swipeRefreshItem.first, @@ -117,13 +123,6 @@ fun LocalLibraryScreen( lazyListState ) } - - SelectFileButton( - fabButtonClick, - Modifier - .align(Alignment.BottomEnd) - .padding(end = SIXTEEN_DP, bottom = TEN_DP) - ) } } } @@ -131,11 +130,13 @@ fun LocalLibraryScreen( @Composable private fun rememberScrollBehavior( - state: LocalLibraryScreenState + state: LocalLibraryScreenState, + listState: LazyListState, ): Pair, LazyListState> { val bottomNavHeightInDp = with(LocalDensity.current) { state.bottomNavigationHeight.toDp() } val bottomNavHeight = remember { mutableStateOf(bottomNavHeightInDp) } val lazyListState = rememberLazyListScrollListener( + lazyListState = listState, onScrollChanged = { direction -> when (direction) { ScrollDirection.SCROLL_UP -> { @@ -185,10 +186,10 @@ private fun BookItemList( } @Composable -private fun SelectFileButton(fabButtonClick: () -> Unit, modifier: Modifier) { +private fun SelectFileButton(fabButtonClick: () -> Unit) { FloatingActionButton( onClick = fabButtonClick, - modifier = modifier, + modifier = Modifier.padding(bottom = FAB_ICON_BOTTOM_MARGIN), containerColor = Black, shape = MaterialTheme.shapes.extraLarge ) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/webserver/ZimHostScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/webserver/ZimHostScreen.kt index a2aa093a6..bb737fe21 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/webserver/ZimHostScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/webserver/ZimHostScreen.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -69,6 +70,7 @@ import org.kiwix.kiwixmobile.ui.ZimFilesLanguageHeader const val START_SERVER_BUTTON_TESTING_TAG = "startServerButtonTestingTag" const val QR_IMAGE_TESTING_TAG = "qrImageTestingTag" +@OptIn(ExperimentalMaterial3Api::class) @Suppress("ComposableLambdaParameterNaming", "LongParameterList") @Composable fun ZimHostScreen( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialogScreen.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialogScreen.kt index c6ed050bc..5772c6711 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialogScreen.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialogScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -57,6 +58,7 @@ const val SAVE_MENU_BUTTON_TESTING_TAG = "saveMenuButtonTestingTag" const val SHARE_MENU_BUTTON_TESTING_TAG = "shareMenuButtonTestingTag" const val DELETE_MENU_BUTTON_TESTING_TAG = "deleteMenuButtonTestingTag" +@OptIn(ExperimentalMaterial3Api::class) @Suppress("ComposableLambdaParameterNaming") @Composable fun AddNoteDialogScreen( 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 d282c9feb..6e7044eae 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 @@ -19,20 +19,20 @@ package org.kiwix.kiwixmobile.core.ui.components import androidx.annotation.StringRes -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +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 @@ -47,55 +47,58 @@ 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.unit.dp +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 import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme import org.kiwix.kiwixmobile.core.ui.theme.MineShaftGray350 import org.kiwix.kiwixmobile.core.ui.theme.White -import org.kiwix.kiwixmobile.core.utils.ComposeDimens.KIWIX_APP_BAR_HEIGHT import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP -import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP const val TOOLBAR_TITLE_TESTING_TAG = "toolbarTitle" +@OptIn(ExperimentalMaterial3Api::class) @Composable fun KiwixAppBar( @StringRes titleId: Int, navigationIcon: @Composable () -> Unit, actionMenuItems: List = emptyList(), - // If this state is provided, the app bar will automatically hide on scroll down and show - // on scroll up, same like scrollingToolbar. - lazyListState: LazyListState? = null, + topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), // Optional search bar, used in fragments that require it searchBar: (@Composable () -> Unit)? = null ) { - val isToolbarVisible = rememberToolbarVisibility(lazyListState) - - val appBarHeight by animateDpAsState( - targetValue = if (isToolbarVisible) KIWIX_APP_BAR_HEIGHT else 0.dp, - animationSpec = tween(durationMillis = 250) - ) KiwixTheme { - Row( - modifier = Modifier - .fillMaxWidth() - .height(appBarHeight) - .background(color = Black), - verticalAlignment = Alignment.CenterVertically - ) { - Spacer(Modifier.padding(start = TWO_DP)) - navigationIcon() - searchBar?.let { - // Display the search bar when provided - it() - } ?: run { - // Otherwise, show the title - AppBarTitle(titleId) - } - Spacer(Modifier.weight(1f)) - ActionMenu(actionMenuItems) + TopAppBar( + title = { AppBarTitleSection(titleId, searchBar) }, + navigationIcon = navigationIcon, + actions = { ActionMenu(actionMenuItems) }, + scrollBehavior = topAppBarScrollBehavior, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Black, + scrolledContainerColor = Black + ) + ) + } +} + +@Suppress("ComposableLambdaParameterNaming") +@Composable +private fun AppBarTitleSection( + @StringRes titleId: Int, + searchBar: (@Composable () -> Unit)? = null +) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(start = SIXTEEN_DP), + contentAlignment = Alignment.CenterStart + ) { + searchBar?.let { + it() + } ?: run { + AppBarTitle(titleId) } } } @@ -113,9 +116,10 @@ private fun AppBarTitle( text = stringResource(titleId), color = appBarTitleColor, style = MaterialTheme.typography.titleMedium, + overflow = TextOverflow.Ellipsis, + maxLines = 1, modifier = Modifier - .padding(horizontal = SIXTEEN_DP) - .testTag(TOOLBAR_TITLE_TESTING_TAG) + .testTag(TOOLBAR_TITLE_TESTING_TAG), ) } @@ -139,9 +143,9 @@ private fun ActionMenu(actionMenuItems: List) { } @Composable -private fun rememberToolbarVisibility(lazyListState: LazyListState?): Boolean { +fun rememberBottomNavigationVisibility(lazyListState: LazyListState?): Boolean { var isToolbarVisible by remember { mutableStateOf(true) } - var lastScrollIndex by remember { mutableIntStateOf(0) } + var lastScrollIndex by remember { mutableIntStateOf(ZERO) } val updatedLazyListState = rememberUpdatedState(lazyListState) LaunchedEffect(updatedLazyListState) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/LazyListScrollListener.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/LazyListScrollListener.kt index 89ad535b3..da3ed1d5e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/LazyListScrollListener.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/LazyListScrollListener.kt @@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.core.ui.components import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -34,10 +33,10 @@ const val ONE_THOUSAND = 1000 @Composable fun rememberLazyListScrollListener( + lazyListState: LazyListState, onScrollChanged: (ScrollDirection) -> Unit, scrollThreshold: Int = 20 ): LazyListState { - val lazyListState = rememberLazyListState() val updatedOnScrollChanged = rememberUpdatedState(onScrollChanged) var previousScrollPosition by remember { mutableIntStateOf(0) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt index acfdc4270..c4ac32a1f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt @@ -97,5 +97,5 @@ object ComposeDimens { val BOOK_ICON_SIZE = 40.dp // LocalLibraryFragment dimens - val FAB_ICON_BOTTOM_MARGIN = 66.dp + val FAB_ICON_BOTTOM_MARGIN = 50.dp }