From c64fff0a4afbc7d31e93713d944b9e35b8668659 Mon Sep 17 00:00:00 2001 From: Iamlooker Date: Thu, 7 Apr 2022 10:41:23 +0530 Subject: [PATCH] Create AppDetail Header Add InstallState enum { TODO(Replace with sealed class) } Use string in image loading { Uri handling is not good in Compose } Make Zoomable image more performant Add few strings { Might Change } --- .../com/looker/droidify/entity/Enums.kt | 7 + .../ui/compose/components/ProductCard.kt | 2 +- .../ui/compose/components/ProductsListItem.kt | 2 +- .../ui/compose/components/ScreenshotList.kt | 2 +- .../ui/compose/pages/app_detail/AppDetail.kt | 2 + .../pages/app_detail/components/Header.kt | 186 ++++++++++++++++++ .../looker/droidify/ui/compose/utils/Image.kt | 61 +++--- src/main/res/values/strings.xml | 2 + 8 files changed, 223 insertions(+), 41 deletions(-) create mode 100644 src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/AppDetail.kt create mode 100644 src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/components/Header.kt diff --git a/src/main/kotlin/com/looker/droidify/entity/Enums.kt b/src/main/kotlin/com/looker/droidify/entity/Enums.kt index 76b7ea61..44af78f5 100644 --- a/src/main/kotlin/com/looker/droidify/entity/Enums.kt +++ b/src/main/kotlin/com/looker/droidify/entity/Enums.kt @@ -12,4 +12,11 @@ enum class UpdateCategory(val id: Int) { ALL(0), UPDATED(1), NEW(2) +} + +enum class InstallState { + INSTALL, + INSTALLING, + INSTALLED, + PENDING } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductCard.kt b/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductCard.kt index f22b2113..2c5e957c 100644 --- a/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductCard.kt +++ b/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductCard.kt @@ -36,7 +36,7 @@ fun ProductCard( item.metadataIcon, repo?.address, repo?.authentication - ) + ).toString() ) } diff --git a/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductsListItem.kt b/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductsListItem.kt index 6e78ed66..8ab827e9 100644 --- a/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductsListItem.kt +++ b/src/main/kotlin/com/looker/droidify/ui/compose/components/ProductsListItem.kt @@ -40,7 +40,7 @@ fun ProductsListItem( item.metadataIcon, repo?.address, repo?.authentication - ) + ).toString() ) } diff --git a/src/main/kotlin/com/looker/droidify/ui/compose/components/ScreenshotList.kt b/src/main/kotlin/com/looker/droidify/ui/compose/components/ScreenshotList.kt index 4f01c1c9..87d98ddb 100644 --- a/src/main/kotlin/com/looker/droidify/ui/compose/components/ScreenshotList.kt +++ b/src/main/kotlin/com/looker/droidify/ui/compose/components/ScreenshotList.kt @@ -36,7 +36,7 @@ fun ScreenshotList( .wrapContentWidth() .requiredHeight(120.dp) .clickable { onScreenShotClick(it) }, - data = it.path.toUri(), + data = it.path, shape = RoundedCornerShape(LocalShapes.current.large) ) } diff --git a/src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/AppDetail.kt b/src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/AppDetail.kt new file mode 100644 index 00000000..5c1003ae --- /dev/null +++ b/src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/AppDetail.kt @@ -0,0 +1,2 @@ +package com.looker.droidify.ui.compose.pages.app_detail + diff --git a/src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/components/Header.kt b/src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/components/Header.kt new file mode 100644 index 00000000..7578bfa3 --- /dev/null +++ b/src/main/kotlin/com/looker/droidify/ui/compose/pages/app_detail/components/Header.kt @@ -0,0 +1,186 @@ +package com.looker.droidify.ui.compose.pages.app_detail.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Close +import androidx.compose.material.icons.rounded.Download +import androidx.compose.material.icons.rounded.Launch +import androidx.compose.material3.* +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.res.stringResource +import androidx.compose.ui.unit.dp +import com.looker.droidify.R +import com.looker.droidify.entity.InstallState +import com.looker.droidify.ui.compose.theme.LocalShapes +import com.looker.droidify.ui.compose.utils.NetworkImage +import com.looker.droidify.utility.extension.text.formatSize + +@Composable +fun Header( + modifier: Modifier = Modifier, + icon: String?, + appName: String, + packageName: String, + versionCode: String, + appSize: String, + appDev: String +) { + Surface( + modifier = modifier.fillMaxWidth(), + shape = RoundedCornerShape(LocalShapes.current.large) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + HeaderTitle( + modifier = Modifier.fillMaxWidth(), + icon = icon, + appName = appName, + packageName = packageName + ) + HeaderExtra( + versionCode = versionCode, + appSize = appSize, + appDev = appDev + ) + DownloadProgress(totalSize = 69420) + InstallButton(installState = InstallState.PENDING) + } + } +} + +@Composable +fun HeaderTitle( + modifier: Modifier = Modifier, + icon: String? = null, + appName: String, + packageName: String, + actions: @Composable () -> Unit = {} +) { + Row( + modifier = modifier.padding(16.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + NetworkImage( + modifier = Modifier.size(56.dp), + data = icon + ) + Spacer(modifier = Modifier.width(16.dp)) + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.SpaceAround + ) { + Text(text = appName, style = MaterialTheme.typography.titleLarge) + Text(text = packageName, style = MaterialTheme.typography.bodyMedium) + } + Box { actions() } + } +} + +@Composable +fun HeaderExtra( + modifier: Modifier = Modifier, + versionCode: String, + appSize: String, + appDev: String +) { + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + HeaderExtrasCard( + modifier = Modifier.weight(1f), + title = stringResource(id = R.string.version), + text = versionCode + ) + HeaderExtrasCard( + modifier = Modifier.weight(1f), + title = stringResource(id = R.string.size), + text = appSize + ) + HeaderExtrasCard( + modifier = Modifier.weight(1f), + title = stringResource(id = R.string.source_code), + text = appDev + ) + } +} + +@Composable +fun HeaderExtrasCard( + modifier: Modifier = Modifier, + extra: @Composable () -> Unit = {}, + title: String, + text: String, + onClick: () -> Unit = {} +) { + Surface( + modifier = modifier + .clip(RoundedCornerShape(50)) + .clickable { onClick() } + .padding(16.dp) + ) { + Column( + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = Alignment.CenterHorizontally + ) { + extra() + Text(text = title, style = MaterialTheme.typography.labelLarge) + Text(text = text, style = MaterialTheme.typography.bodyMedium) + } + } +} + +@Composable +fun DownloadProgress( + modifier: Modifier = Modifier, + downloading: Float = 0f, + totalSize: Long +) { + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.Start + ) { + Text(text = totalSize.div(downloading).toLong().formatSize() + "/" + totalSize.formatSize()) + Spacer(modifier = Modifier.height(8.dp)) + LinearProgressIndicator(progress = downloading) + } +} + +@Composable +fun InstallButton( + modifier: Modifier = Modifier, + installState: InstallState, + onClick: () -> Unit = {} +) { + Button( + modifier = modifier, + onClick = onClick + ) { + Icon( + imageVector = when (installState) { + InstallState.INSTALLING -> Icons.Rounded.Close + InstallState.INSTALLED -> Icons.Rounded.Launch + InstallState.PENDING -> Icons.Rounded.Close + InstallState.INSTALL -> Icons.Rounded.Download + }, + contentDescription = null + ) + Text( + text = when (installState) { + InstallState.INSTALLING -> stringResource(id = R.string.installing) + InstallState.INSTALLED -> stringResource(id = R.string.launch) + InstallState.PENDING -> stringResource(id = R.string.pending) + InstallState.INSTALL -> stringResource(id = R.string.install) + } + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/ui/compose/utils/Image.kt b/src/main/kotlin/com/looker/droidify/ui/compose/utils/Image.kt index 2c40c5a2..b3d4081c 100644 --- a/src/main/kotlin/com/looker/droidify/ui/compose/utils/Image.kt +++ b/src/main/kotlin/com/looker/droidify/ui/compose/utils/Image.kt @@ -1,37 +1,30 @@ package com.looker.droidify.ui.compose.utils -import android.net.Uri import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTransformGestures -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.gestures.rememberTransformableState +import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource -import coil.annotation.ExperimentalCoilApi import coil.compose.SubcomposeAsyncImage import coil.compose.SubcomposeAsyncImageContent import com.looker.droidify.R import com.looker.droidify.ui.compose.theme.LocalShapes -@OptIn(ExperimentalCoilApi::class) @Composable fun NetworkImage( modifier: Modifier = Modifier, - data: Uri?, + data: String?, contentScale: ContentScale = ContentScale.Crop, backgroundColor: Color = MaterialTheme.colorScheme.surface, shape: Shape = RoundedCornerShape(LocalShapes.current.medium) @@ -57,32 +50,24 @@ fun NetworkImage( @Composable fun ZoomableImage( modifier: Modifier = Modifier, - data: Uri? + data: String?, ) { - val scale = remember { mutableStateOf(1f) } - val rotationState = remember { mutableStateOf(1f) } - Box( - modifier = modifier - .fillMaxSize() // Give the size you want... - .background(Color.Gray) - .pointerInput(Unit) { - detectTransformGestures { _, _, zoom, rotation -> - scale.value *= zoom - rotationState.value += rotation - } - } - ) { - NetworkImage( - modifier = Modifier - .align(Alignment.Center) // keep the image centralized into the Box - .graphicsLayer( - // adding some zoom limits (min 50%, max 200%) - scaleX = maxOf(.5f, minOf(3f, scale.value)), - scaleY = maxOf(.5f, minOf(3f, scale.value)), - rotationZ = rotationState.value - ), - data = data, - shape = RectangleShape - ) + var scale by remember { mutableStateOf(1f) } + var offset by remember { mutableStateOf(Offset.Zero) } + val state = rememberTransformableState { zoomChange, offsetChange, _ -> + scale *= zoomChange + offset += offsetChange * scale } + NetworkImage( + modifier = modifier + .graphicsLayer( + scaleX = scale, + scaleY = scale, + translationX = offset.x, + translationY = offset.y + ) + .transformable(state = state), + data = data, + shape = RectangleShape + ) } \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ba2ad79d..54484d9e 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -187,4 +187,6 @@ Number of new apps to show New repository Default Tab + Pending + Installing