From ec762431cd83ff146e6618f760708fb133f08eb5 Mon Sep 17 00:00:00 2001 From: Vinay Gregory John Date: Fri, 19 Apr 2024 11:41:15 +0530 Subject: [PATCH] feat: Add notification deletion animation --- siren-sdk/build.gradle.kts | 2 +- .../siren/androidsdk/core/SDKCoreUI.kt | 117 ++++++++++-------- .../core/elements/NotificationCard.kt | 68 +++++----- 3 files changed, 101 insertions(+), 86 deletions(-) diff --git a/siren-sdk/build.gradle.kts b/siren-sdk/build.gradle.kts index 1c6a177..ba9114c 100644 --- a/siren-sdk/build.gradle.kts +++ b/siren-sdk/build.gradle.kts @@ -78,7 +78,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") // Coil - implementation("io.coil-kt:coil-compose:1.4.0") + implementation("io.coil-kt:coil-compose:2.6.0") } afterEvaluate { diff --git a/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/SDKCoreUI.kt b/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/SDKCoreUI.kt index 5b76403..f6709d5 100644 --- a/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/SDKCoreUI.kt +++ b/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/SDKCoreUI.kt @@ -1,6 +1,9 @@ package com.keyvalue.siren.androidsdk.core import android.content.Context +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.core.tween +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -65,6 +68,7 @@ import com.keyvalue.siren.androidsdk.utils.constants.TOKEN_VERIFICATION_RETRY_IN import com.keyvalue.siren.androidsdk.utils.constants.TokenVerificationStatus import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged @@ -132,7 +136,10 @@ abstract class SDKCoreUI(context: Context, userToken: String, recipientId: Strin painter = painterResource(id = R.drawable.bell_dark), contentDescription = "siren-notification-icon", tint = if (props.darkMode == true) Color.White else Color.Black, - modifier = Modifier.size(iconStyle?.size!!).semantics { contentDescription = "siren-notification-icon" }, + modifier = + Modifier + .size(iconStyle?.size!!) + .semantics { contentDescription = "siren-notification-icon" }, ) } @@ -380,22 +387,26 @@ abstract class SDKCoreUI(context: Context, userToken: String, recipientId: Strin } } + var deletedItem by remember { + mutableStateOf("") + } + + LaunchedEffect(deletedItem) { + if (deletedItem != "") { + delay(300) + deleteByIdState.emit(deletedItem) + deletedItem = "" + } + } + deleteByIdState.collectAsState().apply { if (this.value.isNotEmpty()) { - var position: Int - notificationListState.mapIndexed { index, responseData -> - if (responseData?.id == this.value) { - position = index - notificationListState = notificationListState.subList( - 0, if (position == 0) 0 else position, - ) + - notificationListState.subList( - position + 1, notificationListState.size, - ) - if (notificationListState.isEmpty()) { - showListEmptyState = true - } + notificationListState = + notificationListState.filter { item -> + item?.id != this.value } + if (notificationListState.isEmpty()) { + showListEmptyState = true } } } @@ -594,51 +605,53 @@ abstract class SDKCoreUI(context: Context, userToken: String, recipientId: Strin if (notificationData != null) { it(notificationData) } - } ?: NotificationCard( - notification = notificationData, - cardProps = props.cardProps, - notificationCardStyle, - onCardClick = { - callback.onCardClick(it) - }, - deleteNotificationCallback = { - notificationData?.id?.let { - deleteNotificationInner( - it, - ) { dataStatus, id, jsonObject, isError -> - CoroutineScope(Dispatchers.Main).launch { - if (isError && jsonObject != null) { - callback.onError(jsonObject) - } else { - deleteByIdState.emit(it) + } ?: androidx.compose.animation.AnimatedVisibility(visible = deletedItem != notificationData?.id, exit = shrinkVertically(animationSpec = tween(200)), enter = EnterTransition.None) { + NotificationCard( + notification = notificationData, + cardProps = props.cardProps, + notificationCardStyle, + onCardClick = { + callback.onCardClick(it) + }, + deleteNotificationCallback = { + notificationData?.id?.let { + deleteNotificationInner( + it, + ) { dataStatus, id, jsonObject, isError -> + CoroutineScope(Dispatchers.Main).launch { + if (isError && jsonObject != null) { + callback.onError(jsonObject) + } else { + deletedItem = (id ?: "") + } } } } - } - }, - themeColors = themeColors, - darkMode = props.darkMode ?: false, - defaultCardClickCallback = { - notificationData?.id?.let { - markAsReadInner( - it, - ) { - responseData, errorObject, isError -> - CoroutineScope(Dispatchers.Main).launch { - if (isError && errorObject != null) { - callback.onError(errorObject) - } else { - responseData?.id?.let { id -> - markAsReadByIdState.emit( - id, - ) + }, + themeColors = themeColors, + darkMode = props.darkMode ?: false, + defaultCardClickCallback = { + notificationData?.id?.let { + markAsReadInner( + it, + ) { + responseData, errorObject, isError -> + CoroutineScope(Dispatchers.Main).launch { + if (isError && errorObject != null) { + callback.onError(errorObject) + } else { + responseData?.id?.let { id -> + markAsReadByIdState.emit( + id, + ) + } } } } } - } - }, - ) + }, + ) + } } } item { diff --git a/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/elements/NotificationCard.kt b/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/elements/NotificationCard.kt index 7b98a03..afc7173 100644 --- a/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/elements/NotificationCard.kt +++ b/siren-sdk/src/main/java/com/keyvalue/siren/androidsdk/core/elements/NotificationCard.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Spacer 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.foundation.shape.GenericShape import androidx.compose.material.Icon @@ -27,6 +26,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.contentDescription @@ -37,8 +37,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import coil.compose.rememberImagePainter -import coil.transform.CircleCropTransformation +import coil.compose.AsyncImage import com.keyvalue.siren.androidsdk.R import com.keyvalue.siren.androidsdk.data.model.AllNotificationResponseData import com.keyvalue.siren.androidsdk.helper.client.CardProps @@ -59,18 +58,23 @@ fun NotificationCard( darkMode: Boolean, ) { val avatarImageUrl = notification?.message?.avatar?.imageUrl + val avatarContentDescription = "siren-notification-avatar-${notification?.id}" + val avatarModifier = + Modifier + .size(notificationCardStyle?.avatarSize!!) + .clip(CircleShape) + .conditional(cardProps?.onAvatarClick != null && notification != null) { + clickable { + cardProps?.onAvatarClick?.let { + if (notification != null) { + it(notification) + } + } + } + } + .semantics { contentDescription = "siren-notification-avatar-${notification?.id}" } - val painter = - if (avatarImageUrl.isNullOrEmpty()) { - painterResource(id = if (darkMode) R.drawable.avatar_dark else R.drawable.avatar_light) - } else { - rememberImagePainter( - data = avatarImageUrl, - builder = { - transformations(CircleCropTransformation()) - }, - ) - } + val avatarDefaultPainter = painterResource(id = if (darkMode) R.drawable.avatar_dark else R.drawable.avatar_light) val modifier = Modifier @@ -118,25 +122,23 @@ fun NotificationCard( horizontalArrangement = Arrangement.SpaceBetween, ) { if (cardProps?.hideAvatar != true) { - Image( - painter = painter, - contentDescription = "siren-notification-avatar-${notification?.id}", - modifier = - Modifier - .size(notificationCardStyle.avatarSize!!) - .clip(CircleShape) - .weight(1f) - .conditional(cardProps?.onAvatarClick != null && notification != null) { - clickable { - cardProps?.onAvatarClick?.let { - if (notification != null) { - it(notification) - } - } - } - } - .semantics { contentDescription = "siren-notification-avatar-${notification?.id}" }, - ) + Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) { + if (avatarImageUrl.isNullOrEmpty()) { + Image( + painter = avatarDefaultPainter, + contentDescription = avatarContentDescription, + contentScale = ContentScale.FillBounds, + modifier = avatarModifier, + ) + } else { + AsyncImage( + model = avatarImageUrl, + contentDescription = avatarContentDescription, + contentScale = ContentScale.FillBounds, + modifier = avatarModifier, + ) + } + } } Column( modifier =