mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 10:46:53 -04:00
Migrated BookmarkFragment, HistoryFragment, and NotesFragment to Jetpack Compose.
* Created a `PageScreen` composable, which serves as the base screen for all these fragments. Each fragment can customize it as needed. * Introduced a reusable `PageListItem` composable for consistent list item rendering. * Added an extension function to convert the favicon to a Compose ImageBitmap. If no favicon is available, it falls back to the default ZIM icon, maintaining behavior from the XML-based UI.
This commit is contained in:
parent
45724dca02
commit
bceebd5de4
@ -44,7 +44,8 @@ import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.extensions.faviconToPainter
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.BOOK_ICON_SIZE
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
|
||||
@ -54,8 +55,8 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.KiloByte
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.ArticleCount
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
|
||||
|
||||
const val BOOK_ITEM_CHECKBOX_TESTING_TAG = "bookItemCheckboxTestingTag"
|
||||
const val BOOK_ITEM_TESTING_TAG = "bookItemTestingTag"
|
||||
@ -115,7 +116,7 @@ private fun BookContent(
|
||||
if (selectionMode == SelectionMode.MULTI) {
|
||||
BookCheckbox(bookOnDisk, selectionMode, onMultiSelect, onClick, index)
|
||||
}
|
||||
BookIcon(bookOnDisk.book.faviconToPainter())
|
||||
BookIcon(Base64String(bookOnDisk.book.favicon).toPainter())
|
||||
BookDetails(Modifier.weight(1f), bookOnDisk)
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,13 @@ package org.kiwix.kiwixmobile.core.downloader.model
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
|
||||
@JvmInline
|
||||
value class Base64String(private val encodedString: String?) {
|
||||
@ -35,3 +42,13 @@ value class Base64String(private val encodedString: String?) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Base64String.toPainter(): Painter {
|
||||
val bitmap = remember(this) { toBitmap() }
|
||||
return if (bitmap != null) {
|
||||
BitmapPainter(bitmap.asImageBitmap())
|
||||
} else {
|
||||
painterResource(id = R.drawable.default_zim_file_icon)
|
||||
}
|
||||
}
|
||||
|
@ -18,15 +18,7 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.core.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import org.kiwix.kiwixmobile.core.CoreApp
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.core.utils.BookUtils
|
||||
import org.kiwix.kiwixmobile.core.utils.NetworkUtils
|
||||
@ -62,14 +54,3 @@ fun Book.buildSearchableText(bookUtils: BookUtils): String =
|
||||
append("|")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
@Composable
|
||||
fun Book.faviconToPainter(): Painter {
|
||||
val base64String = Base64String(favicon)
|
||||
val bitmap = remember(base64String) { base64String.toBitmap() }
|
||||
return if (bitmap != null) {
|
||||
BitmapPainter(bitmap.asImageBitmap())
|
||||
} else {
|
||||
painterResource(id = R.drawable.default_zim_file_icon)
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.view.MenuHost
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@ -67,7 +68,7 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
|
||||
@Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil
|
||||
private var actionMode: ActionMode? = null
|
||||
val compositeDisposable = CompositeDisposable()
|
||||
abstract val screenTitle: String
|
||||
abstract val screenTitle: Int
|
||||
abstract val noItemsString: String
|
||||
abstract val switchString: String
|
||||
abstract val searchQueryHint: String
|
||||
@ -78,7 +79,6 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
|
||||
override val fragmentToolbar: Toolbar? by lazy {
|
||||
fragmentPageBinding?.root?.findViewById(R.id.toolbar)
|
||||
}
|
||||
override val fragmentTitle: String? by lazy { screenTitle }
|
||||
|
||||
private val actionModeCallback: ActionMode.Callback =
|
||||
object : ActionMode.Callback {
|
||||
@ -145,7 +145,7 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupMenu()
|
||||
// setupMenu()
|
||||
val activity = requireActivity() as CoreMainActivity
|
||||
fragmentPageBinding?.recyclerView?.apply {
|
||||
layoutManager =
|
||||
@ -182,8 +182,24 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
fragmentPageBinding = FragmentPageBinding.inflate(inflater, container, false)
|
||||
return fragmentPageBinding?.root
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
PageScreen(
|
||||
pageState = pageViewModel.state,
|
||||
effects = pageViewModel.effects,
|
||||
screenTitle = screenTitle,
|
||||
noItemsString = noItemsString,
|
||||
switchString = switchString,
|
||||
searchQueryHint = searchQueryHint,
|
||||
switchIsChecked = switchIsChecked,
|
||||
onSwitchChanged = { isChecked ->
|
||||
pageViewModel.actions.offer(Action.UserClickedShowAllToggle(isChecked))
|
||||
},
|
||||
onItemClick = { pageViewModel.actions.offer(Action.OnItemClick(it)) },
|
||||
onItemLongClick = { pageViewModel.actions.offer(Action.OnItemLongClick(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.page
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.Page
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.PAGE_LIST_ITEM_FAVICON_SIZE
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PageListItem(
|
||||
page: Page,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(
|
||||
horizontal = SIXTEEN_DP,
|
||||
vertical = EIGHT_DP
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = if (page.isSelected) {
|
||||
painterResource(id = R.drawable.ic_check_circle_blue_24dp)
|
||||
} else {
|
||||
Base64String(page.favicon).toPainter()
|
||||
},
|
||||
contentDescription = stringResource(R.string.fav_icon),
|
||||
modifier = Modifier
|
||||
.size(PAGE_LIST_ITEM_FAVICON_SIZE)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(SIXTEEN_DP))
|
||||
|
||||
Text(
|
||||
text = page.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
137
core/src/main/java/org/kiwix/kiwixmobile/core/page/PageScreen.kt
Normal file
137
core/src/main/java/org/kiwix/kiwixmobile/core/page/PageScreen.kt
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.page
|
||||
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import org.kiwix.kiwixmobile.core.base.SideEffect
|
||||
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
|
||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||
import org.kiwix.kiwixmobile.core.page.adapter.Page
|
||||
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.DateItem
|
||||
import org.kiwix.kiwixmobile.core.page.viewmodel.PageState
|
||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
|
||||
@Suppress("LongParameterList", "IgnoredReturnValue", "UnusedParameter")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PageScreen(
|
||||
pageState: MutableLiveData<out PageState<out Page>>,
|
||||
effects: PublishProcessor<SideEffect<*>>,
|
||||
screenTitle: Int,
|
||||
noItemsString: String,
|
||||
switchString: String,
|
||||
searchQueryHint: String,
|
||||
switchIsChecked: Boolean,
|
||||
onSwitchChanged: (Boolean) -> Unit,
|
||||
onItemClick: (Page) -> Unit,
|
||||
onItemLongClick: (Page) -> Unit
|
||||
) {
|
||||
val context = LocalActivity.current as CoreMainActivity
|
||||
|
||||
val state by pageState.observeAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
effects.subscribe { it.invokeWith(context) }
|
||||
}
|
||||
|
||||
KiwixTheme {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Column {
|
||||
KiwixAppBar(
|
||||
titleId = screenTitle,
|
||||
navigationIcon = {},
|
||||
)
|
||||
if (!context.isCustomApp()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SIXTEEN_DP, vertical = EIGHT_DP),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(switchString, modifier = Modifier.weight(1f))
|
||||
Switch(checked = switchIsChecked, onCheckedChange = onSwitchChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
val items = state?.visiblePageItems.orEmpty()
|
||||
Box(modifier = Modifier.padding(padding)) {
|
||||
if (items.isEmpty()) {
|
||||
Text(
|
||||
text = noItemsString,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
} else {
|
||||
LazyColumn {
|
||||
items(items) { item ->
|
||||
when (item) {
|
||||
is Page -> {
|
||||
PageListItem(
|
||||
page = item,
|
||||
onClick = { onItemClick(item) },
|
||||
onLongClick = { onItemLongClick(item) }
|
||||
)
|
||||
}
|
||||
|
||||
is DateItem -> {
|
||||
DateItemText(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DateItemText(dateItem: DateItem) {
|
||||
Text(
|
||||
text = dateItem.dateString,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(SIXTEEN_DP)
|
||||
)
|
||||
}
|
@ -16,7 +16,7 @@ class BookmarksFragment : PageFragment() {
|
||||
PageAdapter(PageItemDelegate(this))
|
||||
}
|
||||
|
||||
override val screenTitle: String by lazy { getString(R.string.bookmarks) }
|
||||
override val screenTitle: Int by lazy { R.string.bookmarks }
|
||||
override val noItemsString: String by lazy { getString(R.string.no_bookmarks) }
|
||||
override val switchString: String by lazy { getString(R.string.bookmarks_from_current_book) }
|
||||
override val deleteIconTitle: String by lazy {
|
||||
|
@ -21,7 +21,7 @@ class HistoryFragment : PageFragment() {
|
||||
|
||||
override val noItemsString: String by lazy { getString(R.string.no_history) }
|
||||
override val switchString: String by lazy { getString(R.string.history_from_current_book) }
|
||||
override val screenTitle: String by lazy { getString(R.string.history) }
|
||||
override val screenTitle: Int by lazy { R.string.history }
|
||||
override val deleteIconTitle: String by lazy {
|
||||
getString(R.string.pref_clear_all_history_title)
|
||||
}
|
||||
|
@ -30,8 +30,7 @@ import org.kiwix.kiwixmobile.core.page.notes.viewmodel.NotesViewModel
|
||||
class NotesFragment : PageFragment() {
|
||||
override val pageViewModel by lazy { viewModel<NotesViewModel>(viewModelFactory) }
|
||||
|
||||
override val screenTitle: String
|
||||
get() = getString(R.string.pref_notes)
|
||||
override val screenTitle: Int get() = R.string.pref_notes
|
||||
|
||||
override val pageAdapter: PageAdapter by lazy {
|
||||
PageAdapter(PageDelegate.PageItemDelegate(this))
|
||||
|
@ -106,4 +106,7 @@ object ComposeDimens {
|
||||
val HELP_SCREEN_ITEM_TITLE_TEXT_SIZE = 20.sp
|
||||
val HELP_SCREEN_ITEM_TITLE_LETTER_SPACING = 0.0125.em
|
||||
val HELP_SCREEN_ARROW_ICON_SIZE = 35.dp
|
||||
|
||||
// Page dimens
|
||||
val PAGE_LIST_ITEM_FAVICON_SIZE = 40.dp
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user