Migrated KiwixMainActivity and CustomMainActivity to Jetpack Compose.

This commit is contained in:
MohitMaliFtechiz 2025-07-18 01:59:25 +05:30
parent 9853e0abd7
commit 095317e102
8 changed files with 307 additions and 0 deletions

View File

@ -0,0 +1,27 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.main
import androidx.annotation.IdRes
data class BottomNavItem(
@IdRes val id: Int,
val title: String,
val iconRes: Int
)

View File

@ -0,0 +1,156 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.main
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.BottomAppBarScrollBehavior
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentManager
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import org.kiwix.kiwixmobile.R.drawable
import org.kiwix.kiwixmobile.R.id
import org.kiwix.kiwixmobile.core.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomNavigationBar(
navController: NavController,
scrollBehavior: BottomAppBarScrollBehavior,
topLevelDestinations: List<Int>
) {
val bottomNavItems = listOf(
BottomNavItem(
id = id.readerFragment,
title = stringResource(id = R.string.reader),
iconRes = drawable.ic_reader_navigation_white_24px
),
BottomNavItem(
id = id.libraryFragment,
title = stringResource(id = R.string.library),
iconRes = drawable.ic_library_navigation_white_24dp
),
BottomNavItem(
id = id.downloadsFragment,
title = stringResource(id = R.string.download),
iconRes = drawable.ic_download_navigation_white_24dp
)
)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestinationId = navBackStackEntry?.destination?.id
if (currentDestinationId in topLevelDestinations) {
BottomAppBar(scrollBehavior = scrollBehavior) {
bottomNavItems.forEach { item ->
NavigationBarItem(
selected = currentDestinationId == item.id,
onClick = { navController.navigate(item.id) },
icon = {
Icon(
painter = painterResource(id = item.iconRes),
contentDescription = item.title
)
},
label = { Text(item.title) }
)
}
}
}
}
@Composable
fun MainNavGraph(
fragmentManager: FragmentManager,
navGraphId: Int
) {
val navController = remember {
fragmentManager.findNavController(R.id.nav_host_fragment)
}
// Drawer states
val leftDrawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val rightDrawerVisible = remember { mutableStateOf(false) }
// Bottom nav destinations
val bottomNavDestinations = listOf(
id.readerFragment,
id.libraryFragment,
id.downloadsFragment
)
// Observe current destination
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestinationId = navBackStackEntry?.destination?.id
// Coroutine scope for drawer
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = leftDrawerState,
drawerContent = { DrawerContentLeft() }
) {
Box(modifier = Modifier.fillMaxSize()) {
// Fragment content
FragmentContainer(
fragmentManager = fragmentManager,
containerId = R.id.nav_host_fragment,
navGraphId = navGraphId
)
// Right drawer (slide in)
AnimatedVisibility(
visible = rightDrawerVisible.value,
enter = slideInHorizontally(initialOffsetX = { it }),
exit = slideOutHorizontally(targetOffsetX = { it }),
modifier = Modifier.align(Alignment.CenterEnd)
) {
DrawerContentRight(
onClose = { rightDrawerVisible.value = false }
)
}
// Bottom nav only on selected destinations
if (currentDestinationId in bottomNavDestinations) {
BottomNavigationBar(
navController = navController,
)
}
}
}
}

View File

@ -372,4 +372,6 @@ object Libs {
const val COIL3_COMPOSE = "io.coil-kt.coil3:coil-compose:${Versions.COIL_COMPOSE}"
const val COIL3_OKHTTP_COMPOSE = "io.coil-kt.coil3:coil-network-okhttp:${Versions.COIL_COMPOSE}"
const val COMPOSE_NAVIGATION =
"androidx.navigation:navigation-compose:${Versions.COMPOSE_NAVIGATION}"
}

View File

@ -117,6 +117,8 @@ object Versions {
const val TURBINE_FLOW_TEST = "1.2.0"
const val COIL_COMPOSE = "3.2.0"
const val COMPOSE_NAVIGATION = "2.7.7"
}
/**

View File

@ -245,6 +245,7 @@ class AllProjectConfigurer {
implementation(Libs.COMPOSE_LIVE_DATA)
implementation(Libs.COIL3_COMPOSE)
implementation(Libs.COIL3_OKHTTP_COMPOSE)
implementation(Libs.COMPOSE_NAVIGATION)
// Compose UI test implementation
androidTestImplementation(Libs.COMPOSE_UI_TEST_JUNIT)

View File

@ -0,0 +1,29 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.main
data class DrawerMenuItem(
val id: Int,
val title: String,
val iconRes: Int,
val visible: Boolean = true,
val onClick: () -> Unit
)
data class DrawerMenuGroup(val drawerMenuItemList: List<DrawerMenuItem>)

View File

@ -0,0 +1,87 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.main
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import org.kiwix.kiwixmobile.core.R
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.NAVIGATION_DRAWER_WIDTH
@Composable
fun MainDrawerMenu(drawerMenuGroupList: List<DrawerMenuGroup>) {
Surface(
modifier = Modifier
.width(NAVIGATION_DRAWER_WIDTH)
.fillMaxHeight(),
shadowElevation = EIGHT_DP
) {
Column {
// Banner image at the top
Image(
painter = painterResource(id = R.drawable.ic_home_kiwix_banner),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
drawerMenuGroupList.forEach {
DrawerGroup(it.drawerMenuItemList)
}
}
}
}
@Composable
fun DrawerGroup(items: List<DrawerMenuItem>) {
Column {
items.filter { it.visible }.forEach { item ->
DrawerMenuItemView(item)
}
}
}
@Composable
fun DrawerMenuItemView(item: DrawerMenuItem) {
ListItem(
leadingContent = {
Icon(
painter = painterResource(id = item.iconRes),
contentDescription = item.title
)
},
headlineContent = {
Text(text = item.title)
},
modifier = Modifier
.fillMaxWidth()
.clickable { item.onClick }
)
}

View File

@ -195,4 +195,7 @@ object ComposeDimens {
const val READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA = 0.38f
val SEARCH_PLACEHOLDER_TEXT_SIZE = 12.sp
val DONATION_LAYOUT_MAXIMUM_WIDTH = 400.dp
// MainActivity dimens
val NAVIGATION_DRAWER_WIDTH = 280.dp
}