From 601405349ee4ce2498375d1f09a8c71738615efa Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 4 Nov 2025 20:20:58 +0900 Subject: [PATCH] =?UTF-8?q?feature(live-room-like-heart):=20=EB=A1=B1?= =?UTF-8?q?=ED=94=84=EB=A0=88=EC=8A=A4=20=EC=99=95=ED=95=98=ED=8A=B8=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존: 하트가 33.3dp 부터 커지는 애니메이션 - 변경: 하트가 133.3dp으로 고정되어 있고 물 채우기 애니메이션 --- app/build.gradle | 2 + .../sodalive/live/room/LiveRoomActivity.kt | 80 +++++++++++-------- .../main/res/layout/activity_live_room.xml | 13 +++ 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 35f7881f..b3ccdfcc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -232,6 +232,8 @@ dependencies { implementation 'io.github.glailton.expandabletextview:expandabletextview:1.0.4' + implementation 'com.github.orbitalsonic:Sonic-Water-Wave-Animation:2.0.1' + // ----- Test dependencies ----- testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.20.0' diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt index 34eeac6c..db515355 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt @@ -23,7 +23,6 @@ import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan -import android.view.Gravity import android.view.LayoutInflater import android.view.MotionEvent import android.view.View @@ -38,6 +37,7 @@ import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.PopupMenu import androidx.core.content.ContextCompat +import androidx.core.graphics.toColorInt import androidx.core.net.toUri import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager @@ -49,6 +49,7 @@ import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.google.gson.Gson +import com.orbitalsonic.waterwave.WaterWaveView import com.orhanobut.logger.Logger import io.agora.rtc2.ClientRoleOptions import io.agora.rtc2.IRtcEngineEventHandler @@ -1869,7 +1870,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB } } - private var longPressCenterHeart: ImageView? = null + private var longPressCenterHeart: WaterWaveView? = null // 롱프레스 상태/파라미터 private var isLongPressTriggered = false @@ -1881,18 +1882,13 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB private val longPressDurationMs = 2000L private val centerHeartShowDelayMs = 500L - private val longPressScaleDurationMs get() = longPressDurationMs - private val longPressScaleStart = 1.0f - private val longPressScaleMax = 4.0f - - // 0.5초 후 중앙 하트 표시 및 스케일 업데이트 시작을 위한 러너블 + // 0.5초 후 중앙 하트 표시 및 물 채우기(progress) 업데이트 시작을 위한 러너블 private val showCenterHeartRunnable = Runnable { if (!isTrackingLongPress) return@Runnable if (isLongPressBlockedByAvailability) return@Runnable if (longPressCenterHeart != null) return@Runnable showCenterHeartForLongPress() longPressVisualStartTime = System.currentTimeMillis() - updateCenterHeartScale(longPressScaleStart) handler.post(longPressScaleUpdateRunnable) } @@ -1905,18 +1901,29 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB if (isLongPressBlockedByAvailability) return val elapsed = System.currentTimeMillis() - pressStartTime - // 스케일은 더 빠르게 커지도록 별도의 duration 기준으로 계산(최대치 도달 후 유지) - val scaleFraction = (elapsed.coerceAtMost(longPressScaleDurationMs) - .toFloat() / longPressScaleDurationMs.toFloat()) - val scale = - longPressScaleStart + (longPressScaleMax - longPressScaleStart) * scaleFraction - updateCenterHeartScale(scale) + // 경과 시간 기준으로 물 채우기(progress) 계산 (0..100) + val progressFraction = (elapsed.coerceAtMost(longPressDurationMs) + .toFloat() / longPressDurationMs.toFloat()) + val progress = (progressFraction * 100f).toInt() + longPressCenterHeart?.let { heartView -> + try { + heartView.progress = progress + } catch (_: Throwable) { + // 일부 버전에서 setter 명이 다를 수 있어 안전 처리 + } + } // 2초 유지 시 트리거 실행 if (elapsed >= longPressDurationMs && !isLongPressTriggered) { isLongPressTriggered = true isTrackingLongPress = false - removeCenterHeartForLongPress(withFade = true) + // 최종 100% 보정 + longPressCenterHeart?.let { hv -> + try { + hv.progress = 100 + } catch (_: Throwable) { + } + } triggerBigHeartDonation() return } @@ -1994,26 +2001,22 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB private fun showCenterHeartForLongPress() { if (longPressCenterHeart != null) return - val size = 33.3f.dpToPx().toInt() - val lp = FrameLayout.LayoutParams(size, size).apply { gravity = Gravity.CENTER } - val heart = ImageView(this).apply { - setImageResource(R.drawable.ic_heart_pink) - layoutParams = lp - scaleX = 1f - scaleY = 1f - alpha = 1f + val heart = binding.heartWave + heart.setBorderColor("#ff959a".toColorInt()) + heart.setFrontWaveColor("#ff959a".toColorInt()) + heart.setBehindWaveColor("#ff959a".toColorInt()) + val elapsed = System.currentTimeMillis() - pressStartTime + val progress = ((elapsed.coerceAtMost(longPressDurationMs) + .toFloat() / longPressDurationMs.toFloat()) * 100f).toInt() + try { + heart.progress = progress + } catch (_: Throwable) { } - binding.flRoot.addView(heart) + heart.alpha = 1f + heart.visibility = View.VISIBLE longPressCenterHeart = heart } - private fun updateCenterHeartScale(scale: Float) { - longPressCenterHeart?.let { - it.scaleX = scale - it.scaleY = scale - } - } - private fun removeCenterHeartForLongPress(withFade: Boolean = false) { val heart = longPressCenterHeart ?: return longPressCenterHeart = null @@ -2021,10 +2024,21 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB heart.animate() .alpha(0f) .setDuration(150L) - .withEndAction { binding.flRoot.removeView(heart) } + .withEndAction { + heart.visibility = View.GONE + try { + heart.progress = 0 + } catch (_: Throwable) { + } + } .start() } else { - binding.flRoot.removeView(heart) + heart.alpha = 1f + heart.visibility = View.GONE + try { + heart.progress = 0 + } catch (_: Throwable) { + } } } diff --git a/app/src/main/res/layout/activity_live_room.xml b/app/src/main/res/layout/activity_live_room.xml index 21734c7b..cbad04bd 100644 --- a/app/src/main/res/layout/activity_live_room.xml +++ b/app/src/main/res/layout/activity_live_room.xml @@ -758,4 +758,17 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + +