Merge pull request #4372 from kiwix/Fixes#4371

Migrated the `CustomDownloadFragment` to Jetpack Compose.
This commit is contained in:
Kelson 2025-08-08 09:46:14 +02:00 committed by GitHub
commit e7d9f3c809
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 259 additions and 329 deletions

View File

@ -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) }
}

View File

@ -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
}

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/right_drawer_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:clipToPadding="false"
android:focusable="true" />

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 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/>.
~
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="ContentDescription"
android:src="@drawable/ic_home_kiwix_banner" />
</LinearLayout>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/section_list_height"
android:background="?android:attr/selectableItemBackground"
android:clickable="false"
android:focusable="false"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="Overdraw">
<TextView
android:id="@+id/titleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/title_text_margin"
android:layout_marginEnd="@dimen/title_text_margin"
android:ellipsize="end"
android:fontFamily="sans-serif-light"
android:gravity="center_vertical"
android:maxLines="1"
android:minHeight="?listPreferredItemHeightSmall"
android:paddingStart="@dimen/title_text_padding"
android:paddingEnd="0dp"
android:textAppearance="?textAppearanceBody1"
tools:text="Some text" />
</RelativeLayout>

View File

@ -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>(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
}
}

View File

@ -0,0 +1,203 @@
/*
* 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.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
)
}
}

View File

@ -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.

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/cd_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:contentDescription="@string/app_name"
android:src="@mipmap/ic_launcher_foreground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ViewAnimator
android:id="@+id/cd_view_animator"
android:layout_width="0dp"
android:layout_height="0dp"
android:inAnimation="@android:anim/fade_in"
android:outAnimation="@android:anim/fade_out"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cd_icon">
<include
android:id="@+id/custom_download_required"
layout="@layout/layout_custom_download_required" />
<include
android:id="@+id/custom_download_in_progress"
layout="@layout/layout_custom_download_in_progress" />
<include
android:id="@+id/custom_download_error"
layout="@layout/layout_custom_download_error" />
<include
android:id="@+id/custom_download_complete"
layout="@layout/layout_custom_download_complete" />
</ViewAnimator>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/fragment_custom_download">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/complete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/fragment_custom_download">
<TextView
android:id="@+id/cd_error_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
<Button
android:id="@+id/cd_retry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/retry"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cd_error_text" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/cd_download_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/cd_progress"
app:layout_constraintStart_toStartOf="@+id/cd_progress"
app:layout_constraintEnd_toEndOf="@+id/cd_progress" />
<ProgressBar
android:id="@+id/cd_progress"
style="?android:progressBarStyleHorizontal"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:max="100"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
<TextView
android:id="@+id/cd_eta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
app:layout_constraintBottom_toBottomOf="@id/cd_progress"
app:layout_constraintStart_toEndOf="@id/cd_progress"
app:layout_constraintTop_toTopOf="@id/cd_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/fragment_custom_download">
<TextView
android:id="@+id/cd_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/invalid_installation"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
<Button
android:id="@+id/cd_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/download"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cd_text" />
</androidx.constraintlayout.widget.ConstraintLayout>