-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
37 changed files
with
901 additions
and
255 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
common/src/main/java/com/github/sikv/photos/common/ActivityPermissionManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.github.sikv.photos.common | ||
|
||
import androidx.activity.result.ActivityResultLauncher | ||
import androidx.activity.result.contract.ActivityResultContracts | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.lifecycle.DefaultLifecycleObserver | ||
import androidx.lifecycle.LifecycleOwner | ||
import java.util.UUID | ||
|
||
class ActivityPermissionManager( | ||
private val activity: AppCompatActivity | ||
) : DefaultLifecycleObserver { | ||
|
||
private val key = UUID.randomUUID().toString() | ||
private lateinit var requestPermission: ActivityResultLauncher<String> | ||
|
||
private var onPermissionRequestResult: ((Boolean) -> Unit)? = null | ||
|
||
init { | ||
activity.lifecycle.addObserver(this) | ||
} | ||
|
||
override fun onCreate(owner: LifecycleOwner) { | ||
super.onCreate(owner) | ||
|
||
requestPermission = activity.activityResultRegistry | ||
.register(key, ActivityResultContracts.RequestPermission()) { granted -> | ||
onPermissionRequestResult?.invoke(granted) | ||
} | ||
} | ||
|
||
fun requestPermission(permission: String, onPermissionRequestResult: (Boolean) -> Unit) { | ||
this.onPermissionRequestResult = onPermissionRequestResult | ||
requestPermission.launch(permission) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
plugins { | ||
alias(libs.plugins.android.library) | ||
alias(libs.plugins.kotlin.android) | ||
} | ||
|
||
android { | ||
namespace = "com.github.sikv.photos.compose.ui" | ||
|
||
buildFeatures { | ||
compose = true | ||
viewBinding = true | ||
} | ||
|
||
composeOptions { | ||
kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation(project(":domain")) | ||
implementation(project(":common-ui")) | ||
|
||
implementation(libs.androidx.compose.material3) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest /> |
83 changes: 83 additions & 0 deletions
83
compose-ui/src/main/java/com/github/sikv/photos/compose/ui/FavoriteButton.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package com.github.sikv.photos.compose.ui | ||
|
||
import androidx.compose.animation.animateColorAsState | ||
import androidx.compose.animation.core.Animatable | ||
import androidx.compose.animation.core.LinearEasing | ||
import androidx.compose.animation.core.keyframes | ||
import androidx.compose.animation.core.tween | ||
import androidx.compose.foundation.layout.offset | ||
import androidx.compose.material3.LocalContentColor | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.draw.scale | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.res.colorResource | ||
import androidx.compose.ui.unit.dp | ||
import com.github.sikv.photos.common.ui.ActionIconButton | ||
|
||
private const val favoriteAnimationDuration = 100 | ||
private const val unFavoriteAnimationDuration = 400 | ||
|
||
@Composable | ||
fun FavoriteButton( | ||
isFavorite: Boolean, | ||
onToggleFavorite: () -> Unit | ||
) { | ||
val scale = remember { Animatable(1.0f) } | ||
val offsetX = remember { Animatable(0f) } | ||
|
||
LaunchedEffect(isFavorite) { | ||
if (isFavorite) { | ||
scale.animateTo( | ||
targetValue = 1.3f, | ||
animationSpec = tween(favoriteAnimationDuration, easing = LinearEasing) | ||
) | ||
scale.animateTo( | ||
targetValue = 1f, | ||
animationSpec = tween(favoriteAnimationDuration, easing = LinearEasing) | ||
) | ||
} else { | ||
// Source: https://ophilia.in/creating-a-wiggle-animation-in-jetpack-compose | ||
offsetX.animateTo( | ||
targetValue = 0f, | ||
animationSpec = keyframes { | ||
for (i in 1..8) { | ||
val x = when (i % 3) { | ||
0 -> 2f | ||
1 -> -2f | ||
else -> 0f | ||
} | ||
x at unFavoriteAnimationDuration / 10 * i with LinearEasing | ||
} | ||
} | ||
) | ||
} | ||
} | ||
|
||
val icon = | ||
if (isFavorite) R.drawable.ic_favorite_red_24dp | ||
else R.drawable.ic_favorite_border_white_24dp | ||
|
||
val tintColor = | ||
if (isFavorite) colorResource(id = R.color.colorRed) | ||
else LocalContentColor.current | ||
|
||
val tint: Color by animateColorAsState( | ||
targetValue = tintColor, | ||
animationSpec = tween(favoriteAnimationDuration), | ||
label = "Favorite button color animation", | ||
) | ||
|
||
ActionIconButton( | ||
icon = icon, | ||
contentDescription = R.string.toggle_favorite, | ||
iconTint = tint, | ||
onClick = onToggleFavorite, | ||
modifier = Modifier | ||
.scale(scale.value) | ||
.offset(x = offsetX.value.dp, y = 0.dp) | ||
) | ||
} |
154 changes: 154 additions & 0 deletions
154
compose-ui/src/main/java/com/github/sikv/photos/compose/ui/PhotoItem.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package com.github.sikv.photos.compose.ui | ||
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.clickable | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.RowScope | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.aspectRatio | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.foundation.layout.width | ||
import androidx.compose.foundation.shape.CircleShape | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.filled.MoreVert | ||
import androidx.compose.material.icons.filled.Share | ||
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.graphics.Color | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.colorResource | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.unit.dp | ||
import com.github.sikv.photos.common.ui.ActionIconButton | ||
import com.github.sikv.photos.common.ui.NetworkImage | ||
import com.github.sikv.photos.common.ui.PlaceholderImage | ||
import com.github.sikv.photos.common.ui.getAttributionPlaceholderBackgroundColor | ||
import com.github.sikv.photos.common.ui.getAttributionPlaceholderTextColor | ||
import com.github.sikv.photos.domain.Photo | ||
|
||
@Composable | ||
fun PhotoItem( | ||
photo: Photo, | ||
isFavorite: Boolean, | ||
onClick: () -> Unit, | ||
onAttributionClick: () -> Unit, | ||
onMoreClick: () -> Unit, | ||
onToggleFavorite: () -> Unit, | ||
onShareClick: () -> Unit, | ||
onDownloadClick: () -> Unit | ||
) { | ||
Column { | ||
Row( | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
Spacer(modifier = Modifier.width(Spacing.One)) | ||
Attribution( | ||
photo = photo, | ||
onAttributionClick = onAttributionClick | ||
) | ||
IconButton( | ||
modifier = Modifier | ||
.background(Color.Transparent, shape = CircleShape), | ||
onClick = onMoreClick | ||
) { | ||
Icon( | ||
imageVector = Icons.Default.MoreVert, | ||
contentDescription = stringResource(id = R.string.more) | ||
) | ||
} | ||
} | ||
|
||
Spacer(modifier = Modifier.height(Spacing.One)) | ||
|
||
NetworkImage( | ||
imageUrl = photo.getPhotoPreviewUrl(), | ||
loading = { | ||
Box( | ||
modifier = Modifier | ||
.aspectRatio(1f) | ||
.background(colorResource(id = R.color.colorPlaceholder)) | ||
) | ||
}, | ||
modifier = Modifier | ||
.aspectRatio(1f) | ||
.clickable(onClick = onClick) | ||
) | ||
Row { | ||
FavoriteButton( | ||
isFavorite = isFavorite, | ||
onToggleFavorite = onToggleFavorite | ||
) | ||
IconButton( | ||
modifier = Modifier | ||
.background(Color.Transparent, shape = CircleShape), | ||
onClick = onShareClick | ||
) { | ||
Icon( | ||
imageVector = Icons.Default.Share, | ||
contentDescription = stringResource(id = R.string.share) | ||
) | ||
} | ||
Spacer(modifier = Modifier.weight(1f)) | ||
ActionIconButton( | ||
icon = R.drawable.ic_file_download_24dp, | ||
contentDescription = R.string.download, | ||
onClick = onDownloadClick | ||
) | ||
} | ||
|
||
Spacer(modifier = Modifier.height(Spacing.Two)) | ||
} | ||
} | ||
|
||
@Composable | ||
private fun RowScope.Attribution( | ||
photo: Photo, | ||
onAttributionClick: () -> Unit | ||
) { | ||
Row( | ||
verticalAlignment = Alignment.CenterVertically, | ||
modifier = Modifier | ||
.clickable(onClick = onAttributionClick) | ||
.padding(end = Spacing.One) | ||
.weight(1f) | ||
) { | ||
val photographerImageUrl = photo.getPhotoPhotographerImageUrl() | ||
|
||
val modifier = Modifier | ||
.size(36.dp) | ||
.clip(CircleShape) | ||
|
||
if (photographerImageUrl != null) { | ||
NetworkImage( | ||
imageUrl = photographerImageUrl, | ||
modifier = modifier | ||
) | ||
} else { | ||
PlaceholderImage( | ||
text = photo.getPhotoPhotographerName().first().uppercaseChar().toString(), | ||
textColor = getAttributionPlaceholderTextColor(LocalContext.current), | ||
backgroundColor = getAttributionPlaceholderBackgroundColor(LocalContext.current), | ||
modifier = modifier | ||
) | ||
} | ||
Spacer(modifier = Modifier.width(Spacing.One)) | ||
Column { | ||
Text(photo.getPhotoPhotographerName(), | ||
style = MaterialTheme.typography.titleSmall | ||
) | ||
Text(photo.getPhotoSource().title, | ||
style = MaterialTheme.typography.bodySmall | ||
) | ||
} | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
compose-ui/src/main/java/com/github/sikv/photos/compose/ui/PhotoItemCompact.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.github.sikv.photos.compose.ui | ||
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.clickable | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.aspectRatio | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.res.colorResource | ||
import com.github.sikv.photos.common.ui.NetworkImage | ||
import com.github.sikv.photos.domain.Photo | ||
|
||
@Composable | ||
fun PhotoItemCompact( | ||
photo: Photo, | ||
onClick: () -> Unit | ||
) { | ||
NetworkImage( | ||
imageUrl = photo.getPhotoPreviewUrl(), | ||
loading = { | ||
Box( | ||
modifier = Modifier | ||
.aspectRatio(1f) | ||
.background(colorResource(id = R.color.colorPlaceholder)) | ||
) | ||
}, | ||
modifier = Modifier | ||
.aspectRatio(1f) | ||
.clickable(onClick = onClick) | ||
) | ||
} |
Oops, something went wrong.