Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release/v1/1.1.0 #51

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions .github/workflows/cd-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
Expand All @@ -32,7 +32,7 @@ jobs:

- name: Generate file name env var
run: |
DATE=$(date +'%d.%m.%Y')
DATE=$(date +'%d.%m.%Y-%H%M%S')
BRANCH_NAME=${GITHUB_REF##*/}
MESSAGE=$(cat << EOF
PurrfectPics-release-${BRANCH_NAME}-${DATE}
Expand All @@ -47,9 +47,9 @@ jobs:
mv "./app/build/outputs/apk/release/app-release.apk" "./app/build/outputs/apk/release/${{ env.OUTPUT_NAME }}.apk"

- name: Upload release APK
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
with:
name: ${{ env.OUTPUT_NAME }}
name: ${{ env.OUTPUT_NAME }}-APK
path: app/build/outputs/apk/release/${{ env.OUTPUT_NAME }}.apk

- name: Build release AAB
Expand All @@ -59,8 +59,15 @@ jobs:
run:
mv "./app/build/outputs/bundle/release/app-release.aab" "./app/build/outputs/bundle/release/${{ env.OUTPUT_NAME }}.aab"

- name: Upload debug AAB
uses: actions/upload-artifact@v1
- name: Upload release AAB
uses: actions/upload-artifact@v4
with:
name: ${{ env.OUTPUT_NAME }}
path: app/build/outputs/bundle/release/${{ env.OUTPUT_NAME }}.aab
name: ${{ env.OUTPUT_NAME }}-AAB
path: app/build/outputs/bundle/release/${{ env.OUTPUT_NAME }}.aab

- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: merged-release-artifacts
pattern: ${{ env.OUTPUT_NAME }}-*
delete-merged: true
4 changes: 2 additions & 2 deletions .github/workflows/ci-android.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:

steps:
- name: Checkout project sources
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ heart.

## Screenshots

| ![Screenshot 2024-04-16 at 10 26 30 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/c08f645e-a362-4d2e-bfff-c99d8966e2b8) | ![Screenshot 2024-04-19 at 12 06 41 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/f7c9873c-8ab8-4215-b4f3-ed801dd8f6c9) | ![Screenshot 2024-04-19 at 12 17 14 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/534f2f34-5f9d-4945-83bf-0076f667bc19)| ![Screenshot 2024-04-19 at 12 10 40 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/69042702-71aa-4eae-b508-871d9c630ff8) | ![Screenshot 2024-04-19 at 12 16 02 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/e55d1bae-6aa4-4401-a6b6-8754b0756efd)|
|----------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![Screenshot 2024-04-16 at 10 26 30 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/c08f645e-a362-4d2e-bfff-c99d8966e2b8) | ![Screenshot 2024-04-19 at 12 06 41 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/f7c9873c-8ab8-4215-b4f3-ed801dd8f6c9) | ![Screenshot 2024-04-19 at 12 17 14 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/534f2f34-5f9d-4945-83bf-0076f667bc19) | ![Screenshot 2024-04-19 at 12 10 40 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/69042702-71aa-4eae-b508-871d9c630ff8) | ![Screenshot 2024-04-19 at 12 16 02 AM](https://github.com/manununhez/purrfect-pics/assets/5048531/e55d1bae-6aa4-4401-a6b6-8754b0756efd) | ![Screenshot 2024-04-19 at 2 15 49 PM](https://github.com/manununhez/purrfect-pics/assets/5048531/b4185b5a-85b7-47e0-91df-ab0e0d822c42) |
|:--:|:--:|:--:|:--:|:--:|:--:|
| Splashscreen | Home - Featured and popular items | Popular paginated items | Your favorites cats | Detail image (share and favorite button) | Detail image with zoomable elements|

## Considerations

Expand Down
36 changes: 30 additions & 6 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile

#Datastore
-keep class androidx.datastore.*.** {*;}
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* {
<fields>;
}

#https://stackoverflow.com/questions/42782328/retrofit-2-returns-null-in-release-apk-when-minifyenable-but-ok-in-debug-apk
-keep class com.manuelnunez.apps.core.services.dto.* {*;}
Expand Down Expand Up @@ -77,20 +82,39 @@
#Hilt
-keep class androidx.hilt.* {*;}

## Fragment names
-keepnames class * extends androidx.fragment.app.Fragment
#Serialization
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}

-keepnames class * extends android.os.Parcelable
-keepnames class * extends java.io.Serializable
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}

# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}

# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
### R8
## Gson
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}

-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
## Kotlin suspend functions
-keep class kotlin.coroutines.Continuation

Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
package com.manuelnunez.apps.core.ui.component

import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImagePainter
import coil.compose.AsyncImagePainter.State.Error
import coil.compose.AsyncImagePainter.State.Loading
import coil.compose.rememberAsyncImagePainter
import com.manuelnunez.apps.core.ui.R
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.roundToInt
import kotlin.math.sin

@Composable
fun StatefulAsyncImage(
modifier: Modifier = Modifier,
imageUrl: String,
contentDescription: String,
zoomable: Boolean = false,
placeholder: Painter = painterResource(R.drawable.ic_broken_image),
contentScale: ContentScale = ContentScale.Crop
) {
Expand All @@ -53,11 +66,73 @@ fun StatefulAsyncImage(
)
}

