compose: downloads: Initial migration
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -263,6 +263,7 @@ dependencies {
|
|||||||
ksp(libs.androidx.room.compiler)
|
ksp(libs.androidx.room.compiler)
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
|
implementation(libs.androidx.room.paging)
|
||||||
|
|
||||||
implementation(libs.process.phoenix)
|
implementation(libs.process.phoenix)
|
||||||
|
|
||||||
|
|||||||
@@ -72,12 +72,12 @@ fun Context.appInfo(packageName: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.share(app: App) {
|
fun Context.share(displayName: String, packageName: String) {
|
||||||
try {
|
try {
|
||||||
val sendIntent = Intent().apply {
|
val sendIntent = Intent().apply {
|
||||||
action = Intent.ACTION_SEND
|
action = Intent.ACTION_SEND
|
||||||
putExtra(Intent.EXTRA_SUBJECT, app.displayName)
|
putExtra(Intent.EXTRA_SUBJECT, displayName)
|
||||||
putExtra(Intent.EXTRA_TEXT, "${Constants.SHARE_URL}${app.packageName}")
|
putExtra(Intent.EXTRA_TEXT, "${Constants.SHARE_URL}${packageName}")
|
||||||
type = "text/plain"
|
type = "text/plain"
|
||||||
}
|
}
|
||||||
startActivity(Intent.createChooser(sendIntent, getString(R.string.action_share)))
|
startActivity(Intent.createChooser(sendIntent, getString(R.string.action_share)))
|
||||||
|
|||||||
@@ -6,35 +6,43 @@
|
|||||||
package com.aurora.store.compose.composables
|
package com.aurora.store.compose.composables
|
||||||
|
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredSize
|
import androidx.compose.foundation.layout.requiredSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SplitButtonDefaults
|
||||||
|
import androidx.compose.material3.SplitButtonLayout
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import coil3.compose.AsyncImage
|
|
||||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||||
import coil3.request.ImageRequest
|
|
||||||
import coil3.request.crossfade
|
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composables.app.AnimatedAppIconComposable
|
||||||
import com.aurora.store.compose.preview.AppPreviewProvider
|
import com.aurora.store.compose.preview.AppPreviewProvider
|
||||||
import com.aurora.store.compose.preview.coilPreviewProvider
|
import com.aurora.store.compose.preview.coilPreviewProvider
|
||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
@@ -45,34 +53,42 @@ import com.aurora.store.util.CommonUtil.getETAString
|
|||||||
* @param modifier The modifier to be applied to the composable
|
* @param modifier The modifier to be applied to the composable
|
||||||
* @param download [Download] to display
|
* @param download [Download] to display
|
||||||
* @param onClick Callback when this composable is clicked
|
* @param onClick Callback when this composable is clicked
|
||||||
* @param onLongClick Callback when this composable is long clicked
|
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadComposable(
|
fun DownloadComposable(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
download: Download,
|
download: Download,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: (() -> Unit) = {}
|
onClear: () -> Unit = {},
|
||||||
|
onCancel: () -> Unit = {},
|
||||||
|
onExport: (() -> Unit)? = null,
|
||||||
|
onInstall: (() -> Unit)? = null,
|
||||||
|
onShare: (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Row(
|
val progress = "${download.progress}%"
|
||||||
modifier = modifier
|
val speed = "${Formatter.formatShortFileSize(LocalContext.current, download.speed)}/s"
|
||||||
.fillMaxWidth()
|
val eta = getETAString(LocalContext.current, download.timeRemaining)
|
||||||
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
|
||||||
|
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClick)
|
||||||
.padding(
|
.padding(
|
||||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
vertical = dimensionResource(R.dimen.padding_small)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
AsyncImage(
|
Row(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.data(download.iconURL)
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
.crossfade(true)
|
) {
|
||||||
.build(),
|
Row {
|
||||||
contentDescription = null,
|
AnimatedAppIconComposable(
|
||||||
contentScale = ContentScale.Crop,
|
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_medium)),
|
||||||
modifier = Modifier
|
iconUrl = download.iconURL,
|
||||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
inProgress = download.isRunning,
|
||||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
progress = download.progress.toFloat()
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||||
@@ -87,32 +103,76 @@ fun DownloadComposable(
|
|||||||
text = stringResource(download.downloadStatus.localized),
|
text = stringResource(download.downloadStatus.localized),
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
progress = { download.progress.toFloat() }
|
|
||||||
)
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "${download.progress}%",
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
|
|
||||||
if (download.isRunning) {
|
if (download.isRunning) {
|
||||||
Text(
|
Text(
|
||||||
text = getETAString(LocalContext.current, download.timeRemaining),
|
text = "$progress • $speed • $eta",
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "${Formatter.formatShortFileSize(LocalContext.current, download.speed)}/s",
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SplitButtonLayout(
|
||||||
|
leadingButton = {
|
||||||
|
when {
|
||||||
|
download.isRunning -> {
|
||||||
|
SplitButtonDefaults.LeadingButton(onClick = onCancel) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
SplitButtonDefaults.LeadingButton(onClick = onClear) {
|
||||||
|
Text(text = stringResource(R.string.action_clear))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trailingButton = {
|
||||||
|
SplitButtonDefaults.TrailingButton(
|
||||||
|
checked = isExpanded,
|
||||||
|
onCheckedChange = { isExpanded = it }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = when {
|
||||||
|
isExpanded -> painterResource(R.drawable.ic_keyboard_arrow_up)
|
||||||
|
else -> painterResource(R.drawable.ic_keyboard_arrow_down)
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(R.string.expand)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isExpanded,
|
||||||
|
enter = slideInVertically() + expandVertically() + fadeIn(),
|
||||||
|
exit = slideOutVertically() + shrinkVertically() + fadeOut()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
VerticalButtonComposable(
|
||||||
|
painter = painterResource(R.drawable.ic_share),
|
||||||
|
text = stringResource(R.string.action_share),
|
||||||
|
onClick = onShare
|
||||||
|
)
|
||||||
|
|
||||||
|
VerticalButtonComposable(
|
||||||
|
painter = painterResource(R.drawable.ic_install),
|
||||||
|
text = stringResource(R.string.action_install),
|
||||||
|
onClick = onInstall
|
||||||
|
)
|
||||||
|
|
||||||
|
VerticalButtonComposable(
|
||||||
|
painter = painterResource(R.drawable.ic_file_copy),
|
||||||
|
text = stringResource(R.string.action_export),
|
||||||
|
onClick = onExport
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.composables
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.aurora.store.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable with an icon and text placed vertically to function as a button
|
||||||
|
* @param modifier Modifier
|
||||||
|
* @param painter Icon for the button
|
||||||
|
* @param text Text describing the button's action
|
||||||
|
* @param onClick Callback when this composable is clicked
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun VerticalButtonComposable(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
painter: Painter,
|
||||||
|
text: String,
|
||||||
|
onClick: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(dimensionResource(R.dimen.padding_xsmall))
|
||||||
|
.clip(RoundedCornerShape(dimensionResource(R.dimen.icon_size_default)))
|
||||||
|
.clickable(onClick = { if (onClick != null) onClick() }, enabled = onClick != null)
|
||||||
|
.padding(dimensionResource(R.dimen.padding_medium)),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = text
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun VerticalButtonComposablePreview() {
|
||||||
|
VerticalButtonComposable(
|
||||||
|
painter = painterResource(R.drawable.ic_file_copy),
|
||||||
|
text = stringResource(R.string.action_export)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ import com.aurora.gplayapi.data.models.App
|
|||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.compose.preview.AppPreviewProvider
|
import com.aurora.store.compose.preview.AppPreviewProvider
|
||||||
import com.aurora.store.compose.preview.coilPreviewProvider
|
import com.aurora.store.compose.preview.coilPreviewProvider
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable to show icon for an app that can be animated to also show install progress
|
* Composable to show icon for an app that can be animated to also show install progress
|
||||||
@@ -49,6 +50,10 @@ fun AnimatedAppIconComposable(
|
|||||||
progress: Float = 0F,
|
progress: Float = 0F,
|
||||||
inProgress: Boolean = false
|
inProgress: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
val animatedProgress by animateFloatAsState(
|
||||||
|
targetValue = progress,
|
||||||
|
animationSpec = tween(durationMillis = 300)
|
||||||
|
)
|
||||||
val animatedScale by animateFloatAsState(
|
val animatedScale by animateFloatAsState(
|
||||||
targetValue = if (inProgress) 0.75F else 1F,
|
targetValue = if (inProgress) 0.75F else 1F,
|
||||||
animationSpec = tween(durationMillis = 300)
|
animationSpec = tween(durationMillis = 300)
|
||||||
@@ -61,10 +66,10 @@ fun AnimatedAppIconComposable(
|
|||||||
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
||||||
if (inProgress) {
|
if (inProgress) {
|
||||||
val indicatorModifier = Modifier.fillMaxSize()
|
val indicatorModifier = Modifier.fillMaxSize()
|
||||||
if (progress > 0) {
|
if (animatedProgress > 0) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = indicatorModifier,
|
modifier = indicatorModifier,
|
||||||
progress = { progress / 100 }
|
progress = { animatedProgress / 100 }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
CircularProgressIndicator(modifier = indicatorModifier)
|
CircularProgressIndicator(modifier = indicatorModifier)
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.menu
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.menu.items.DownloadsMenuItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menu for the blacklist screen
|
||||||
|
* @param modifier The modifier to be applied to the composable
|
||||||
|
* @param onMenuItemClicked Callback when a menu item has been clicked
|
||||||
|
* @see DownloadsMenuItem
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun DownloadsMenu(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isExpanded: Boolean = false,
|
||||||
|
onMenuItemClicked: (menuItem: DownloadsMenuItem) -> Unit = {}
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(isExpanded) }
|
||||||
|
fun onClick(menuItem: DownloadsMenuItem) {
|
||||||
|
onMenuItemClicked(menuItem)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
IconButton(onClick = { expanded = true }) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_more_vert),
|
||||||
|
contentDescription = stringResource(R.string.menu)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_cancel_all)) },
|
||||||
|
onClick = { onClick(DownloadsMenuItem.CANCEL_ALL) }
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_clear_finished)) },
|
||||||
|
onClick = { onClick(DownloadsMenuItem.CLEAR_FINISHED) }
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_force_clear_all)) },
|
||||||
|
onClick = { onClick(DownloadsMenuItem.FORCE_CLEAR_ALL) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun DownloadsMenuPreview() {
|
||||||
|
DownloadsMenu(isExpanded = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.menu.items
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid menu items for the purpose of handling clicks
|
||||||
|
*/
|
||||||
|
enum class DownloadsMenuItem {
|
||||||
|
CANCEL_ALL,
|
||||||
|
FORCE_CLEAR_ALL,
|
||||||
|
CLEAR_FINISHED
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import com.aurora.store.compose.ui.commons.BlacklistScreen
|
|||||||
import com.aurora.store.compose.ui.details.AppDetailsScreen
|
import com.aurora.store.compose.ui.details.AppDetailsScreen
|
||||||
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
||||||
import com.aurora.store.compose.ui.commons.PermissionRationaleScreen
|
import com.aurora.store.compose.ui.commons.PermissionRationaleScreen
|
||||||
|
import com.aurora.store.compose.ui.downloads.DownloadsScreen
|
||||||
import com.aurora.store.compose.ui.search.SearchScreen
|
import com.aurora.store.compose.ui.search.SearchScreen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +77,15 @@ fun NavDisplay(startDestination: NavKey) {
|
|||||||
onNavigateUp = { onNavigateUp() },
|
onNavigateUp = { onNavigateUp() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry<Screen.Downloads> {
|
||||||
|
DownloadsScreen(
|
||||||
|
onNavigateUp = { onNavigateUp() },
|
||||||
|
onNavigateToAppDetails = { packageName ->
|
||||||
|
backstack.add(Screen.AppDetails(packageName))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,4 +38,7 @@ sealed class Screen : NavKey, Parcelable {
|
|||||||
data class PermissionRationale(
|
data class PermissionRationale(
|
||||||
val requiredPermissions: Set<PermissionType> = emptySet()
|
val requiredPermissions: Set<PermissionType> = emptySet()
|
||||||
) : Screen()
|
) : Screen()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object Downloads : Screen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ private fun ScreenContentApp(
|
|||||||
AppDetailsMenuItem.MANUAL_DOWNLOAD -> {
|
AppDetailsMenuItem.MANUAL_DOWNLOAD -> {
|
||||||
showExtraPane(ExtraScreen.ManualDownload)
|
showExtraPane(ExtraScreen.ManualDownload)
|
||||||
}
|
}
|
||||||
AppDetailsMenuItem.SHARE -> context.share(app)
|
AppDetailsMenuItem.SHARE -> context.share(app.displayName, app.packageName)
|
||||||
AppDetailsMenuItem.APP_INFO -> context.appInfo(app.packageName)
|
AppDetailsMenuItem.APP_INFO -> context.appInfo(app.packageName)
|
||||||
AppDetailsMenuItem.PLAY_STORE -> context.browse("$SHARE_URL${app.packageName}")
|
AppDetailsMenuItem.PLAY_STORE -> context.browse("$SHARE_URL${app.packageName}")
|
||||||
AppDetailsMenuItem.ADD_TO_HOME -> {
|
AppDetailsMenuItem.ADD_TO_HOME -> {
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.downloads
|
||||||
|
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.paging.LoadState
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
|
import androidx.paging.compose.itemKey
|
||||||
|
import com.aurora.extensions.share
|
||||||
|
import com.aurora.extensions.toast
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composables.DownloadComposable
|
||||||
|
import com.aurora.store.compose.composables.ErrorComposable
|
||||||
|
import com.aurora.store.compose.composables.ProgressComposable
|
||||||
|
import com.aurora.store.compose.composables.TopAppBarComposable
|
||||||
|
import com.aurora.store.compose.menu.DownloadsMenu
|
||||||
|
import com.aurora.store.compose.menu.items.DownloadsMenuItem
|
||||||
|
import com.aurora.store.data.room.download.Download
|
||||||
|
import com.aurora.store.viewmodel.downloads.DownloadsViewModel
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DownloadsScreen(
|
||||||
|
onNavigateUp: () -> Unit,
|
||||||
|
viewModel: DownloadsViewModel = hiltViewModel(),
|
||||||
|
onNavigateToAppDetails: (packageName: String) -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val downloads = viewModel.downloads.collectAsLazyPagingItems()
|
||||||
|
|
||||||
|
val exportMimeType = "application/zip"
|
||||||
|
var requestedExport by rememberSaveable { mutableStateOf<Download?>(null) }
|
||||||
|
val documentLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.CreateDocument(exportMimeType),
|
||||||
|
onResult = {
|
||||||
|
if (it != null) {
|
||||||
|
requestedExport?.let { download -> viewModel.export(download, it) }
|
||||||
|
} else {
|
||||||
|
context.toast(R.string.failed_apk_export)
|
||||||
|
}
|
||||||
|
requestedExport = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
ScreenContent(
|
||||||
|
onNavigateUp = onNavigateUp,
|
||||||
|
downloads = downloads,
|
||||||
|
onNavigateToAppDetails = onNavigateToAppDetails,
|
||||||
|
onCancelAll = { viewModel.cancelAll() },
|
||||||
|
onForceClearAll = { viewModel.clearAll() },
|
||||||
|
onClearFinished = { viewModel.clearFinished() },
|
||||||
|
onCancel = { packageName -> viewModel.cancel(packageName) },
|
||||||
|
onShare = { download -> context.share(download.displayName, download.packageName) },
|
||||||
|
onInstall = { download -> viewModel.install(download) },
|
||||||
|
onClear = { download ->
|
||||||
|
viewModel.clear(download.packageName, download.versionCode)
|
||||||
|
},
|
||||||
|
onExport = { download ->
|
||||||
|
requestedExport = download
|
||||||
|
documentLauncher.launch("${download.packageName}.zip")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent(
|
||||||
|
downloads: LazyPagingItems<Download> = flowOf(PagingData.empty<Download>()).collectAsLazyPagingItems(),
|
||||||
|
onNavigateUp: () -> Unit = {},
|
||||||
|
onNavigateToAppDetails: (packageName: String) -> Unit = {},
|
||||||
|
onCancel: (packageName: String) -> Unit = {},
|
||||||
|
onClear: (download: Download) -> Unit = {},
|
||||||
|
onExport: ((download: Download) -> Unit)? = null,
|
||||||
|
onInstall: ((download: Download) -> Unit)? = null,
|
||||||
|
onShare: ((download: Download) -> Unit)? = null,
|
||||||
|
onCancelAll: () -> Unit = {},
|
||||||
|
onForceClearAll: () -> Unit = {},
|
||||||
|
onClearFinished: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* For some reason paging3 frequently out-of-nowhere invalidates the list which causes
|
||||||
|
* the loading animation to play again even if the keys are same causing a glitching effect.
|
||||||
|
*
|
||||||
|
* Save the initial loading state to make sure we don't replay the loading animation again.
|
||||||
|
*/
|
||||||
|
var initialLoad by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SetupMenu() {
|
||||||
|
DownloadsMenu { menuItem ->
|
||||||
|
when (menuItem) {
|
||||||
|
DownloadsMenuItem.CANCEL_ALL -> onCancelAll()
|
||||||
|
DownloadsMenuItem.FORCE_CLEAR_ALL -> onForceClearAll()
|
||||||
|
DownloadsMenuItem.CLEAR_FINISHED -> onClearFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBarComposable(
|
||||||
|
title = stringResource(R.string.title_download_manager),
|
||||||
|
onNavigateUp = onNavigateUp,
|
||||||
|
actions = { if (downloads.itemCount != 0) SetupMenu() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(vertical = dimensionResource(R.dimen.padding_medium))
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
downloads.loadState.refresh is LoadState.Loading && initialLoad -> {
|
||||||
|
ProgressComposable()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
initialLoad = false
|
||||||
|
|
||||||
|
if (downloads.itemCount == 0) {
|
||||||
|
ErrorComposable(
|
||||||
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
icon = painterResource(R.drawable.ic_disclaimer),
|
||||||
|
message = stringResource(R.string.download_none)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LazyColumn {
|
||||||
|
items(
|
||||||
|
count = downloads.itemCount,
|
||||||
|
key = downloads.itemKey { it.packageName }
|
||||||
|
) { index ->
|
||||||
|
downloads[index]?.let { download ->
|
||||||
|
DownloadComposable(
|
||||||
|
download = download,
|
||||||
|
onClick = { onNavigateToAppDetails(download.packageName) },
|
||||||
|
onClear = { onClear(download) },
|
||||||
|
onShare = { onShare!!(download) },
|
||||||
|
onCancel = { onCancel(download.packageName) },
|
||||||
|
onExport = if (download.canInstall(LocalContext.current)) {
|
||||||
|
{ onExport!!(download) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
onInstall = if (download.isSuccessful) {
|
||||||
|
{ onInstall!!(download) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun DownloadsScreenPreview() {
|
||||||
|
ScreenContent()
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package com.aurora.store.data.helper
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
@@ -43,9 +45,11 @@ class DownloadHelper @Inject constructor(
|
|||||||
private const val VERSION_CODE = "VERSION_CODE"
|
private const val VERSION_CODE = "VERSION_CODE"
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadsList = downloadDao.downloads()
|
val downloadsList get() = downloadDao.downloads()
|
||||||
.stateIn(AuroraApp.scope, SharingStarted.WhileSubscribed(), emptyList())
|
.stateIn(AuroraApp.scope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
|
|
||||||
|
val pagedDownloads get() = downloadDao.pagedDownloads()
|
||||||
|
|
||||||
private val TAG = DownloadHelper::class.java.simpleName
|
private val TAG = DownloadHelper::class.java.simpleName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.aurora.store.data.room.download
|
package com.aurora.store.data.room.download
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
@@ -8,6 +10,7 @@ import com.aurora.gplayapi.data.models.PlayFile
|
|||||||
import com.aurora.store.data.model.DownloadStatus
|
import com.aurora.store.data.model.DownloadStatus
|
||||||
import com.aurora.store.data.room.suite.ExternalApk
|
import com.aurora.store.data.room.suite.ExternalApk
|
||||||
import com.aurora.store.data.room.update.Update
|
import com.aurora.store.data.room.update.Update
|
||||||
|
import com.aurora.store.util.PathUtil
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@ data class Download(
|
|||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
val isFinished get() = downloadStatus in DownloadStatus.finished
|
val isFinished get() = downloadStatus in DownloadStatus.finished
|
||||||
val isRunning get() = downloadStatus in DownloadStatus.running
|
val isRunning get() = downloadStatus in DownloadStatus.running
|
||||||
|
val isSuccessful get() = downloadStatus == DownloadStatus.COMPLETED
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromApp(app: App): Download {
|
fun fromApp(app: App): Download {
|
||||||
@@ -104,4 +108,9 @@ data class Download(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canInstall(context: Context): Boolean {
|
||||||
|
val dir = PathUtil.getAppDownloadDir(context, packageName, versionCode)
|
||||||
|
return isSuccessful && dir.listFiles() != null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.aurora.store.data.room.download
|
package com.aurora.store.data.room.download
|
||||||
|
|
||||||
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
@@ -40,6 +41,9 @@ interface DownloadDao {
|
|||||||
@Query("SELECT * FROM download")
|
@Query("SELECT * FROM download")
|
||||||
fun downloads(): Flow<List<Download>>
|
fun downloads(): Flow<List<Download>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM download ORDER BY downloadedAt DESC")
|
||||||
|
fun pagedDownloads(): PagingSource<Int, Download>
|
||||||
|
|
||||||
@Query("SELECT * FROM download WHERE packageName = :packageName")
|
@Query("SELECT * FROM download WHERE packageName = :packageName")
|
||||||
suspend fun getDownload(packageName: String): Download
|
suspend fun getDownload(packageName: String): Download
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ object NotificationUtil {
|
|||||||
val builder = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_DOWNLOADS)
|
val builder = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_DOWNLOADS)
|
||||||
builder.setSmallIcon(R.drawable.ic_notification_outlined)
|
builder.setSmallIcon(R.drawable.ic_notification_outlined)
|
||||||
builder.setContentTitle(download.displayName)
|
builder.setContentTitle(download.displayName)
|
||||||
builder.setContentIntent(getContentIntentForDownloads(context))
|
builder.setContentIntent(getContentIntentForDetails(context, download.packageName))
|
||||||
builder.setLargeIcon(largeIcon)
|
builder.setLargeIcon(largeIcon)
|
||||||
|
|
||||||
val cancelIntent = Intent(context, DownloadCancelReceiver::class.java).apply {
|
val cancelIntent = Intent(context, DownloadCancelReceiver::class.java).apply {
|
||||||
@@ -379,14 +379,6 @@ object NotificationUtil {
|
|||||||
.createPendingIntent()
|
.createPendingIntent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getContentIntentForDownloads(context: Context): PendingIntent {
|
|
||||||
return NavDeepLinkBuilder(context)
|
|
||||||
.setGraph(R.navigation.mobile_navigation)
|
|
||||||
.setDestination(R.id.downloadFragment)
|
|
||||||
.setComponentName(MainActivity::class.java)
|
|
||||||
.createPendingIntent()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getInstallIntent(context: Context, download: Download): PendingIntent? {
|
private fun getInstallIntent(context: Context, download: Download): PendingIntent? {
|
||||||
val intent = Intent(context, InstallActivity::class.java).apply {
|
val intent = Intent(context, InstallActivity::class.java).apply {
|
||||||
putExtra(Constants.PARCEL_DOWNLOAD, download)
|
putExtra(Constants.PARCEL_DOWNLOAD, download)
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* Aurora Store
|
|
||||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
*
|
|
||||||
* Aurora Store 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.view.epoxy.views
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.text.format.Formatter
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import coil3.load
|
|
||||||
import coil3.request.placeholder
|
|
||||||
import coil3.request.transformations
|
|
||||||
import coil3.transform.RoundedCornersTransformation
|
|
||||||
import com.airbnb.epoxy.CallbackProp
|
|
||||||
import com.airbnb.epoxy.ModelProp
|
|
||||||
import com.airbnb.epoxy.ModelView
|
|
||||||
import com.aurora.store.R
|
|
||||||
import com.aurora.store.data.model.DownloadStatus
|
|
||||||
import com.aurora.store.data.room.download.Download
|
|
||||||
import com.aurora.store.databinding.ViewDownloadBinding
|
|
||||||
import com.aurora.store.util.CommonUtil.getETAString
|
|
||||||
|
|
||||||
@ModelView(
|
|
||||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
|
||||||
baseModelClass = BaseModel::class
|
|
||||||
)
|
|
||||||
class DownloadView @JvmOverloads constructor(
|
|
||||||
context: Context?,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0
|
|
||||||
) : BaseView<ViewDownloadBinding>(context, attrs, defStyleAttr) {
|
|
||||||
|
|
||||||
@ModelProp
|
|
||||||
fun download(download: Download) {
|
|
||||||
binding.imgDownload.load(download.iconURL) {
|
|
||||||
placeholder(R.drawable.bg_placeholder)
|
|
||||||
transformations(RoundedCornersTransformation(32F))
|
|
||||||
}
|
|
||||||
binding.txtTitle.text = download.displayName
|
|
||||||
binding.txtStatus.text = context.getString(download.downloadStatus.localized)
|
|
||||||
|
|
||||||
binding.progressDownload.apply {
|
|
||||||
progress = download.progress
|
|
||||||
isIndeterminate = download.progress <= 0 && !download.isFinished
|
|
||||||
}
|
|
||||||
binding.txtProgress.text = ("${download.progress}%")
|
|
||||||
|
|
||||||
binding.txtEta.text = getETAString(context, download.timeRemaining)
|
|
||||||
binding.txtSpeed.text = "${Formatter.formatShortFileSize(context, download.speed)}/s"
|
|
||||||
|
|
||||||
when (download.downloadStatus) {
|
|
||||||
DownloadStatus.DOWNLOADING, DownloadStatus.QUEUED -> {
|
|
||||||
binding.txtSpeed.visibility = VISIBLE
|
|
||||||
binding.txtEta.visibility = VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
binding.txtSpeed.visibility = INVISIBLE
|
|
||||||
binding.txtEta.visibility = INVISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallbackProp
|
|
||||||
fun click(onClickListener: OnClickListener?) {
|
|
||||||
binding.root.setOnClickListener(onClickListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallbackProp
|
|
||||||
fun longClick(onClickListener: OnLongClickListener?) {
|
|
||||||
binding.root.setOnLongClickListener(onClickListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,7 @@ class AppsContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
|
|||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.menu_download_manager -> {
|
R.id.menu_download_manager -> {
|
||||||
findNavController().navigate(R.id.downloadFragment)
|
requireContext().navigate(Screen.Downloads)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_more -> {
|
R.id.menu_more -> {
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* Aurora Store
|
|
||||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
*
|
|
||||||
* Aurora Store 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.view.ui.downloads
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import android.view.View
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.aurora.Constants.GITLAB_URL
|
|
||||||
import com.aurora.extensions.browse
|
|
||||||
import com.aurora.store.R
|
|
||||||
import com.aurora.store.data.room.download.Download
|
|
||||||
import com.aurora.store.databinding.FragmentDownloadBinding
|
|
||||||
import com.aurora.store.view.epoxy.views.DownloadViewModel_
|
|
||||||
import com.aurora.store.view.epoxy.views.TextDividerViewModel_
|
|
||||||
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
|
||||||
import com.aurora.store.view.ui.commons.BaseFragment
|
|
||||||
import com.aurora.store.viewmodel.downloads.DownloadViewModel
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.NonCancellable
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class DownloadFragment : BaseFragment<FragmentDownloadBinding>() {
|
|
||||||
|
|
||||||
private val viewModel: DownloadViewModel by viewModels()
|
|
||||||
|
|
||||||
private lateinit var downloadList: List<Download>
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
// Toolbar
|
|
||||||
binding.toolbar.apply {
|
|
||||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
|
||||||
setOnMenuItemClickListener {
|
|
||||||
when (it.itemId) {
|
|
||||||
R.id.action_force_clear_all -> {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch(NonCancellable) {
|
|
||||||
viewModel.downloadHelper.clearAllDownloads()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_cancel_all -> {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch(NonCancellable) {
|
|
||||||
viewModel.downloadHelper.cancelAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_clear_completed -> {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch(NonCancellable) {
|
|
||||||
viewModel.downloadHelper.clearFinishedDownloads()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
viewModel.downloadHelper.downloadsList.collectLatest {
|
|
||||||
downloadList = it
|
|
||||||
updateController(it.reversed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateController(downloads: List<Download>) {
|
|
||||||
binding.recycler.withModels {
|
|
||||||
if (downloads.isEmpty()) {
|
|
||||||
add(
|
|
||||||
NoAppViewModel_()
|
|
||||||
.id("no_downloads")
|
|
||||||
.message(R.string.download_none)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
downloads.groupBy {
|
|
||||||
DateUtils.getRelativeTimeSpanString(
|
|
||||||
it.downloadedAt,
|
|
||||||
Date().time,
|
|
||||||
DateUtils.DAY_IN_MILLIS
|
|
||||||
).toString()
|
|
||||||
}.forEach { (date, downloadList) ->
|
|
||||||
add(
|
|
||||||
TextDividerViewModel_()
|
|
||||||
.id(date)
|
|
||||||
.title(date)
|
|
||||||
)
|
|
||||||
|
|
||||||
downloadList.forEach {
|
|
||||||
add(
|
|
||||||
DownloadViewModel_()
|
|
||||||
.id(it.packageName)
|
|
||||||
.download(it)
|
|
||||||
.click { _ ->
|
|
||||||
if (it.packageName == requireContext().packageName) {
|
|
||||||
requireContext().browse(GITLAB_URL)
|
|
||||||
} else {
|
|
||||||
openDetailsFragment(it.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.longClick { _ ->
|
|
||||||
openDownloadMenuSheet(it.packageName)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openDownloadMenuSheet(packageName: String) {
|
|
||||||
val download = downloadList.find { it.packageName == packageName }!!
|
|
||||||
findNavController().navigate(
|
|
||||||
DownloadFragmentDirections.actionDownloadFragmentToDownloadMenuSheet(download)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,7 @@ class GamesContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
|
|||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.menu_download_manager -> {
|
R.id.menu_download_manager -> {
|
||||||
findNavController().navigate(R.id.downloadFragment)
|
requireContext().navigate(Screen.Downloads)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_more -> {
|
R.id.menu_more -> {
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
/*
|
|
||||||
* Aurora Store
|
|
||||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
*
|
|
||||||
* Aurora Store 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.view.ui.sheets
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.navigation.fragment.navArgs
|
|
||||||
import com.aurora.extensions.copyToClipBoard
|
|
||||||
import com.aurora.extensions.toast
|
|
||||||
import com.aurora.store.R
|
|
||||||
import com.aurora.store.data.model.DownloadStatus
|
|
||||||
import com.aurora.store.databinding.SheetDownloadMenuBinding
|
|
||||||
import com.aurora.store.util.PathUtil
|
|
||||||
import com.aurora.store.viewmodel.sheets.DownloadMenuViewModel
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.NonCancellable
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class DownloadMenuSheet : BaseDialogSheet<SheetDownloadMenuBinding>() {
|
|
||||||
|
|
||||||
private val TAG = DownloadMenuSheet::class.java.simpleName
|
|
||||||
|
|
||||||
private val viewModel: DownloadMenuViewModel by viewModels()
|
|
||||||
private val args: DownloadMenuSheetArgs by navArgs()
|
|
||||||
private val playStoreURL = "https://play.google.com/store/apps/details?id="
|
|
||||||
|
|
||||||
private val exportMimeType = "application/zip"
|
|
||||||
|
|
||||||
private val requestDocumentCreation =
|
|
||||||
registerForActivityResult(ActivityResultContracts.CreateDocument(exportMimeType)) {
|
|
||||||
if (it != null) {
|
|
||||||
viewModel.copyDownloadedApp(requireContext(), args.download, it)
|
|
||||||
} else {
|
|
||||||
toast(R.string.failed_apk_export)
|
|
||||||
}
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
with(binding.navigationView) {
|
|
||||||
val downloadCompleted = args.download.downloadStatus == DownloadStatus.COMPLETED
|
|
||||||
val downloadDir = PathUtil.getAppDownloadDir(
|
|
||||||
requireContext(),
|
|
||||||
args.download.packageName,
|
|
||||||
args.download.versionCode
|
|
||||||
)
|
|
||||||
|
|
||||||
menu.findItem(R.id.action_cancel).isVisible = !args.download.isFinished
|
|
||||||
menu.findItem(R.id.action_clear).isVisible = args.download.isFinished
|
|
||||||
menu.findItem(R.id.action_install).isVisible = downloadCompleted
|
|
||||||
menu.findItem(R.id.action_local).isVisible =
|
|
||||||
downloadCompleted && downloadDir.listFiles() != null
|
|
||||||
|
|
||||||
setNavigationItemSelectedListener { item ->
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_install -> {
|
|
||||||
install()
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_copy -> {
|
|
||||||
requireContext().copyToClipBoard(
|
|
||||||
"${playStoreURL}${args.download.packageName}"
|
|
||||||
)
|
|
||||||
requireContext().toast(requireContext().getString(R.string.toast_clipboard_copied))
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_cancel -> {
|
|
||||||
findViewTreeLifecycleOwner()?.lifecycleScope?.launch(NonCancellable) {
|
|
||||||
viewModel.downloadHelper.cancelDownload(args.download.packageName)
|
|
||||||
}
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_clear -> {
|
|
||||||
findViewTreeLifecycleOwner()?.lifecycleScope?.launch(NonCancellable) {
|
|
||||||
viewModel.downloadHelper.clearDownload(
|
|
||||||
args.download.packageName,
|
|
||||||
args.download.versionCode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_local -> {
|
|
||||||
requestDocumentCreation.launch("${args.download.packageName}.zip")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun install() {
|
|
||||||
try {
|
|
||||||
viewModel.appInstaller.getPreferredInstaller().install(args.download)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.e(TAG, "Failed to install ${args.download.packageName}", exception)
|
|
||||||
if (exception is NullPointerException) {
|
|
||||||
requireContext().toast(R.string.installer_status_failure_invalid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -73,7 +73,7 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
binding.toolbar.setOnMenuItemClickListener {
|
binding.toolbar.setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.menu_download_manager -> {
|
R.id.menu_download_manager -> {
|
||||||
findNavController().navigate(R.id.downloadFragment)
|
requireContext().navigate(Screen.Downloads)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_more -> {
|
R.id.menu_more -> {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.viewmodel.downloads
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import com.aurora.store.data.helper.DownloadHelper
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class DownloadViewModel @Inject constructor(val downloadHelper: DownloadHelper) : ViewModel()
|
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.viewmodel.downloads
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.cachedIn
|
||||||
|
import com.aurora.store.data.helper.DownloadHelper
|
||||||
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
|
import com.aurora.store.data.room.download.Download
|
||||||
|
import com.aurora.store.data.work.ExportWorker
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class DownloadsViewModel @Inject constructor(
|
||||||
|
private val downloadHelper: DownloadHelper,
|
||||||
|
private val appInstaller: AppInstaller,
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val TAG = DownloadsViewModel::class.java.simpleName
|
||||||
|
|
||||||
|
private val _downloads = MutableStateFlow<PagingData<Download>>(PagingData.empty())
|
||||||
|
val downloads = _downloads.asStateFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
getPagedDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(packageName: String) {
|
||||||
|
viewModelScope.launch(NonCancellable) {
|
||||||
|
downloadHelper.cancelDownload(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelAll() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
downloadHelper.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear(packageName: String, versionCode: Long) {
|
||||||
|
viewModelScope.launch(NonCancellable) {
|
||||||
|
downloadHelper.clearDownload(packageName, versionCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearFinished() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
downloadHelper.clearFinishedDownloads()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAll() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
downloadHelper.clearAllDownloads()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun install(download: Download) {
|
||||||
|
try {
|
||||||
|
appInstaller.getPreferredInstaller().install(download)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.e(TAG, "Failed to install ${download.packageName}", exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun export(download: Download, uri: Uri) {
|
||||||
|
ExportWorker.exportDownloadedApp(context, download, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPagedDownloads() {
|
||||||
|
Pager(
|
||||||
|
config = PagingConfig(
|
||||||
|
pageSize = 20,
|
||||||
|
enablePlaceholders = true
|
||||||
|
),
|
||||||
|
pagingSourceFactory = { downloadHelper.pagedDownloads }
|
||||||
|
).flow.distinctUntilChanged()
|
||||||
|
.cachedIn(viewModelScope)
|
||||||
|
.onEach { _downloads.value = it }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.viewmodel.sheets
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import com.aurora.store.data.helper.DownloadHelper
|
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
|
||||||
import com.aurora.store.data.room.download.Download
|
|
||||||
import com.aurora.store.data.work.ExportWorker
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class DownloadMenuViewModel @Inject constructor(
|
|
||||||
val downloadHelper: DownloadHelper,
|
|
||||||
val appInstaller: AppInstaller
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
fun copyDownloadedApp(context: Context, download: Download, uri: Uri) {
|
|
||||||
ExportWorker.exportDownloadedApp(context, download, uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
app/src/main/res/drawable/ic_keyboard_arrow_down.xml
Normal file
14
app/src/main/res/drawable/ic_keyboard_arrow_down.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
~ SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>
|
||||||
|
</vector>
|
||||||
14
app/src/main/res/drawable/ic_keyboard_arrow_up.xml
Normal file
14
app/src/main/res/drawable/ic_keyboard_arrow_up.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
~ SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M480,432L296,616L240,560L480,320L720,560L664,616L480,432Z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<LinearLayout 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"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".view.ui.downloads.DownloadFragment">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minHeight="?attr/actionBarSize"
|
|
||||||
app:menu="@menu/menu_download_main"
|
|
||||||
app:navigationIcon="@drawable/ic_arrow_back"
|
|
||||||
app:title="@string/title_download_manager" />
|
|
||||||
|
|
||||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
|
||||||
android:id="@+id/recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="true"
|
|
||||||
android:paddingBottom="@dimen/height_bottom_adj"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
app:stackFromEnd="false"
|
|
||||||
tools:listitem="@layout/view_download" />
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<FrameLayout 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="wrap_content"
|
|
||||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView
|
|
||||||
android:id="@+id/navigation_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:padding="@dimen/padding_small"
|
|
||||||
app:elevation="0dp"
|
|
||||||
app:menu="@menu/menu_download_single" />
|
|
||||||
</FrameLayout>
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<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="wrap_content"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:paddingStart="@dimen/padding_medium"
|
|
||||||
android:paddingTop="@dimen/padding_xsmall"
|
|
||||||
android:paddingEnd="@dimen/padding_small"
|
|
||||||
android:paddingBottom="@dimen/padding_xsmall">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/img_download"
|
|
||||||
android:layout_width="@dimen/icon_size_medium"
|
|
||||||
android:layout_height="@dimen/icon_size_medium"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:src="@drawable/bg_placeholder" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/txt_title"
|
|
||||||
style="@style/AuroraTextStyle.Line1"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/margin_small"
|
|
||||||
android:layout_toEndOf="@id/img_download"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
tools:text="App Name" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/txt_status"
|
|
||||||
style="@style/AuroraTextStyle.Line2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/txt_title"
|
|
||||||
android:layout_alignStart="@id/txt_title"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
tools:text="Status" />
|
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
|
||||||
android:id="@+id/progress_download"
|
|
||||||
style="@style/Widget.Material3.LinearProgressIndicator.Legacy"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_below="@id/txt_status"
|
|
||||||
android:layout_alignStart="@id/txt_title"
|
|
||||||
android:indeterminate="false"
|
|
||||||
android:max="100"
|
|
||||||
android:paddingTop="@dimen/padding_xxsmall"
|
|
||||||
android:paddingBottom="@dimen/padding_xxsmall"
|
|
||||||
tools:progress="75" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/progress_download"
|
|
||||||
android:layout_alignStart="@id/txt_title"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:weightSum="3">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/txt_progress"
|
|
||||||
style="@style/AuroraTextStyle.Line2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
tools:text="75%" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/txt_eta"
|
|
||||||
style="@style/AuroraTextStyle.Line2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAlignment="center"
|
|
||||||
tools:text="15 sec" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/txt_speed"
|
|
||||||
style="@style/AuroraTextStyle.Line2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAlignment="viewEnd"
|
|
||||||
tools:text="1.5Mb/s" />
|
|
||||||
</LinearLayout>
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_cancel_all"
|
|
||||||
android:title="@string/download_cancel_all" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_clear_completed"
|
|
||||||
android:title="@string/download_clear_finished" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_force_clear_all"
|
|
||||||
android:title="@string/download_force_clear_all" />
|
|
||||||
</menu>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_local"
|
|
||||||
android:title="@string/action_get_local_apk" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_install"
|
|
||||||
android:title="@string/action_install" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_copy"
|
|
||||||
android:title="@string/action_copy_link" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_cancel"
|
|
||||||
android:title="@string/action_cancel" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_clear"
|
|
||||||
android:title="@string/action_clear" />
|
|
||||||
</menu>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_clear"
|
|
||||||
android:icon="@drawable/ic_cancel"
|
|
||||||
android:title="@string/action_clear"
|
|
||||||
android:visible="false"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_download"
|
|
||||||
android:icon="@drawable/ic_download_manager"
|
|
||||||
android:title="@string/title_download_manager"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
</menu>
|
|
||||||
@@ -110,15 +110,6 @@
|
|||||||
android:name="com.aurora.store.view.ui.preferences.updates.UpdatesPreference"
|
android:name="com.aurora.store.view.ui.preferences.updates.UpdatesPreference"
|
||||||
android:label="UpdatesPreference"
|
android:label="UpdatesPreference"
|
||||||
tools:layout="@layout/fragment_setting" />
|
tools:layout="@layout/fragment_setting" />
|
||||||
<fragment
|
|
||||||
android:id="@+id/downloadFragment"
|
|
||||||
android:name="com.aurora.store.view.ui.downloads.DownloadFragment"
|
|
||||||
android:label="@string/title_download_manager"
|
|
||||||
tools:layout="@layout/fragment_download">
|
|
||||||
<action
|
|
||||||
android:id="@+id/action_downloadFragment_to_downloadMenuSheet"
|
|
||||||
app:destination="@id/downloadMenuSheet" />
|
|
||||||
</fragment>
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/categoryBrowseFragment"
|
android:id="@+id/categoryBrowseFragment"
|
||||||
android:name="com.aurora.store.view.ui.commons.CategoryBrowseFragment"
|
android:name="com.aurora.store.view.ui.commons.CategoryBrowseFragment"
|
||||||
@@ -272,20 +263,6 @@
|
|||||||
android:name="app"
|
android:name="app"
|
||||||
app:argType="com.aurora.store.data.model.MinimalApp" />
|
app:argType="com.aurora.store.data.model.MinimalApp" />
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog
|
|
||||||
android:id="@+id/filterSheet"
|
|
||||||
android:name="com.aurora.store.view.ui.sheets.FilterSheet"
|
|
||||||
android:label="FilterSheet"
|
|
||||||
tools:layout="@layout/sheet_filter" />
|
|
||||||
<dialog
|
|
||||||
android:id="@+id/downloadMenuSheet"
|
|
||||||
android:name="com.aurora.store.view.ui.sheets.DownloadMenuSheet"
|
|
||||||
android:label="DownloadMenuSheet"
|
|
||||||
tools:layout="@layout/sheet_download_menu">
|
|
||||||
<argument
|
|
||||||
android:name="download"
|
|
||||||
app:argType="com.aurora.store.data.room.download.Download" />
|
|
||||||
</dialog>
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/deviceMiuiSheet"
|
android:id="@+id/deviceMiuiSheet"
|
||||||
android:name="com.aurora.store.view.ui.sheets.DeviceMiuiSheet"
|
android:name="com.aurora.store.view.ui.sheets.DeviceMiuiSheet"
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ androidx-preference-ktx = { module = "androidx.preference:preference-ktx", versi
|
|||||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||||
|
androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "room" }
|
||||||
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
|
|||||||
Reference in New Issue
Block a user