Fixed: Clicking on "Plus button" in tab switcher crashes the application.

* Refactored the "FullScreen Video" functionality according to compose UI.
* Refactored the functionality where custom app is configured to show search placeholder in toolbar, and created the search placeholder in compose.
* Refactored the functionality where custom app is configured to show app icon in place of humbuger icon.
This commit is contained in:
MohitMaliFtechiz 2025-06-21 00:46:15 +05:30
parent 234e9169f6
commit 2608694442
10 changed files with 208 additions and 126 deletions

View File

@ -85,7 +85,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
}) })
} }
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar?.let { activity.setupDrawerToggle(it, true) } activity.setupDrawerToggle(true)
openPageInBookFromNavigationArguments() openPageInBookFromNavigationArguments()
} }
@ -180,7 +180,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
* @see closeAllTabs * @see closeAllTabs
*/ */
override fun hideTabSwitcher(shouldCloseZimBook: Boolean) { override fun hideTabSwitcher(shouldCloseZimBook: Boolean) {
toolbar?.let { activity?.setupDrawerToggle(it, true) } activity?.setupDrawerToggle(true)
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
if (webViewList.isEmpty()) { if (webViewList.isEmpty()) {
readerMenuState?.hideTabSwitcher() readerMenuState?.hideTabSwitcher()
@ -302,14 +302,12 @@ class KiwixReaderFragment : CoreReaderFragment() {
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
override fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? { override fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
// requireNotNull(activityMainRoot)
return ToolbarScrollingKiwixWebView( return ToolbarScrollingKiwixWebView(
requireContext(), requireContext(),
this, this,
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"), attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
null, null,
// requireNotNull(videoView), requireNotNull(readerScreenState.value.fullScreenItem.second),
null,
CoreWebViewClient(this, requireNotNull(zimReaderContainer)), CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
// requireNotNull(toolbarContainer), // requireNotNull(toolbarContainer),
// requireNotNull(bottomToolbar), // requireNotNull(bottomToolbar),

View File

@ -33,7 +33,6 @@ import android.view.MenuItem
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -101,8 +100,8 @@ object ActivityExtensions {
val Activity.cachedComponent: CoreActivityComponent val Activity.cachedComponent: CoreActivityComponent
get() = coreMainActivity.cachedComponent get() = coreMainActivity.cachedComponent
fun Activity.setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean = false) = fun Activity.setupDrawerToggle(shouldEnableRightDrawer: Boolean = false) =
coreMainActivity.setupDrawerToggle(toolbar, shouldEnableRightDrawer) coreMainActivity.setupDrawerToggle(shouldEnableRightDrawer)
fun Activity.navigate(fragmentId: Int) { fun Activity.navigate(fragmentId: Int) {
coreMainActivity.navigate(fragmentId) coreMainActivity.navigate(fragmentId)

View File

@ -27,7 +27,6 @@ import android.view.MenuItem
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
@ -59,9 +58,7 @@ import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadMonitorServ
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadMonitorService.Companion.STOP_DOWNLOAD_SERVICE import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadMonitorService.Companion.STOP_DOWNLOAD_SERVICE
import org.kiwix.kiwixmobile.core.error.ErrorActivity import org.kiwix.kiwixmobile.core.error.ErrorActivity
import org.kiwix.kiwixmobile.core.extensions.browserIntent import org.kiwix.kiwixmobile.core.extensions.browserIntent
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
import org.kiwix.kiwixmobile.core.extensions.isServiceRunning import org.kiwix.kiwixmobile.core.extensions.isServiceRunning
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.search.NAV_ARG_SEARCH_STRING import org.kiwix.kiwixmobile.core.search.NAV_ARG_SEARCH_STRING
@ -271,15 +268,16 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
override fun onSupportNavigateUp(): Boolean = override fun onSupportNavigateUp(): Boolean =
navController.navigateUp() || super.onSupportNavigateUp() navController.navigateUp() || super.onSupportNavigateUp()
open fun setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean = false) { open fun setupDrawerToggle(shouldEnableRightDrawer: Boolean = false) {
// Set the initial contentDescription to the hamburger icon. // Set the initial contentDescription to the hamburger icon.
// This method is called from various locations after modifying the navigationIcon. // This method is called from various locations after modifying the navigationIcon.
// For example, we previously changed this icon/contentDescription to the "+" button // For example, we previously changed this icon/contentDescription to the "+" button
// when opening the tabSwitcher. After closing the tabSwitcher, we reset the // when opening the tabSwitcher. After closing the tabSwitcher, we reset the
// contentDescription to the default hamburger icon. // contentDescription to the default hamburger icon.
toolbar.getToolbarNavigationIcon()?.setToolTipWithContentDescription( // Todo we will refactore this when migrating the CoreMainActivity.
getString(R.string.open_drawer) // toolbar.getToolbarNavigationIcon()?.setToolTipWithContentDescription(
) // getString(R.string.open_drawer)
// )
drawerToggle = drawerToggle =
ActionBarDrawerToggle( ActionBarDrawerToggle(
this, this,

View File

@ -75,7 +75,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.edit import androidx.core.content.edit
@ -174,6 +173,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility
import org.kiwix.kiwixmobile.core.ui.models.IconItem import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth import org.kiwix.kiwixmobile.core.utils.DimenUtils.getWindowWidth
import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler
import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler.ShowDonationDialogCallback import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler.ShowDonationDialogCallback
@ -233,8 +233,6 @@ abstract class CoreReaderFragment :
var tabSwitcherRoot: View? = null var tabSwitcherRoot: View? = null
var videoView: ViewGroup? = null
var activityMainRoot: View? = null var activityMainRoot: View? = null
@JvmField @JvmField
@ -273,8 +271,6 @@ abstract class CoreReaderFragment :
protected var actionBar: ActionBar? = null protected var actionBar: ActionBar? = null
protected var mainMenu: MainMenu? = null protected var mainMenu: MainMenu? = null
var toolbarWithSearchPlaceholder: ConstraintLayout? = null
private var tabRecyclerView: RecyclerView? = null private var tabRecyclerView: RecyclerView? = null
private var isFirstTimeMainPageLoaded = true private var isFirstTimeMainPageLoaded = true
private var isFromManageExternalLaunch = false private var isFromManageExternalLaunch = false
@ -374,7 +370,10 @@ abstract class CoreReaderFragment :
closeTab(position) closeTab(position)
} }
}, },
shouldShowFullScreenMode = false shouldShowFullScreenMode = false,
searchPlaceHolderItemForCustomApps = false to {
openSearch(searchString = "", isOpenedFromTabView = false, false)
}
) )
) )
private var readerLifeCycleScope: CoroutineScope? = null private var readerLifeCycleScope: CoroutineScope? = null
@ -486,12 +485,19 @@ abstract class CoreReaderFragment :
updateTabIcon(size) updateTabIcon(size)
} }
} }
LaunchedEffect(currentWebViewIndex, readerMenuState?.isInTabSwitcher) { LaunchedEffect(Unit) {
readerScreenState.update { readerScreenState.update {
copy( copy(
bottomNavigationHeight = getBottomNavigationHeight(), bottomNavigationHeight = getBottomNavigationHeight(),
readerScreenTitle = context.getString(R.string.reader), readerScreenTitle = context.getString(R.string.reader),
darkModeViewPainter = darkModeViewPainter, darkModeViewPainter = darkModeViewPainter,
fullScreenItem = fullScreenItem.first to getVideoView()
)
}
}
LaunchedEffect(currentWebViewIndex, readerMenuState?.isInTabSwitcher) {
readerScreenState.update {
copy(
currentWebViewPosition = currentWebViewIndex, currentWebViewPosition = currentWebViewIndex,
showTabSwitcher = readerMenuState?.isInTabSwitcher == true showTabSwitcher = readerMenuState?.isInTabSwitcher == true
) )
@ -504,7 +510,8 @@ abstract class CoreReaderFragment :
NavigationIcon( NavigationIcon(
iconItem = navigationIcon(), iconItem = navigationIcon(),
contentDescription = navigationIconContentDescription(), contentDescription = navigationIconContentDescription(),
onClick = { navigationIconClick() } onClick = { navigationIconClick() },
iconTint = navigationIconTint()
) )
}, },
listState = lazyListState listState = lazyListState
@ -609,7 +616,15 @@ abstract class CoreReaderFragment :
viewLifecycleOwner, viewLifecycleOwner,
Observer(::storeSearchItem) Observer(::storeSearchItem)
) )
handleClicks() }
private fun getVideoView() = context?.let {
FrameLayout(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
} }
private fun getBottomNavigationHeight(): Int = getBottomNavigationView()?.measuredHeight ?: ZERO private fun getBottomNavigationHeight(): Int = getBottomNavigationView()?.measuredHeight ?: ZERO
@ -639,7 +654,25 @@ abstract class CoreReaderFragment :
} }
} }
private fun navigationIcon() = if (readerMenuState?.isInTabSwitcher == true) { /**
* Returns the tint color to be applied to the navigation icon.
*
* Subclasses (e.g., CustomReaderFragment) can override this method to provide custom behavior,
* such as setting a colored app icon in place of the default hamburger icon when configured.
*
* By default, this returns [White], which is appropriate for vector icons that rely on tinting.
*/
open fun navigationIconTint() = White
/**
* Provides the navigationIcon based on condition.
* Subclasses like CustomReaderFragment override this method to provide custom
* behavior, such as set the app icon on hamburger when configure to not show the title.
*
* WARNING: If modifying this method, ensure thorough testing with custom apps
* to verify proper functionality.
*/
open fun navigationIcon() = if (readerMenuState?.isInTabSwitcher == true) {
IconItem.Drawable(R.drawable.ic_round_add_white_36dp) IconItem.Drawable(R.drawable.ic_round_add_white_36dp)
} else { } else {
IconItem.Vector(Icons.Filled.Menu) IconItem.Vector(Icons.Filled.Menu)
@ -652,25 +685,17 @@ abstract class CoreReaderFragment :
private fun prepareViews() { private fun prepareViews() {
fragmentReaderBinding?.let { readerBinding -> fragmentReaderBinding?.let { readerBinding ->
videoView = readerBinding.fullscreenVideoContainer
with(readerBinding.root) { with(readerBinding.root) {
activityMainRoot = findViewById(R.id.activity_main_root) activityMainRoot = findViewById(R.id.activity_main_root)
contentFrame = findViewById(R.id.activity_main_content_frame) contentFrame = findViewById(R.id.activity_main_content_frame)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
tabSwitcherRoot = findViewById(R.id.activity_main_tab_switcher) tabSwitcherRoot = findViewById(R.id.activity_main_tab_switcher)
toolbarWithSearchPlaceholder = findViewById(R.id.toolbarWithSearchPlaceholder)
tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view) tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view)
donationLayout = findViewById(R.id.donation_layout) donationLayout = findViewById(R.id.donation_layout)
} }
} }
} }
private fun handleClicks() {
toolbarWithSearchPlaceholder?.setOnClickListener {
openSearch(searchString = "", isOpenedFromTabView = false, false)
}
}
private fun initTabCallback() { private fun initTabCallback() {
tabCallback = object : ItemTouchHelper.Callback() { tabCallback = object : ItemTouchHelper.Callback() {
override fun getMovementFlags( override fun getMovementFlags(
@ -859,34 +884,30 @@ abstract class CoreReaderFragment :
showBackToTopButton = false showBackToTopButton = false
) )
} }
showSearchPlaceHolderInToolbar(true)
startAnimation(tabSwitcherRoot, R.anim.slide_down) startAnimation(tabSwitcherRoot, R.anim.slide_down)
tabsAdapter?.let { tabsAdapter ->
tabRecyclerView?.let { recyclerView ->
if (tabsAdapter.selected < webViewList.size &&
recyclerView.layoutManager != null
) {
recyclerView.layoutManager?.scrollToPosition(tabsAdapter.selected)
}
}
// Notify the tabs adapter to update the UI when the tab switcher is shown
// This ensures that any changes made to the adapter's data or views are
// reflected correctly.
tabsAdapter.notifyDataSetChanged()
}
readerMenuState?.showTabSwitcherOptions() readerMenuState?.showTabSwitcherOptions()
} }
/** /**
* Sets the tabs switcher visibility, controlling the visibility of the tab. * Controls the visibility of the search placeholder in the toolbar.
* Subclasses, like CustomReaderFragment, override this method to provide custom
* behavior, such as hiding the placeholder in the toolbar when a custom app is configured
* not to show the title. This is necessary because the same toolbar is used for displaying tabs.
* *
* WARNING: If modifying this method, ensure thorough testing with custom apps * Subclasses (e.g., CustomReaderFragment) can override this method to customize behavior,
* to verify proper functionality. * such as showing a search placeholder instead of the title when the app is configured to
* hide the title. This is important because the same toolbar is shared with the tab display.
*
* NOTE: This method sets `showSearchPlaceHolderForCustomApps` to `false` by default.
* Subclasses must explicitly handle the `true` case if needed.
*
* When modifying this method, thoroughly test with custom app configurations to
* ensure correct toolbar behavior.
*/ */
open fun setTabSwitcherVisibility(visibility: Int) { open fun showSearchPlaceHolderInToolbar(isTabSwitcherShowing: Boolean) {
tabSwitcherRoot?.visibility = visibility readerScreenState.update {
copy(
searchPlaceHolderItemForCustomApps = searchPlaceHolderItemForCustomApps.copy(first = false)
)
}
} }
/** /**
@ -926,7 +947,7 @@ abstract class CoreReaderFragment :
* as closing the ZIM book would require reloading the ZIM file, which can be a resource-intensive operation. * as closing the ZIM book would require reloading the ZIM file, which can be a resource-intensive operation.
*/ */
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) { protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
toolbar?.let(::setUpDrawerToggle) setUpDrawerToggle()
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
selectTab(currentWebViewIndex) selectTab(currentWebViewIndex)
readerScreenState.update { readerScreenState.update {
@ -935,24 +956,15 @@ abstract class CoreReaderFragment :
pageLoadingItem = false to ZERO, pageLoadingItem = false to ZERO,
) )
} }
showSearchPlaceHolderInToolbar(false)
readerMenuState?.showWebViewOptions(urlIsValid()) readerMenuState?.showWebViewOptions(urlIsValid())
// Reset the top margin of web views to 0 to remove any previously set margin // Reset the top margin of web views to 0 to remove any previously set margin
// This ensures that the web views are displayed without any additional top margin for kiwix custom apps. // This ensures that the web views are displayed without any additional top margin for kiwix custom apps.
// setTopMarginToWebViews(0) // setTopMarginToWebViews(0)
} }
/** open fun setUpDrawerToggle() {
* Sets the drawer toggle, controlling the toolbar. (requireActivity() as CoreMainActivity).setupDrawerToggle(true)
* Subclasses like CustomReaderFragment override this method to provide custom
* behavior, such as set the app icon on hamburger when configure to not show the title.
*
* WARNING: If modifying this method, ensure thorough testing with custom apps
* to verify proper functionality.
*/
open fun setUpDrawerToggle(toolbar: Toolbar) {
toolbar.let {
(requireActivity() as CoreMainActivity).setupDrawerToggle(it, true)
}
} }
/** /**
@ -1335,10 +1347,8 @@ abstract class CoreReaderFragment :
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun unBindViewsAndBinding() { private fun unBindViewsAndBinding() {
activityMainRoot = null activityMainRoot = null
toolbarWithSearchPlaceholder = null
tabRecyclerView = null tabRecyclerView = null
tabSwitcherRoot = null tabSwitcherRoot = null
videoView = null
contentFrame = null contentFrame = null
compatCallback?.finish() compatCallback?.finish()
compatCallback = null compatCallback = null
@ -1402,14 +1412,12 @@ abstract class CoreReaderFragment :
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
protected open fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? { protected open fun createWebView(attrs: AttributeSet?): ToolbarScrollingKiwixWebView? {
// requireNotNull(activityMainRoot)
return ToolbarScrollingKiwixWebView( return ToolbarScrollingKiwixWebView(
requireActivity(), requireActivity(),
this, this,
attrs ?: throw IllegalArgumentException("AttributeSet must not be null"), attrs ?: throw IllegalArgumentException("AttributeSet must not be null"),
null, null,
// requireNotNull(readerScreenState.value.fullScreenItem.second), requireNotNull(readerScreenState.value.fullScreenItem.second),
null,
CoreWebViewClient(this, requireNotNull(zimReaderContainer)), CoreWebViewClient(this, requireNotNull(zimReaderContainer)),
// requireNotNull(toolbarContainer), // requireNotNull(toolbarContainer),
// requireNotNull(bottomToolbar), // requireNotNull(bottomToolbar),
@ -1446,7 +1454,6 @@ abstract class CoreReaderFragment :
if (selectTab) { if (selectTab) {
selectTab(webViewList.size - 1) selectTab(webViewList.size - 1)
} }
tabsAdapter?.notifyDataSetChanged()
} }
return webView return webView
} }
@ -1739,10 +1746,18 @@ abstract class CoreReaderFragment :
*/ */
override fun onFullscreenVideoToggled(isFullScreen: Boolean) { override fun onFullscreenVideoToggled(isFullScreen: Boolean) {
if (isFullScreen) { if (isFullScreen) {
readerScreenState.update {
copy(
fullScreenItem = fullScreenItem.copy(first = true),
shouldShowBottomAppBar = false
)
}
(requireActivity() as CoreMainActivity).disableDrawer(false) (requireActivity() as CoreMainActivity).disableDrawer(false)
} else { } else {
readerScreenState.update { copy(fullScreenItem = fullScreenItem.copy(first = false)) }
if (!isInFullScreenMode()) { if (!isInFullScreenMode()) {
toolbar?.let(::setUpDrawerToggle) readerScreenState.update { copy(shouldShowBottomAppBar = true) }
setUpDrawerToggle()
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
} }
} }
@ -1768,7 +1783,7 @@ abstract class CoreReaderFragment :
@Suppress("MagicNumber") @Suppress("MagicNumber")
open fun closeFullScreen() { open fun closeFullScreen() {
toolbar?.let(::setUpDrawerToggle) setUpDrawerToggle()
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
sharedPreferenceUtil?.putPrefFullScreen(false) sharedPreferenceUtil?.putPrefFullScreen(false)
readerScreenState.update { readerScreenState.update {
@ -2187,10 +2202,8 @@ abstract class CoreReaderFragment :
protected fun isInFullScreenMode(): Boolean = sharedPreferenceUtil?.prefFullScreen == true protected fun isInFullScreenMode(): Boolean = sharedPreferenceUtil?.prefFullScreen == true
private fun updateBottomToolbarVisibility() { private fun updateBottomToolbarVisibility() {
// TODO refactroe this code once we integrate the tabSwitcher
// tabSwitcherRoot?.visibility != VISIBLE && !isInFullScreenMode()
readerScreenState.update { readerScreenState.update {
copy(shouldShowBottomAppBar = !isInFullScreenMode()) copy(shouldShowBottomAppBar = !showTabSwitcher && !isInFullScreenMode())
} }
} }
@ -2861,11 +2874,7 @@ abstract class CoreReaderFragment :
try { try {
isFromManageExternalLaunch = true isFromManageExternalLaunch = true
currentWebViewIndex = 0 currentWebViewIndex = 0
tabsAdapter?.apply { webViewList.removeFirstOrNull()
webViewList.removeAt(0)
notifyItemRemoved(0)
notifyDataSetChanged()
}
webViewHistoryItemList.forEach { webViewHistoryItem -> webViewHistoryItemList.forEach { webViewHistoryItem ->
newTab("", shouldLoadUrl = false)?.let { newTab("", shouldLoadUrl = false)?.let {
restoreTabState(it, webViewHistoryItem) restoreTabState(it, webViewHistoryItem)

View File

@ -167,6 +167,7 @@ class ReaderMenuState(
fun hideTabSwitcher() { fun hideTabSwitcher() {
isInTabSwitcher = false isInTabSwitcher = false
updateMenuItems()
} }
private fun updateMenuItems() { private fun updateMenuItems() {

View File

@ -26,6 +26,7 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -48,6 +49,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.BottomAppBarDefaults import androidx.compose.material3.BottomAppBarDefaults
@ -85,6 +87,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -119,14 +122,17 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_ALL_TAB_BUTTON_BOTTO
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_ANIMATION_TIMEOUT import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_ANIMATION_TIMEOUT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_SIZE import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CLOSE_TAB_ICON_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_BUTTON_ICON_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT import org.kiwix.kiwixmobile.core.utils.ComposeDimens.READER_BOTTOM_APP_BAR_LAYOUT_HEIGHT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SEARCH_PLACEHOLDER_TEXT_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SEVEN_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SEVEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_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.TEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.THREE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TTS_BUTTONS_CONTROL_ALPHA
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml import org.kiwix.kiwixmobile.core.utils.StyleUtils.fromHtml
@ -176,12 +182,14 @@ private fun ReaderTopBar(
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
navigationIcon: @Composable () -> Unit, navigationIcon: @Composable () -> Unit,
) { ) {
if (!state.shouldShowFullScreenMode) { if (!state.shouldShowFullScreenMode && !state.fullScreenItem.first) {
KiwixAppBar( KiwixAppBar(
title = if (state.showTabSwitcher) "" else state.readerScreenTitle, title = if (state.showTabSwitcher) "" else state.readerScreenTitle,
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actionMenuItems = actionMenuItems, actionMenuItems = actionMenuItems,
topAppBarScrollBehavior = scrollBehavior topAppBarScrollBehavior = scrollBehavior,
searchBar =
searchPlaceHolderIfActive(state.searchPlaceHolderItemForCustomApps)
) )
} }
} }
@ -199,6 +207,7 @@ private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = M
) )
state.isNoBookOpenInReader -> NoBookOpenView(state.onOpenLibraryButtonClicked) state.isNoBookOpenInReader -> NoBookOpenView(state.onOpenLibraryButtonClicked)
state.fullScreenItem.first -> ShowFullScreenView(state)
else -> { else -> {
ShowZIMFileContent(state) ShowZIMFileContent(state)
@ -214,7 +223,6 @@ private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = M
state.shouldShowBottomAppBar state.shouldShowBottomAppBar
) )
} }
ShowFullScreenView(state)
CloseFullScreenImageButton( CloseFullScreenImageButton(
state.shouldShowFullScreenMode, state.shouldShowFullScreenMode,
state.onExitFullscreenClick state.onExitFullscreenClick
@ -226,6 +234,53 @@ private fun ReaderContentLayout(state: ReaderScreenState, modifier: Modifier = M
} }
} }
@Composable
private fun searchPlaceHolderIfActive(
searchPlaceHolderItemForCustomApps: Pair<Boolean, () -> Unit>
): (@Composable () -> Unit)? = if (searchPlaceHolderItemForCustomApps.first) {
{
SearchPlaceholder(
stringResource(R.string.search_label),
searchPlaceHolderItemForCustomApps.second
)
}
} else {
null
}
@Composable
fun SearchPlaceholder(hint: String, searchPlaceHolderClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(
color = Color.Transparent,
shape = RoundedCornerShape(THREE_DP)
)
.border(
width = 1.5.dp,
color = colorResource(id = R.color.alabaster_white),
shape = RoundedCornerShape(THREE_DP)
)
.padding(horizontal = FIVE_DP, vertical = FIVE_DP)
.clickable(onClick = searchPlaceHolderClick),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = hint,
color = Color.Gray,
modifier = Modifier.weight(1f),
fontSize = SEARCH_PLACEHOLDER_TEXT_SIZE
)
Spacer(modifier = Modifier.width(TEN_DP))
Icon(
painter = IconItem.Drawable(R.drawable.action_search).toPainter(),
contentDescription = null,
tint = White
)
}
}
@Composable @Composable
private fun BoxScope.CloseFullScreenImageButton( private fun BoxScope.CloseFullScreenImageButton(
shouldShowFullScreenMode: Boolean, shouldShowFullScreenMode: Boolean,
@ -254,7 +309,14 @@ private fun ShowZIMFileContent(state: ReaderScreenState) {
state.selectedWebView?.let { selectedWebView -> state.selectedWebView?.let { selectedWebView ->
key(selectedWebView) { key(selectedWebView) {
AndroidView( AndroidView(
factory = { selectedWebView }, factory = { context ->
// Create a new container and add the WebView to it
FrameLayout(context).apply {
// Ensure the WebView has no parent before adding
(selectedWebView.parent as? ViewGroup)?.removeView(selectedWebView)
addView(selectedWebView)
}
},
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }
@ -263,8 +325,8 @@ private fun ShowZIMFileContent(state: ReaderScreenState) {
@Composable @Composable
private fun ShowFullScreenView(state: ReaderScreenState) { private fun ShowFullScreenView(state: ReaderScreenState) {
if (state.fullScreenItem.first) { state.fullScreenItem.second?.let { videoView ->
state.fullScreenItem.second AndroidView(factory = { videoView })
} }
} }

View File

@ -18,8 +18,8 @@
package org.kiwix.kiwixmobile.core.main.reader package org.kiwix.kiwixmobile.core.main.reader
import android.widget.FrameLayout
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.ui.platform.ComposeView
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
import org.kiwix.kiwixmobile.core.main.KiwixWebView import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
@ -60,9 +60,9 @@ data class ReaderScreenState(
* *
* A [Pair] containing: * A [Pair] containing:
* - [Boolean]: Whether to show/hide full screen mode. * - [Boolean]: Whether to show/hide full screen mode.
* - [ComposeView]: full screen view. * - [FrameLayout]: full screen view.
*/ */
val fullScreenItem: Pair<Boolean, ComposeView?>, val fullScreenItem: Pair<Boolean, FrameLayout?>,
/** /**
* Manages the showing of "Full screen mode". * Manages the showing of "Full screen mode".
*/ */
@ -124,7 +124,7 @@ data class ReaderScreenState(
/** /**
* Handles the clicks of next page button in reader bottom toolbar. * Handles the clicks of next page button in reader bottom toolbar.
* *
* A [Pair] containing: * A [Triple] containing:
* - [Unit]: Handles the normal click of button(For going to next page). * - [Unit]: Handles the normal click of button(For going to next page).
* - [Unit]: Handles the long click of button(For showing the next pages history). * - [Unit]: Handles the long click of button(For showing the next pages history).
* - [Boolean]: Handles the button should enable or not. * - [Boolean]: Handles the button should enable or not.
@ -149,4 +149,8 @@ data class ReaderScreenState(
* Manages the click event on tabs. * Manages the click event on tabs.
*/ */
val onTabClickListener: TabClickListener, val onTabClickListener: TabClickListener,
/**
* Manages the showing/hiding of search placeholder in toolbar for custom apps.
*/
val searchPlaceHolderItemForCustomApps: Pair<Boolean, () -> Unit>
) )

View File

@ -192,4 +192,5 @@ object ComposeDimens {
const val CLOSE_TAB_ICON_ANIMATION_TIMEOUT = 1200L const val CLOSE_TAB_ICON_ANIMATION_TIMEOUT = 1200L
val BACK_TO_TOP_BUTTON_BOTTOM_MARGIN = 80.dp val BACK_TO_TOP_BUTTON_BOTTOM_MARGIN = 80.dp
const val READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA = 0.38f const val READER_BOTTOM_APP_BAR_DISABLE_BUTTON_ALPHA = 0.38f
val SEARCH_PLACEHOLDER_TEXT_SIZE = 12.sp
} }

View File

@ -22,7 +22,6 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
@ -102,8 +101,8 @@ class CustomMainActivity : CoreMainActivity() {
} }
} }
override fun setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean) { override fun setupDrawerToggle(shouldEnableRightDrawer: Boolean) {
super.setupDrawerToggle(toolbar, shouldEnableRightDrawer) super.setupDrawerToggle(shouldEnableRightDrawer)
activityCustomMainBinding.drawerNavView.apply { activityCustomMainBinding.drawerNavView.apply {
/** /**
* Hide the 'ZimHostFragment' option from the navigation menu * Hide the 'ZimHostFragment' option from the navigation menu

View File

@ -23,26 +23,27 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.ui.graphics.Color
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.R.dimen
import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.extensions.browserIntent import org.kiwix.kiwixmobile.core.extensions.browserIntent
import org.kiwix.kiwixmobile.core.extensions.getResizedDrawable
import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.update
import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment import org.kiwix.kiwixmobile.core.main.reader.CoreReaderFragment
import org.kiwix.kiwixmobile.core.main.reader.ReaderMenuState import org.kiwix.kiwixmobile.core.main.reader.ReaderMenuState
import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin import org.kiwix.kiwixmobile.core.main.reader.RestoreOrigin
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.LanguageUtils import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower
import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getDemoFilePathForCustomApp import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getDemoFilePathForCustomApp
@ -81,10 +82,11 @@ class CustomReaderFragment : CoreReaderFragment() {
val toolbarToc = val toolbarToc =
activity?.findViewById<ImageView>(org.kiwix.kiwixmobile.core.R.id.bottom_toolbar_toc) activity?.findViewById<ImageView>(org.kiwix.kiwixmobile.core.R.id.bottom_toolbar_toc)
toolbarToc?.isEnabled = false toolbarToc?.isEnabled = false
// TODO refactor this with compose UI.
} }
with(activity as AppCompatActivity) { with(activity as AppCompatActivity) {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar?.let(::setUpDrawerToggle) setUpDrawerToggle()
} }
loadPageFromNavigationArguments() loadPageFromNavigationArguments()
if (BuildConfig.DISABLE_EXTERNAL_LINK) { if (BuildConfig.DISABLE_EXTERNAL_LINK) {
@ -97,25 +99,33 @@ class CustomReaderFragment : CoreReaderFragment() {
} }
/** /**
* Overrides the method to configure the hamburger icon. When the "setting title" is disabled * Returns the tint color for the navigation icon.
* in a custom app, this function set the app logo on hamburger. *
* If the custom app is configured to show the app icon in place of the hamburger icon
* (i.e., [BuildConfig.DISABLE_TITLE] is true), the tint is set to [Color.Unspecified] to preserve
* the original colors of the image.
*
* Otherwise, [White] is used as the default tint, which is suitable for vector icons.
*/ */
override fun setUpDrawerToggle(toolbar: Toolbar) { override fun navigationIconTint(): Color =
super.setUpDrawerToggle(toolbar)
if (BuildConfig.DISABLE_TITLE) { if (BuildConfig.DISABLE_TITLE) {
Color.Unspecified
} else {
White
}
override fun navigationIcon(): IconItem = when {
readerMenuState?.isInTabSwitcher == true -> {
IconItem.Drawable(org.kiwix.kiwixmobile.core.R.drawable.ic_round_add_white_36dp)
}
BuildConfig.DISABLE_TITLE -> {
// if the title is disable then set the app logo to hamburger icon, // if the title is disable then set the app logo to hamburger icon,
// see https://github.com/kiwix/kiwix-android/issues/3528#issuecomment-1814905330 // see https://github.com/kiwix/kiwix-android/issues/3528#issuecomment-1814905330
val iconSize = IconItem.MipmapImage(R.mipmap.ic_launcher)
resources.getDimensionPixelSize(dimen.hamburger_icon_size)
requireActivity().getResizedDrawable(R.mipmap.ic_launcher, iconSize, iconSize)
?.let { drawable ->
super.toolbar?.apply {
navigationIcon = drawable
// remove the default margin between hamburger and placeholder
contentInsetStartWithNavigation = 0
}
}
} }
else -> IconItem.Vector(Icons.Filled.Menu)
} }
/** /**
@ -123,17 +133,16 @@ class CustomReaderFragment : CoreReaderFragment() {
* When the "setting title" is disabled/enabled in a custom app, * When the "setting title" is disabled/enabled in a custom app,
* this function set the visibility of placeholder in toolbar when showing the tabs. * this function set the visibility of placeholder in toolbar when showing the tabs.
*/ */
override fun setTabSwitcherVisibility(visibility: Int) { override fun showSearchPlaceHolderInToolbar(isTabSwitcherShowing: Boolean) {
if (BuildConfig.DISABLE_TITLE) { if (BuildConfig.DISABLE_TITLE) {
// If custom apps are configured to show the placeholder, // If custom apps are configured to show the placeholder,
// and if tabs are visible, hide the placeholder. // and if tabs are visible, hide the placeholder.
// If tabs are hidden, show the placeholder. // If tabs are hidden, show the placeholder.
updateToolbarSearchPlaceholderVisibility(if (visibility == VISIBLE) GONE else VISIBLE) updateToolbarSearchPlaceholderVisibility(!isTabSwitcherShowing)
} else { } else {
// Permanently hide the placeholder if the custom app is not configured to show it. // Permanently hide the placeholder if the custom app is not configured to show it.
updateToolbarSearchPlaceholderVisibility(GONE) updateToolbarSearchPlaceholderVisibility(false)
} }
super.setTabSwitcherVisibility(visibility)
} }
private fun loadPageFromNavigationArguments() { private fun loadPageFromNavigationArguments() {
@ -346,21 +355,23 @@ class CustomReaderFragment : CoreReaderFragment() {
*/ */
override fun updateTitle() { override fun updateTitle() {
if (BuildConfig.DISABLE_TITLE) { if (BuildConfig.DISABLE_TITLE) {
// Set an empty title for the toolbar because we are handling the toolbar click on behalf of this title.
// Since we have increased the zone for triggering search suggestions (see https://github.com/kiwix/kiwix-android/pull/3566), // Since we have increased the zone for triggering search suggestions (see https://github.com/kiwix/kiwix-android/pull/3566),
// we need to set this title for handling the toolbar click, // we need to set this title for handling the toolbar click,
// even if it is empty. If we do not set up this title, // even if it is empty. If we do not set up this title,
// the search screen will open if the user clicks on the toolbar from the tabs screen. // the search screen will open if the user clicks on the toolbar from the tabs screen.
actionBar?.title = " " updateToolbarSearchPlaceholderVisibility(true)
updateToolbarSearchPlaceholderVisibility(VISIBLE)
} else { } else {
updateToolbarSearchPlaceholderVisibility(GONE) updateToolbarSearchPlaceholderVisibility(false)
super.updateTitle() super.updateTitle()
} }
} }
private fun updateToolbarSearchPlaceholderVisibility(visibility: Int) { private fun updateToolbarSearchPlaceholderVisibility(show: Boolean) {
toolbarWithSearchPlaceholder?.visibility = visibility readerScreenState.update {
copy(
searchPlaceHolderItemForCustomApps = searchPlaceHolderItemForCustomApps.copy(first = show)
)
}
} }
override fun createNewTab() { override fun createNewTab() {