diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt
index 5013a09cc..85ee32973 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt
@@ -1769,7 +1769,7 @@ abstract class CoreReaderFragment :
}
// opens home screen when user closes all tabs
- protected fun showNoBookOpenViews() {
+ open fun showNoBookOpenViews() {
readerScreenState.update { copy(isNoBookOpenInReader = true) }
}
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 cc5f66f56..85ed18ad7 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
@@ -195,4 +195,8 @@ object ComposeDimens {
// MainActivity dimens
val NAVIGATION_DRAWER_WIDTH = 280.dp
+
+ // Custom download screen dimens
+ val CUSTOM_DOWNLOAD_LAYOUT_TOP_MARGIN = 100.dp
+ val CUSTOM_DOWNLOAD_PROGRESS_BAR_WIDTH = 200.dp
}
diff --git a/core/src/main/res/layout/drawer_right.xml b/core/src/main/res/layout/drawer_right.xml
deleted file mode 100644
index fc5003d0c..000000000
--- a/core/src/main/res/layout/drawer_right.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/core/src/main/res/layout/nav_main.xml b/core/src/main/res/layout/nav_main.xml
deleted file mode 100644
index 0f3fd2a6b..000000000
--- a/core/src/main/res/layout/nav_main.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
diff --git a/core/src/main/res/layout/section_list.xml b/core/src/main/res/layout/section_list.xml
deleted file mode 100644
index b5f7dd9ab..000000000
--- a/core/src/main/res/layout/section_list.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadFragment.kt
index 908e36783..eaa2f237d 100644
--- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadFragment.kt
+++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadFragment.kt
@@ -19,39 +19,33 @@
package org.kiwix.kiwixmobile.custom.download
import android.Manifest.permission.POST_NOTIFICATIONS
-import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
import androidx.core.app.ActivityCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
-import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
-import org.kiwix.kiwixmobile.core.data.remote.isAuthenticationUrl
-import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
-import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission
-import org.kiwix.kiwixmobile.core.extensions.setDistinctDisplayedChild
+import org.kiwix.kiwixmobile.core.extensions.update
import org.kiwix.kiwixmobile.core.extensions.viewModel
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.navigateToAppSettings
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
+import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
+import org.kiwix.kiwixmobile.core.utils.dialog.DialogHost
import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
import org.kiwix.kiwixmobile.custom.customActivityComponent
-import org.kiwix.kiwixmobile.custom.databinding.FragmentCustomDownloadBinding
import org.kiwix.kiwixmobile.custom.download.Action.ClickedDownload
import org.kiwix.kiwixmobile.custom.download.Action.ClickedRetry
-import org.kiwix.kiwixmobile.custom.download.State.DownloadComplete
-import org.kiwix.kiwixmobile.custom.download.State.DownloadFailed
-import org.kiwix.kiwixmobile.custom.download.State.DownloadInProgress
-import org.kiwix.kiwixmobile.custom.download.State.DownloadRequired
import javax.inject.Inject
class CustomDownloadFragment : BaseFragment(), FragmentActivityExtensions {
@@ -68,8 +62,8 @@ class CustomDownloadFragment : BaseFragment(), FragmentActivityExtensions {
var sharedPreferenceUtil: SharedPreferenceUtil? = null
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
-
- private var fragmentCustomDownloadBinding: FragmentCustomDownloadBinding? = null
+ private var composeView: ComposeView? = null
+ private var downloadState = mutableStateOf(State.DownloadRequired)
override fun inject(baseActivity: BaseActivity) {
baseActivity.customActivityComponent.inject(this)
}
@@ -80,12 +74,20 @@ class CustomDownloadFragment : BaseFragment(), FragmentActivityExtensions {
savedInstanceState: Bundle?
): View? {
super.onCreate(savedInstanceState)
- fragmentCustomDownloadBinding =
- FragmentCustomDownloadBinding.inflate(inflater, container, false)
+ composeView = ComposeView(requireContext()).apply {
+ setContent {
+ CustomDownloadScreen(
+ state = downloadState.value,
+ onDownloadClick = { downloadButtonClick() },
+ onRetryClick = { retryButtonClick() }
+ )
+ DialogHost(alertDialogShower as AlertDialogShower)
+ }
+ }
val activity = requireActivity() as CoreMainActivity
viewLifecycleOwner.lifecycleScope.launch {
downloadViewModel.state.collect { state ->
- render(state)
+ downloadState.update { state }
}
}
@@ -95,26 +97,22 @@ class CustomDownloadFragment : BaseFragment(), FragmentActivityExtensions {
effect.invokeWith(activity)
}
}
- return fragmentCustomDownloadBinding?.root
+ return composeView
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- fragmentCustomDownloadBinding?.apply {
- customDownloadRequired.cdDownloadButton.setOnClickListener {
- if (requireActivity().hasNotificationPermission(sharedPreferenceUtil)) {
- performAction(ClickedDownload)
- } else {
- requestNotificationPermission()
- }
- }
- customDownloadError.cdRetryButton.setOnClickListener {
- if (requireActivity().hasNotificationPermission(sharedPreferenceUtil)) {
- performAction(ClickedRetry)
- } else {
- requestNotificationPermission()
- }
- }
+ private fun downloadButtonClick() {
+ if (requireActivity().hasNotificationPermission(sharedPreferenceUtil)) {
+ performAction(ClickedDownload)
+ } else {
+ requestNotificationPermission()
+ }
+ }
+
+ private fun retryButtonClick() {
+ if (requireActivity().hasNotificationPermission(sharedPreferenceUtil)) {
+ performAction(ClickedRetry)
+ } else {
+ requestNotificationPermission()
}
}
@@ -144,63 +142,9 @@ class CustomDownloadFragment : BaseFragment(), FragmentActivityExtensions {
activity?.finish()
}
- private fun render(state: State): Unit? {
- return when (state) {
- DownloadRequired ->
- fragmentCustomDownloadBinding?.cdViewAnimator?.setDistinctDisplayedChild(0)
-
- is DownloadInProgress -> {
- fragmentCustomDownloadBinding?.cdViewAnimator?.setDistinctDisplayedChild(1)
- showDownloadingProgress(state.downloads[0])
- }
-
- is DownloadFailed -> {
- fragmentCustomDownloadBinding?.cdViewAnimator?.setDistinctDisplayedChild(2)
- val errorMessage = context?.let { context ->
- if (state.downloadState.zimUrl?.isAuthenticationUrl == false) {
- return@let getErrorMessageFromDownloadState(state.downloadState, context)
- }
-
- val defaultErrorMessage = getErrorMessageFromDownloadState(state.downloadState, context)
- // Check if `REQUEST_NOT_SUCCESSFUL` indicates an unsuccessful response from the server.
- // If the server does not respond to the URL, we will display a custom message to the user.
- if (defaultErrorMessage == context.getString(
- R.string.failed_state,
- "REQUEST_NOT_SUCCESSFUL"
- )
- ) {
- context.getString(
- R.string.failed_state,
- context.getString(R.string.custom_download_error_message_for_authentication_failed)
- )
- } else {
- defaultErrorMessage
- }
- }
- fragmentCustomDownloadBinding?.customDownloadError?.cdErrorText?.text = errorMessage
- }
-
- DownloadComplete ->
- fragmentCustomDownloadBinding?.cdViewAnimator?.setDistinctDisplayedChild(3)
- }
- }
-
- private fun getErrorMessageFromDownloadState(
- downloadState: DownloadState,
- context: Context
- ): String = "${downloadState.toReadableState(context)}"
-
- private fun showDownloadingProgress(downloadItem: DownloadItem) {
- fragmentCustomDownloadBinding?.customDownloadInProgress?.apply {
- cdDownloadState.text = downloadItem.readableEta
- cdEta.text = context?.let(downloadItem.downloadState::toReadableState)
- cdProgress.progress = downloadItem.progress
- }
- }
-
override fun onDestroyView() {
super.onDestroyView()
- fragmentCustomDownloadBinding?.root?.removeAllViews()
- fragmentCustomDownloadBinding = null
+ composeView?.disposeComposition()
+ composeView = null
}
}
diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadScreen.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadScreen.kt
new file mode 100644
index 000000000..4fc71f733
--- /dev/null
+++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadScreen.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.custom.download
+
+import android.content.Context
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import org.kiwix.kiwixmobile.core.R.string
+import org.kiwix.kiwixmobile.core.data.remote.isAuthenticationUrl
+import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
+import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
+import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
+import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
+import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
+import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CUSTOM_DOWNLOAD_LAYOUT_TOP_MARGIN
+import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CUSTOM_DOWNLOAD_PROGRESS_BAR_WIDTH
+import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
+import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIX_DP
+import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWENTY_DP
+import org.kiwix.kiwixmobile.custom.R
+import org.kiwix.kiwixmobile.custom.download.State.DownloadComplete
+import org.kiwix.kiwixmobile.custom.download.State.DownloadFailed
+import org.kiwix.kiwixmobile.custom.download.State.DownloadInProgress
+import org.kiwix.kiwixmobile.custom.download.State.DownloadRequired
+
+@Composable
+fun CustomDownloadScreen(
+ state: State,
+ onDownloadClick: () -> Unit,
+ onRetryClick: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .animateContentSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = CUSTOM_DOWNLOAD_LAYOUT_TOP_MARGIN),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.mipmap.ic_launcher_foreground),
+ contentDescription = stringResource(id = R.string.app_name),
+ modifier = Modifier
+ .wrapContentSize()
+ .align(Alignment.CenterHorizontally)
+ )
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Crossfade(targetState = state, label = "download-state") { currentState ->
+ when (currentState) {
+ is DownloadRequired -> DownloadRequiredView(onDownloadClick)
+ is DownloadInProgress -> DownloadInProgressView(currentState.downloads[0])
+ is DownloadFailed -> DownloadErrorView(onRetryClick, currentState)
+ is DownloadComplete -> DownloadCompleteView()
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun DownloadRequiredView(onDownloadClick: () -> Unit) {
+ Column(
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = stringResource(id = R.string.invalid_installation),
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(TWENTY_DP))
+ KiwixButton(
+ clickListener = onDownloadClick,
+ buttonText = stringResource(id = string.download)
+ )
+ }
+}
+
+@Composable
+private fun DownloadInProgressView(downloadItem: DownloadItem) {
+ Column(
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(text = downloadItem.readableEta.toString())
+
+ Spacer(modifier = Modifier.height(TWENTY_DP))
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ ContentLoadingProgressBar(
+ progress = downloadItem.progress,
+ modifier = Modifier
+ .width(CUSTOM_DOWNLOAD_PROGRESS_BAR_WIDTH)
+ .height(SIX_DP),
+ progressBarStyle = ProgressBarStyle.HORIZONTAL
+ )
+ Spacer(modifier = Modifier.width(FIVE_DP))
+ Text(text = downloadItem.downloadState.toReadableState(LocalContext.current).toString())
+ }
+ }
+}
+
+@Composable
+private fun DownloadErrorView(onRetryClick: () -> Unit, downloadFailed: DownloadFailed) {
+ val context = LocalContext.current
+ Column(
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = getActualErrorMessage(downloadFailed, context),
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(TWENTY_DP))
+ KiwixButton(
+ clickListener = onRetryClick,
+ buttonText = stringResource(id = R.string.retry)
+ )
+ }
+}
+
+private fun getActualErrorMessage(downloadFailed: DownloadFailed, context: Context): String {
+ if (downloadFailed.downloadState.zimUrl?.isAuthenticationUrl == false) {
+ return getErrorMessageFromDownloadState(downloadFailed.downloadState, context)
+ }
+
+ val defaultErrorMessage = getErrorMessageFromDownloadState(downloadFailed.downloadState, context)
+ // Check if `REQUEST_NOT_SUCCESSFUL` indicates an unsuccessful response from the server.
+ // If the server does not respond to the URL, we will display a custom message to the user.
+ return if (defaultErrorMessage == context.getString(
+ string.failed_state,
+ "REQUEST_NOT_SUCCESSFUL"
+ )
+ ) {
+ context.getString(
+ string.failed_state,
+ context.getString(string.custom_download_error_message_for_authentication_failed)
+ )
+ } else {
+ defaultErrorMessage
+ }
+}
+
+private fun getErrorMessageFromDownloadState(
+ downloadState: DownloadState,
+ context: Context
+): String = "${downloadState.toReadableState(context)}"
+
+@Composable
+private fun DownloadCompleteView() {
+ Box(
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ contentAlignment = Alignment.TopCenter
+ ) {
+ Text(
+ text = stringResource(id = string.complete),
+ textAlign = TextAlign.Center
+ )
+ }
+}
diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt
index d00b965fc..5e3c83691 100644
--- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt
+++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt
@@ -20,6 +20,8 @@ package org.kiwix.kiwixmobile.custom.main
import android.app.Dialog
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
import android.view.Menu
import android.view.View
import androidx.appcompat.app.AppCompatActivity
@@ -53,6 +55,8 @@ import java.io.File
import java.util.Locale
import javax.inject.Inject
+const val OPENING_DOWNLOAD_SCREEN_DELAY = 300L
+
class CustomReaderFragment : CoreReaderFragment() {
override fun inject(baseActivity: BaseActivity) {
baseActivity.customActivityComponent.inject(this)
@@ -288,13 +292,15 @@ class CustomReaderFragment : CoreReaderFragment() {
},
onNoFilesFound = {
if (sharedPreferenceUtil?.prefIsTest == false) {
- val navOptions = NavOptions.Builder()
- .setPopUpTo(CustomDestination.Reader.route, true)
- .build()
- (requireActivity() as CoreMainActivity).navigate(
- CustomDestination.Downloads.route,
- navOptions
- )
+ Handler(Looper.getMainLooper()).postDelayed({
+ val navOptions = NavOptions.Builder()
+ .setPopUpTo(CustomDestination.Reader.route, true)
+ .build()
+ (requireActivity() as CoreMainActivity).navigate(
+ CustomDestination.Downloads.route,
+ navOptions
+ )
+ }, OPENING_DOWNLOAD_SCREEN_DELAY)
}
}
)
@@ -389,6 +395,10 @@ class CustomReaderFragment : CoreReaderFragment() {
newMainPageTab()
}
+ override fun showNoBookOpenViews() {
+ readerScreenState.update { copy(isNoBookOpenInReader = false) }
+ }
+
/**
* Overrides the method to show the donation popup. When the "Support url" is disabled
* in a custom app, this function stop to show the donationPopup.
diff --git a/custom/src/main/res/layout/fragment_custom_download.xml b/custom/src/main/res/layout/fragment_custom_download.xml
deleted file mode 100644
index 0ba9f2b85..000000000
--- a/custom/src/main/res/layout/fragment_custom_download.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/custom/src/main/res/layout/layout_custom_download_complete.xml b/custom/src/main/res/layout/layout_custom_download_complete.xml
deleted file mode 100644
index 649ec1c97..000000000
--- a/custom/src/main/res/layout/layout_custom_download_complete.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
diff --git a/custom/src/main/res/layout/layout_custom_download_error.xml b/custom/src/main/res/layout/layout_custom_download_error.xml
deleted file mode 100644
index 406562ae7..000000000
--- a/custom/src/main/res/layout/layout_custom_download_error.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/custom/src/main/res/layout/layout_custom_download_in_progress.xml b/custom/src/main/res/layout/layout_custom_download_in_progress.xml
deleted file mode 100644
index d90dc0d94..000000000
--- a/custom/src/main/res/layout/layout_custom_download_in_progress.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/custom/src/main/res/layout/layout_custom_download_required.xml b/custom/src/main/res/layout/layout_custom_download_required.xml
deleted file mode 100644
index c734ac04a..000000000
--- a/custom/src/main/res/layout/layout_custom_download_required.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-