diff --git a/app/src/main/java/org/kiwix/kiwixmobile/ui/BookItem.kt b/app/src/main/java/org/kiwix/kiwixmobile/ui/BookItem.kt index 34e9106fd..bc5b90734 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/ui/BookItem.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/ui/BookItem.kt @@ -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) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/Base64String.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/Base64String.kt index a4f3b0dfd..df06f6980 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/Base64String.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/Base64String.kt @@ -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) + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/BookExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/BookExtensions.kt index 01cf8ad42..b023c1cb4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/BookExtensions.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/BookExtensions.kt @@ -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) - } -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt index b5af06fd3..16142e89c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt @@ -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() { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageListItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageListItem.kt new file mode 100644 index 000000000..a74c560d2 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageListItem.kt @@ -0,0 +1,86 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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 + ) + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageScreen.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageScreen.kt new file mode 100644 index 000000000..8eae0c5f0 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageScreen.kt @@ -0,0 +1,137 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * 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 . + * + */ + +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>, + effects: PublishProcessor>, + 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) + ) +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/BookmarksFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/BookmarksFragment.kt index dd776ea11..2ba944d47 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/BookmarksFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/BookmarksFragment.kt @@ -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 { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/HistoryFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/HistoryFragment.kt index f2545bfa5..85d2d822e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/HistoryFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/HistoryFragment.kt @@ -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) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/NotesFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/NotesFragment.kt index 68fd9827a..4c6a4ae44 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/NotesFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/NotesFragment.kt @@ -30,8 +30,7 @@ import org.kiwix.kiwixmobile.core.page.notes.viewmodel.NotesViewModel class NotesFragment : PageFragment() { override val pageViewModel by lazy { viewModel(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)) 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 8016750a2..d48dc6106 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 @@ -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 }