compose: Replicate epoxy views with composables
Jetpack compose allows creating custom views easily without third-party libraries. Replicate all epoxy custom views with compose to be able to replace it easily in future when and where ever required. Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.Checkbox
|
||||
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.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable for displaying package details in a list for blacklisting
|
||||
* @param icon Icon for the package
|
||||
* @param displayName User-readable name of the package
|
||||
* @param packageName Name of the package
|
||||
* @param versionName versionName of the package
|
||||
* @param versionCode versionCode of the package
|
||||
* @param isChecked Whether the app is blacklisted
|
||||
* @param onClick Callback when the composable is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun BlackListComposable(
|
||||
icon: Bitmap,
|
||||
displayName: String,
|
||||
packageName: String,
|
||||
versionName: String,
|
||||
versionCode: Long,
|
||||
isChecked: Boolean = false,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(modifier = Modifier.weight(1F)) {
|
||||
Image(
|
||||
bitmap = icon.asImageBitmap(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||
) {
|
||||
Text(
|
||||
text = displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = packageName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.version, versionName, versionCode),
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Checkbox(checked = isChecked, onCheckedChange = { onClick() })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BlackListComposablePreview() {
|
||||
BlackListComposable(
|
||||
icon = ColorDrawable(Color.TRANSPARENT).toBitmap(56, 56),
|
||||
displayName = LocalContext.current.getString(R.string.app_name),
|
||||
packageName = BuildConfig.APPLICATION_ID,
|
||||
versionName = BuildConfig.VERSION_NAME,
|
||||
versionCode = BuildConfig.VERSION_CODE.toLong(),
|
||||
isChecked = true
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.gplayapi.data.models.Category
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to show a category in a list
|
||||
* @param category [Category] details to display
|
||||
* @param onClick Callback when this composable is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun CategoryComposable(category: Category, onClick: () -> Unit = {}) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(category.imageUrl)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.ic_android),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_default))
|
||||
)
|
||||
Text(
|
||||
text = category.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun CategoryComposablePreview() {
|
||||
CategoryComposable(category = Category(title = "Art & Design"))
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Checkbox
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to display device details for spoofing in a list
|
||||
* @param userReadableName Name of the device, obtained through `UserReadableName` property
|
||||
* @param manufacturer Name of the device manufacturer, obtained through `Build.MANUFACTURER` property
|
||||
* @param androidVersionSdk Android version on the device, obtained through `Build.VERSION.SDK_INT` property
|
||||
* @param platforms Platforms supported on the device, obtained through `Platforms` property
|
||||
* @param isChecked If the device is selected
|
||||
* @param onClick Callback when the composable is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun DeviceComposable(
|
||||
userReadableName: String,
|
||||
manufacturer: String,
|
||||
androidVersionSdk: String,
|
||||
platforms: String,
|
||||
isChecked: Boolean = false,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1F)) {
|
||||
Text(
|
||||
text = userReadableName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = LocalContext.current.getString(
|
||||
R.string.spoof_property,
|
||||
manufacturer,
|
||||
androidVersionSdk
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = platforms,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
Checkbox(checked = isChecked, enabled = !isChecked, onCheckedChange = { onClick() })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DeviceComposablePreview() {
|
||||
DeviceComposable(
|
||||
userReadableName = "Google Pixel 7a",
|
||||
manufacturer = "Google",
|
||||
androidVersionSdk = "33",
|
||||
platforms = "arm64-v8a",
|
||||
isChecked = true
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to display dispenser URL in a list
|
||||
* @param url URL of the dispenser
|
||||
* @param onClick Callback when this URL is clicked
|
||||
* @param onClear Callback when the clear button is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun DispenserComposable(url: String, onClick: () -> Unit = {}, onClear: () -> Unit = {}) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1F),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small))
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_server),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = url,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
TextButton(onClick = onClear) {
|
||||
Text(
|
||||
text = stringResource(R.string.remove),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DispenserComposablePreview() {
|
||||
DispenserComposable(url = "https://auroraoss.com/api/auth")
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
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.layout.ContentScale
|
||||
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.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.util.CommonUtil.getDownloadSpeedString
|
||||
import com.aurora.store.util.CommonUtil.getETAString
|
||||
|
||||
/**
|
||||
* Composable to display details of a download in a list
|
||||
* @param download [Download] to display
|
||||
* @param onClick Callback when this composable is clicked
|
||||
* @param onLongClick Callback when this composable is long clicked
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun DownloadComposable(
|
||||
download: Download,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: (() -> Unit) = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(download.iconURL)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.ic_android),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||
) {
|
||||
Text(
|
||||
text = download.displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = stringResource(download.downloadStatus.localized),
|
||||
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) {
|
||||
Text(
|
||||
text = getETAString(LocalContext.current, download.timeRemaining),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Text(
|
||||
text = getDownloadSpeedString(LocalContext.current, download.speed),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DownloadComposablePreview() {
|
||||
val app = App(
|
||||
packageName = BuildConfig.APPLICATION_ID,
|
||||
displayName = LocalContext.current.getString(R.string.app_name)
|
||||
)
|
||||
DownloadComposable(download = Download.fromApp(app))
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.layout.ContentScale
|
||||
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.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
|
||||
/**
|
||||
* Composable to display a favourite app in a list
|
||||
* @param favourite A [Favourite] app to display
|
||||
* @param onClick Callback when this composable is clicked
|
||||
* @param onClear Callback when the favourite button is clicked to remove the app from favourites
|
||||
*/
|
||||
@Composable
|
||||
fun FavouriteComposable(
|
||||
favourite: Favourite,
|
||||
onClick: () -> Unit = {},
|
||||
onClear: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(modifier = Modifier.weight(1F)) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(favourite.iconURL)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.ic_android),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small))
|
||||
) {
|
||||
Text(
|
||||
text = favourite.displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = favourite.packageName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = DateUtils.formatDateTime(
|
||||
LocalContext.current,
|
||||
favourite.added,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onClear) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_favorite_checked),
|
||||
contentDescription = stringResource(R.string.action_favourite)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun FavouriteComposablePreview() {
|
||||
val app = App(
|
||||
packageName = BuildConfig.APPLICATION_ID,
|
||||
displayName = LocalContext.current.getString(R.string.app_name)
|
||||
)
|
||||
FavouriteComposable(favourite = Favourite.fromApp(app, Favourite.Mode.MANUAL))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to display sticky header in list
|
||||
* @param title Title to display
|
||||
* @param onClick Callback when this composable is clicked
|
||||
* @see TextDividerComposable
|
||||
* @see UpdateHeaderComposable
|
||||
*/
|
||||
@Composable
|
||||
fun HeaderComposable(@StringRes title: Int, onClick: () -> Unit = {}) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_small),
|
||||
vertical = dimensionResource(R.dimen.padding_xxsmall)
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(title),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1F)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_arrow_right),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun HeaderComposablePreview() {
|
||||
HeaderComposable(title = R.string.app_name)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable for displaying installed package details in a list
|
||||
* @param icon Icon for the package
|
||||
* @param displayName User-readable name of the package
|
||||
* @param packageName Name of the package
|
||||
* @param versionName versionName of the package
|
||||
* @param versionCode versionCode of the package
|
||||
* @param onClick Callback when the composable is clicked
|
||||
* @param onLongClick Callback whe composable is long clicked
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun InstalledAppComposable(
|
||||
icon: Bitmap,
|
||||
displayName: String,
|
||||
packageName: String,
|
||||
versionName: String,
|
||||
versionCode: Long,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
)
|
||||
) {
|
||||
Row {
|
||||
Image(
|
||||
bitmap = icon.asImageBitmap(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||
) {
|
||||
Text(
|
||||
text = displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = packageName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.version, versionName, versionCode),
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun InstalledAppComposablePreview() {
|
||||
InstalledAppComposable(
|
||||
icon = ColorDrawable(Color.TRANSPARENT).toBitmap(56, 56),
|
||||
displayName = LocalContext.current.getString(R.string.app_name),
|
||||
packageName = BuildConfig.APPLICATION_ID,
|
||||
versionName = BuildConfig.VERSION_NAME,
|
||||
versionCode = BuildConfig.VERSION_CODE.toLong()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.installer.SessionInstaller
|
||||
import com.aurora.store.data.model.InstallerInfo
|
||||
|
||||
/**
|
||||
* Composable to display installer details in a list
|
||||
* @param installerInfo A [InstallerInfo] object to display details
|
||||
* @param isSelected Whether this installer is selected
|
||||
* @param onClick Callback when this composable is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun InstallerComposable(
|
||||
installerInfo: InstallerInfo,
|
||||
isSelected: Boolean = false,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1F)) {
|
||||
Text(
|
||||
text = stringResource(installerInfo.title),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = stringResource(installerInfo.subtitle),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = stringResource(installerInfo.description),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
RadioButton(selected = isSelected, onClick = onClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun InstallerComposablePreview() {
|
||||
InstallerComposable(installerInfo = SessionInstaller.installerInfo, isSelected = true)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
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.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.Link
|
||||
|
||||
/**
|
||||
* Composable to show link details in a list
|
||||
* @param link [Link] to show details
|
||||
* @param onClick Callback when the composable is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun LinkComposable(link: Link, onClick: () -> Unit = {}) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(link.icon),
|
||||
contentDescription = null
|
||||
)
|
||||
VerticalDivider(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = dimensionResource(R.dimen.margin_large))
|
||||
.width(dimensionResource(R.dimen.padding_xxsmall))
|
||||
.height(dimensionResource(R.dimen.height_header))
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = link.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = link.subtitle,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LinkComposablePreview() {
|
||||
LinkComposable(
|
||||
link = Link(
|
||||
id = 0,
|
||||
title = LocalContext.current.getString(R.string.title_about),
|
||||
subtitle = LocalContext.current.getString(R.string.about_aurora_store_subtitle),
|
||||
url = "https://auroraoss.com/",
|
||||
icon = R.drawable.ic_menu_about
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Composable to display locale details in a list
|
||||
* @param displayName Display name of the locale
|
||||
* @param displayLanguage Display name of the language in the locale
|
||||
* @param isChecked Whether the locale is checked/selected
|
||||
* @param onClick Callback when the composable is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun LocaleComposable(
|
||||
displayName: String,
|
||||
displayLanguage: String,
|
||||
isChecked: Boolean = false,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1F)) {
|
||||
Text(
|
||||
text = displayName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = displayLanguage,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Checkbox(checked = isChecked, enabled = !isChecked, onCheckedChange = { onClick() })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LocaleComposablePreview() {
|
||||
LocaleComposable(
|
||||
displayName = Locale.JAPANESE.displayName,
|
||||
displayLanguage = Locale.JAPAN.getDisplayLanguage(Locale.JAPAN),
|
||||
isChecked = true
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.Permission
|
||||
import com.aurora.store.data.model.PermissionType
|
||||
|
||||
/**
|
||||
* Composable to display permission details in a list
|
||||
* @param permission [Permission] to display
|
||||
* @param isGranted If the permission has been granted
|
||||
* @param onAction Callback when the user clicks the action button
|
||||
*/
|
||||
@Composable
|
||||
fun PermissionComposable(
|
||||
permission: Permission,
|
||||
isGranted: Boolean = false,
|
||||
onAction: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1F)) {
|
||||
Text(
|
||||
text = permission.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = permission.subtitle,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
TextButton(onClick = onAction, enabled = !isGranted) {
|
||||
Text(
|
||||
text = if (isGranted) {
|
||||
stringResource(R.string.action_granted)
|
||||
} else {
|
||||
stringResource(R.string.action_grant)
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PermissionComposablePreview() {
|
||||
PermissionComposable(
|
||||
permission = Permission(
|
||||
PermissionType.STORAGE_MANAGER,
|
||||
LocalContext.current.getString(R.string.onboarding_permission_esm),
|
||||
LocalContext.current.getString(R.string.onboarding_permission_esa_desc),
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.gplayapi.SearchSuggestEntry
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable for displaying search suggestions in a list
|
||||
* @param searchSuggestEntry A [SearchSuggestEntry] to display search suggestion
|
||||
* @param onClick Callback when this composable is clicked
|
||||
* @param onAction Callback when action button is clicked
|
||||
*/
|
||||
@Composable
|
||||
fun SearchSuggestionComposable(
|
||||
searchSuggestEntry: SearchSuggestEntry,
|
||||
onClick: () -> Unit = {},
|
||||
onAction: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_medium)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.weight(1F)
|
||||
.clickable { onClick() },
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(
|
||||
if (searchSuggestEntry.hasImageContainer()) {
|
||||
searchSuggestEntry.imageContainer.imageUrl
|
||||
} else {
|
||||
R.drawable.ic_search_suggestion
|
||||
}
|
||||
)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.ic_search_suggestion),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_default))
|
||||
)
|
||||
Text(
|
||||
text = searchSuggestEntry.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onAction) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_search_append),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SearchSuggestionComposablePreview() {
|
||||
SearchSuggestionComposable(
|
||||
searchSuggestEntry = SearchSuggestEntry.getDefaultInstance()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to display a sticky header in a list
|
||||
* @param title Title to display
|
||||
* @see HeaderComposable
|
||||
* @see UpdateHeaderComposable
|
||||
*/
|
||||
@Composable
|
||||
fun TextDividerComposable(@StringRes title: Int) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dimensionResource(R.dimen.padding_small))
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(title).uppercase(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TextDividerComposablePreview() {
|
||||
TextDividerComposable(title = R.string.item_optional)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to display sticky header in list
|
||||
* @param title Title to display
|
||||
* @param actionTitle Title for the action button
|
||||
* @param onAction Callback when action button is clicked
|
||||
* @see TextDividerComposable
|
||||
* @see HeaderComposable
|
||||
*/
|
||||
@Composable
|
||||
fun UpdateHeaderComposable(
|
||||
@StringRes title: Int,
|
||||
@StringRes actionTitle: Int,
|
||||
onAction: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(title),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Button(onClick = onAction) {
|
||||
Text(
|
||||
text = stringResource(actionTitle),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun UpdateHeaderComposablePreview() {
|
||||
UpdateHeaderComposable(
|
||||
title = R.string.updates_available,
|
||||
actionTitle = R.string.action_update_all
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables.app
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.util.CommonUtil.addSiPrefix
|
||||
|
||||
/**
|
||||
* Composable for displaying minimal app details in a horizontal-scrollable list
|
||||
* @param app [App] to display
|
||||
* @param onClick Callback when the composable is clicked
|
||||
* @see AppListComposable
|
||||
*/
|
||||
@Composable
|
||||
fun AppComposable(app: App, onClick: () -> Unit = {}) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(dimensionResource(R.dimen.icon_size_cluster))
|
||||
.clickable { onClick() }
|
||||
.padding(dimensionResource(R.dimen.padding_xsmall))
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(app.iconArtwork.url)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.ic_android),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_cluster))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
)
|
||||
Text(
|
||||
text = app.displayName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = if (app.size > 0) addSiPrefix(app.size) else app.downloadString,
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AppComposablePreview() {
|
||||
AppComposable(
|
||||
app = App(
|
||||
packageName = BuildConfig.APPLICATION_ID,
|
||||
displayName = LocalContext.current.getString(R.string.app_name),
|
||||
size = 7431013
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables.app
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
|
||||
/**
|
||||
* Composable for displaying minimal app details in a vertical-scrollable list
|
||||
* @param app [App] to display
|
||||
* @param onClick Callback when the composable is clicked
|
||||
* @see AppComposable
|
||||
*/
|
||||
@Composable
|
||||
fun AppListComposable(app: App, onClick: () -> Unit = {}) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_small)
|
||||
)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(app.iconArtwork.url)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.ic_android),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||
) {
|
||||
Text(
|
||||
text = app.displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(text = app.developerName, style = MaterialTheme.typography.bodySmall)
|
||||
Text(
|
||||
text = buildExtras(app).joinToString(separator = " • "),
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun AppListComposablePreview() {
|
||||
AppListComposable(
|
||||
app = App(
|
||||
packageName = BuildConfig.APPLICATION_ID,
|
||||
displayName = LocalContext.current.getString(R.string.app_name),
|
||||
developerName = "Rahul Kumar Patel",
|
||||
isFree = true,
|
||||
containsAds = false,
|
||||
size = 7431013
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildExtras(app: App): List<String> {
|
||||
return mutableListOf<String>().apply {
|
||||
add(if (app.size > 0) CommonUtil.addSiPrefix(app.size) else app.downloadString)
|
||||
add("${app.labeledRating}★")
|
||||
|
||||
if (app.isFree) {
|
||||
add(stringResource(R.string.details_free))
|
||||
} else {
|
||||
add(stringResource(R.string.details_paid))
|
||||
}
|
||||
|
||||
if (app.containsAds) {
|
||||
add(stringResource(R.string.details_contains_ads))
|
||||
} else {
|
||||
add(stringResource(R.string.details_no_ads))
|
||||
}
|
||||
|
||||
if (app.dependencies.dependentPackages.contains(PackageUtil.PACKAGE_NAME_GMS)) {
|
||||
add(stringResource(R.string.details_gsf_dependent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables.app
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to display an indeterminate circular progress indicator
|
||||
*/
|
||||
@Composable
|
||||
fun AppProgressComposable() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dimensionResource(R.dimen.padding_small)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_small))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AppProgressComposablePreview() {
|
||||
AppProgressComposable()
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables.app
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to show error message when no apps are available for a request
|
||||
* @param message Message for error
|
||||
* @see NoAppAltComposable
|
||||
*/
|
||||
@Composable
|
||||
fun NoAppAltComposable(@StringRes message: Int) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dimensionResource(R.dimen.padding_xsmall))
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(message),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NoAppAltComposablePreview() {
|
||||
NoAppAltComposable(message = R.string.details_no_dependencies)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables.app
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* Composable to show error message when no apps are available for a request
|
||||
* @param icon Drawable for error
|
||||
* @param message Message for error
|
||||
* @param actionMessage Message to show on action button; defaults to null with button not visible
|
||||
* @param onAction Callback when action button is clicked
|
||||
* @see NoAppAltComposable
|
||||
*/
|
||||
@Composable
|
||||
fun NoAppComposable(
|
||||
@DrawableRes icon: Int,
|
||||
@StringRes message: Int,
|
||||
@StringRes actionMessage: Int? = null,
|
||||
onAction: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
dimensionResource(R.dimen.margin_small),
|
||||
Alignment.CenterVertically
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size))
|
||||
)
|
||||
Text(text = stringResource(message))
|
||||
|
||||
if (actionMessage != null) {
|
||||
Button(onClick = onAction) {
|
||||
Text(
|
||||
text = stringResource(actionMessage),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NoAppComposablePreview() {
|
||||
NoAppComposable(
|
||||
icon = R.drawable.ic_updates,
|
||||
message = R.string.details_no_updates,
|
||||
actionMessage = R.string.check_updates
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user