Image(
modifier = modifier,
painter = if (isError.not() && !isLocalInspection) imageLoader else placeholder,
contentScale =
if (isError.not() && !isLocalInspection) contentScale else ContentScale.FillBounds,
contentDescription = contentDescription)
if (zoomable) {
ZoomableImage(
modifier,
isError,
isLocalInspection,
imageLoader,
placeholder,
contentScale,
contentDescription)
} else {
Image(
modifier = modifier,
painter = if (isError.not() && !isLocalInspection) imageLoader else placeholder,
contentScale =
if (isError.not() && !isLocalInspection) contentScale else ContentScale.FillBounds,
contentDescription = contentDescription)
}
}
}

@Composable
private fun ZoomableImage(
modifier: Modifier,
isError: Boolean,
isLocalInspection: Boolean,
imageLoader: AsyncImagePainter,
placeholder: Painter,
contentScale: ContentScale,
contentDescription: String
) {
val angle by remember { mutableFloatStateOf(0f) }
var zoom by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }

val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp.value
val screenHeight = configuration.screenHeightDp.dp.value

Image(
modifier =
modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.graphicsLayer(scaleX = zoom, scaleY = zoom, rotationZ = angle)
.pointerInput(Unit) {
detectTransformGestures(
onGesture = { _, pan, gestureZoom, _ ->
zoom = (zoom * gestureZoom).coerceIn(1F..4F)
if (zoom > 1) {
val x = (pan.x * zoom)
val y = (pan.y * zoom)
val angleRad = angle * PI / 180.0

offsetX =
(offsetX + (x * cos(angleRad) - y * sin(angleRad)).toFloat()).coerceIn(
-(screenWidth * zoom)..(screenWidth * zoom))
offsetY =
(offsetY + (x * sin(angleRad) + y * cos(angleRad)).toFloat()).coerceIn(
-(screenHeight * zoom)..(screenHeight * zoom))
} else {
offsetX = 0F
offsetY = 0F
}
})
},
painter = if (isError.not() && !isLocalInspection) imageLoader else placeholder,
contentScale =
if (isError.not() && !isLocalInspection) contentScale else ContentScale.FillBounds,
contentDescription = contentDescription)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.windowInsetsPadding
Expand Down Expand Up @@ -76,10 +77,11 @@ private fun DetailPortrait(
modifier = Modifier.weight(1f).wrapContentSize(),
horizontalAlignment = Alignment.CenterHorizontally) {
StatefulAsyncImage(
modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp),
modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp).heightIn(min = 180.dp),
imageUrl = item.imageUrl,
contentDescription = item.photoId,
contentScale = ContentScale.Fit)
contentScale = ContentScale.Fit,
zoomable = true)

Row {
ShareImage(url = item.imageUrl)
Expand Down Expand Up @@ -128,10 +130,12 @@ private fun DetailLandscape(

Row(verticalAlignment = Alignment.CenterVertically) {
StatefulAsyncImage(
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp).weight(0.7f),
modifier =
Modifier.fillMaxWidth().padding(vertical = 6.dp).weight(0.7f).heightIn(min = 180.dp),
imageUrl = item.imageUrl,
contentDescription = item.photoId,
contentScale = ContentScale.Fit)
contentScale = ContentScale.Fit,
zoomable = true)

Column(Modifier.weight(0.1f)) {
ShareImage(url = item.imageUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
Expand All @@ -24,6 +26,9 @@ import androidx.compose.ui.unit.dp
import com.manuelnunez.apps.core.domain.model.Item
import com.manuelnunez.apps.core.ui.component.ImageCard
import com.manuelnunez.apps.core.ui.component.SurfaceText
import com.manuelnunez.apps.core.ui.theme.MainTheme
import com.manuelnunez.apps.core.ui.utils.OrientationPreviews
import com.manuelnunez.apps.core.ui.utils.ThemePreviews
import com.manuelnunez.apps.features.favorites.ui.R

@Composable
Expand All @@ -50,19 +55,50 @@ private fun FavoriteItems(navigateToDetails: (Item) -> Unit, favoriteItems: List
GridCells.Adaptive(minSize = 100.dp)
} else GridCells.Fixed(3)

LazyVerticalGrid(
columns = cellConfiguration,
modifier = Modifier.fillMaxSize(),
state = gridState,
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, top = 10.dp, bottom = 20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
items(items = favoriteItems) { item ->
ImageCard(
modifier = Modifier.size(width = 100.dp, height = 200.dp),
imageUrl = item.imageUrl,
cardContentDescription = item.description,
onClick = { navigateToDetails.invoke(item) })
if (favoriteItems.isEmpty()) {
Text(
modifier = Modifier.fillMaxSize().padding(10.dp),
color = MaterialTheme.colorScheme.onSurface,
text = "Add your favorite cats, and they will be display here! ")
} else {
LazyVerticalGrid(
columns = cellConfiguration,
modifier = Modifier.fillMaxSize(),
state = gridState,
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, top = 10.dp, bottom = 20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
items(items = favoriteItems) { item ->
ImageCard(
modifier = Modifier.size(width = 100.dp, height = 200.dp),
imageUrl = item.imageUrl,
cardContentDescription = item.description,
onClick = { navigateToDetails.invoke(item) })
}
}
}
}
}

@ThemePreviews
@OrientationPreviews
@Composable
fun FavoriteScreenEmptyPreview() {
MainTheme { FavoritesScreen(items = emptyList(), navigateToDetails = {}, onBackClick = {}) }
}

@ThemePreviews
@OrientationPreviews
@Composable
fun FavoriteScreenPreview() {
MainTheme {
val items =
List(5) { index ->
Item(
"$index",
"https://example.com/$index",
description = "description: $index",
thumbnailUrl = "https://example.com/$index")
}
FavoritesScreen(items = items, navigateToDetails = {}, onBackClick = {})
}
}
Loading