Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fun PrivacyScreen(
webViewClient = WebViewClient()
})
}
val link = remember { mutableStateOf<String?>(null) }
val link = remember(url) { mutableStateOf<String?>(url) }
Column(
modifier = Modifier
.fillMaxSize()
Expand Down
2 changes: 1 addition & 1 deletion blog/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies {
implementation(project(":blog:domain"))
implementation(libs.dagger)
ksp(libs.dagger.compiler)

implementation(libs.lottie)
testImplementation(libs.junit)
testImplementation(libs.kotlin.test)
testImplementation(libs.androidx.test)
Expand Down
104 changes: 96 additions & 8 deletions blog/ui/src/main/kotlin/eu/peernetwork/blog/ui/compose/MediaView.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package eu.peernetwork.blog.ui.compose

import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
Expand All @@ -17,30 +22,89 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import eu.peernetwork.blog.ui.model.UiAuthor
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import eu.peernetwork.blog.ui.model.UiAction
import eu.peernetwork.blog.ui.model.UiAuthor
import eu.peernetwork.core.ui.R
import eu.peernetwork.core.ui.design.compose.DesignTextButton
import eu.peernetwork.core.ui.theme.PeerTheme
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun BoxScope.ConfirmedLikeHeartOverlay(
isLiked: Boolean,
modifier: Modifier = Modifier,
size: Dp = 160.dp,
rawRes: Int = R.raw.like
) {
val (play, setPlay) = remember { mutableStateOf(false) }
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(rawRes))
val progress by animateLottieCompositionAsState(
composition = composition,
isPlaying = play,
iterations = 1,
speed = 1f
)
var prev by remember { mutableStateOf(isLiked) }
LaunchedEffect(isLiked) {
if (!prev && isLiked) {
setPlay(false)
setPlay(true)
}
prev = isLiked
}
LaunchedEffect(progress) {
if (progress >= 1f) setPlay(false)
}

AnimatedVisibility(
visible = play && progress < 1f,
enter = fadeIn(animationSpec = tween(160)),
exit = fadeOut(animationSpec = tween(220)),
modifier = modifier
) {
LottieAnimation(
composition = composition,
progress = { progress },
modifier = Modifier.size(size)
)
}
}

@Composable
fun MediaView(
author: UiAuthor,
description: String,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
isLiked: Boolean = false,
onClick: () -> Unit = {},
onDoubleClick: () -> Unit = {},
onAuthorClick: () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {},
engagements: @Composable RowScope.() -> Unit = {},
Expand All @@ -53,6 +117,12 @@ fun MediaView(
val updatedCaption by rememberUpdatedState(caption)
val updatedEngagements by rememberUpdatedState(engagements)
val updatedModeration by rememberUpdatedState(moderation)
val click by rememberUpdatedState(onClick)
val dblClick by rememberUpdatedState(onDoubleClick)

val scope = rememberCoroutineScope()
val (singleTapJob, setSingleTapJob) = remember { mutableStateOf<Job?>(null) }

PostScaffold(
modifier = modifier,
header = {},
Expand All @@ -73,13 +143,30 @@ fun MediaView(
footer = { }
) {
Box(
modifier = Modifier.clickable(
enabled = true,
role = Role.Button,
onClick = onClick
)
modifier = Modifier
.semantics { role = Role.Button }
.pointerInput(Unit) {
detectTapGestures(
onTap = {
val job = scope.launch {
delay(120)
click()
}
setSingleTapJob(job)
},
onDoubleTap = {
singleTapJob?.cancel()
dblClick()
}
) }
) {
updatedContent()
ConfirmedLikeHeartOverlay(
isLiked = isLiked,
modifier = Modifier.align(Alignment.Center),
size = 160.dp
)

Image(
painter = painterResource(R.drawable.overlay_gradient),
contentDescription = null,
Expand Down Expand Up @@ -120,6 +207,7 @@ fun PreviewMediaPreview() {
isfollowed = false
),
description = "Description...",
isLiked = false,
engagements = {
UiAction.ENGAGEMENTS.forEach {
DesignTextButton(
Expand Down Expand Up @@ -154,4 +242,4 @@ fun PreviewMediaPreview() {
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@file:OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
package eu.peernetwork.blog.ui.compose

import androidx.compose.foundation.layout.RowScope
Expand All @@ -16,7 +17,9 @@ import eu.peernetwork.blog.ui.model.UiPost
fun LazyItemScope.PostItem(
post: UiPost,
position: Int,
isLiked: Boolean = false,
onClick: () -> Unit = {},
onDoubleClick: () -> Unit = {},
onAuthorClick: () -> Unit = {},
onMentionClick: (String) -> Unit = {},
onHashtagClick: (String) -> Unit = {},
Expand All @@ -37,6 +40,7 @@ fun LazyItemScope.PostItem(
MediaView(
author = post.author,
description = post.time,
isLiked = isLiked,
modifier = Modifier
.padding(bottom = 16.dp),
caption = {
Expand All @@ -52,18 +56,21 @@ fun LazyItemScope.PostItem(
engagements = engagements,
moderation = moderation,
onClick = onClick,
onDoubleClick = onDoubleClick,
onAuthorClick = onAuthorClick,
actions = actions
) { updatedContent(post) }
} else {
TextPreview(
author = post.author,
description = post.time,
isLiked = isLiked,
modifier = Modifier.padding(bottom = 12.dp)
.padding(horizontal = 8.dp),
engagements = engagements,
moderation = moderation,
onClick = onClick,
onDoubleClick = onDoubleClick,
onAuthorClick = onAuthorClick,
actions = actions
) {
Expand Down
53 changes: 44 additions & 9 deletions blog/ui/src/main/kotlin/eu/peernetwork/blog/ui/compose/PostView.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package eu.peernetwork.blog.ui.compose

import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
Expand All @@ -19,24 +18,35 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import eu.peernetwork.blog.ui.model.UiAuthor
import eu.peernetwork.blog.ui.model.UiAction
import eu.peernetwork.blog.ui.model.UiAuthor
import eu.peernetwork.core.ui.design.compose.DesignTextButton
import eu.peernetwork.core.ui.theme.PeerTheme
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun TextPreview(
author: UiAuthor,
description: String,
modifier: Modifier = Modifier,
isLiked: Boolean = false,
onClick: () -> Unit = {},
onDoubleClick: () -> Unit = {},
onAuthorClick: () -> Unit = {},
contentPadding: PaddingValues = PaddingValues(
top = 16.dp,
Expand All @@ -53,6 +63,12 @@ fun TextPreview(
val updatedContent by rememberUpdatedState(content)
val updatedEngagements by rememberUpdatedState(engagements)
val updatedModeration by rememberUpdatedState(moderation)
val tap by rememberUpdatedState(onClick)
val dblTap by rememberUpdatedState(onDoubleClick)

val scope = rememberCoroutineScope()
val (singleTapJob, setSingleTapJob) = remember { mutableStateOf<Job?>(null) }

PostScaffold(
modifier = modifier,
header = {
Expand All @@ -73,12 +89,30 @@ fun TextPreview(
.background(
MaterialTheme.colorScheme.surfaceVariant,
RoundedCornerShape(24.dp)
).clickable(
enabled = true,
role = Role.Button,
onClick = onClick
)
) },
.semantics { role = Role.Button }
.pointerInput(Unit) {
detectTapGestures(
onTap = {
val job = scope.launch {
delay(120)
tap()
}
setSingleTapJob(job)
},
onDoubleTap = {
singleTapJob?.cancel()
dblTap()
}
)
}
) {
ConfirmedLikeHeartOverlay(
isLiked = isLiked,
modifier = Modifier.align(Alignment.Center),
size = 140.dp
)
} },
contentPadding = contentPadding,
footer = {
Row(
Expand All @@ -94,7 +128,7 @@ fun TextPreview(
}

@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview
fun PreviewTextPostCard() {
PeerTheme {
TextPreview(
Expand All @@ -108,6 +142,7 @@ fun PreviewTextPostCard() {
isfollowed = false
),
description = "Description...",
isLiked = false,
engagements = {
UiAction.ENGAGEMENTS.forEach {
DesignTextButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,20 @@ fun LazyItemScope.PhotoListing(
content: @Composable (UiPost) -> Unit = {}
) {
val uiContent = post.mapToContent()
val engagementModel = uiEngagementEvent.onLoad(uiContent)
val clickHandler by rememberUpdatedState { onAuthorClick(post.author.id) }
val updatedConnection by rememberUpdatedState(connection)
val handleOnPostClick by rememberUpdatedState(onPostClick)
PostItem(
post = post,
position = index,
isLiked = engagementModel.isLiked,
onClick = { handleOnPostClick(post.id, index) },
onDoubleClick = {
if (!engagementModel.isLiked) {
uiEngagementEvent.onLike(engagementModel)
}
},
onAuthorClick = clickHandler,
onMentionClick = onMentionClick,
onHashtagClick = onHashtagClick,
Expand Down
Loading
Loading