feat: implement avatar animation and enhance image sharing functionality
This commit is contained in:
@@ -145,6 +145,37 @@ private val GPU_ALPHA_THRESHOLD_MATRIX = floatArrayOf(
|
||||
*/
|
||||
private const val BLACK_BAR_HEIGHT_DP = 32f
|
||||
|
||||
private fun isCenteredTopCutout(
|
||||
notchInfo: NotchInfoUtils.NotchInfo?,
|
||||
screenWidthPx: Float
|
||||
): Boolean {
|
||||
if (notchInfo == null || notchInfo.bounds.width() <= 0f || notchInfo.bounds.height() <= 0f) {
|
||||
return false
|
||||
}
|
||||
val tolerancePx = screenWidthPx * 0.20f
|
||||
return abs(notchInfo.bounds.centerX() - screenWidthPx / 2f) <= tolerancePx
|
||||
}
|
||||
|
||||
private fun resolveSafeNotchCenterY(
|
||||
notchInfo: NotchInfoUtils.NotchInfo?,
|
||||
hasCenteredCutout: Boolean,
|
||||
statusBarHeightPx: Float,
|
||||
fallbackCenterY: Float
|
||||
): Float {
|
||||
if (!hasCenteredCutout || notchInfo == null) return fallbackCenterY
|
||||
|
||||
val rawCenterY = if (notchInfo.isLikelyCircle) {
|
||||
notchInfo.bounds.bottom - min(notchInfo.bounds.width(), notchInfo.bounds.height()) / 2f
|
||||
} else {
|
||||
notchInfo.bounds.centerY()
|
||||
}
|
||||
|
||||
// Some OEM cutout specs report unstable Y; keep collapse target in status-bar safe zone.
|
||||
val minSafeY = max(statusBarHeightPx * 0.45f, fallbackCenterY)
|
||||
val maxSafeY = max(statusBarHeightPx * 0.95f, minSafeY + 1f)
|
||||
return rawCenterY.coerceIn(minSafeY, maxSafeY)
|
||||
}
|
||||
|
||||
/**
|
||||
* State for avatar position and size during animation
|
||||
* Like Telegram's ProfileMetaballView state variables
|
||||
@@ -399,9 +430,13 @@ fun ProfileMetaballOverlay(
|
||||
}
|
||||
}
|
||||
|
||||
val hasCenteredNotch = remember(notchInfo, screenWidthPx) {
|
||||
!MetaballDebug.forceNoNotch && isCenteredTopCutout(notchInfo, screenWidthPx)
|
||||
}
|
||||
|
||||
// Calculate notch radius based on real device notch (like Telegram's ProfileGooeyView)
|
||||
val notchRadiusPx = remember(notchInfo) {
|
||||
if (notchInfo != null && notchInfo.gravity == Gravity.CENTER) {
|
||||
val notchRadiusPx = remember(notchInfo, hasCenteredNotch) {
|
||||
if (hasCenteredNotch && notchInfo != null) {
|
||||
if (notchInfo.isLikelyCircle) {
|
||||
// Circular camera cutout - use actual width/height
|
||||
min(notchInfo.bounds.width(), notchInfo.bounds.height()) / 2f
|
||||
@@ -417,8 +452,8 @@ fun ProfileMetaballOverlay(
|
||||
|
||||
// Notch center position - ONLY use if notch is centered (like front camera)
|
||||
// If notch is off-center (corner notch), use screen center instead
|
||||
val notchCenterX = remember(notchInfo, screenWidthPx) {
|
||||
if (notchInfo != null && notchInfo.gravity == Gravity.CENTER) {
|
||||
val notchCenterX = remember(notchInfo, screenWidthPx, hasCenteredNotch) {
|
||||
if (hasCenteredNotch && notchInfo != null) {
|
||||
// Centered notch (like Dynamic Island or punch-hole camera)
|
||||
notchInfo.bounds.centerX()
|
||||
} else {
|
||||
@@ -427,15 +462,13 @@ fun ProfileMetaballOverlay(
|
||||
}
|
||||
}
|
||||
|
||||
val notchCenterY = remember(notchInfo) {
|
||||
if (notchInfo != null && notchInfo.isLikelyCircle) {
|
||||
// For circle: center is at bottom - width/2 (like Telegram)
|
||||
notchInfo.bounds.bottom - notchInfo.bounds.width() / 2f
|
||||
} else if (notchInfo != null) {
|
||||
notchInfo.bounds.centerY()
|
||||
} else {
|
||||
statusBarHeightPx / 2f
|
||||
}
|
||||
val notchCenterY = remember(notchInfo, hasCenteredNotch, statusBarHeightPx) {
|
||||
resolveSafeNotchCenterY(
|
||||
notchInfo = notchInfo,
|
||||
hasCenteredCutout = hasCenteredNotch,
|
||||
statusBarHeightPx = statusBarHeightPx,
|
||||
fallbackCenterY = statusBarHeightPx / 2f
|
||||
)
|
||||
}
|
||||
|
||||
// Scale Telegram thresholds to our bigger base avatar size (120dp vs 96dp).
|
||||
@@ -492,8 +525,7 @@ fun ProfileMetaballOverlay(
|
||||
val blackBarHeightPx = with(density) { BLACK_BAR_HEIGHT_DP.dp.toPx() }
|
||||
|
||||
// Detect if device has a real centered notch (debug override supported)
|
||||
val hasRealNotch = !MetaballDebug.forceNoNotch &&
|
||||
notchInfo != null && notchInfo.gravity == Gravity.CENTER && notchInfo.bounds.width() > 0
|
||||
val hasRealNotch = hasCenteredNotch
|
||||
|
||||
// Calculate "v" parameter - thickness of connector based on distance
|
||||
val distance = avatarState.centerY - notchCenterY
|
||||
@@ -895,8 +927,9 @@ fun ProfileMetaballOverlayCpu(
|
||||
val notchInfo = remember(view) {
|
||||
NotchInfoUtils.getInfo(context) ?: NotchInfoUtils.getInfoFromCutout(view)
|
||||
}
|
||||
val hasRealNotch = !MetaballDebug.forceNoNotch &&
|
||||
notchInfo != null && notchInfo.gravity == Gravity.CENTER && notchInfo.bounds.width() > 0
|
||||
val hasRealNotch = remember(notchInfo, screenWidthPx) {
|
||||
!MetaballDebug.forceNoNotch && isCenteredTopCutout(notchInfo, screenWidthPx)
|
||||
}
|
||||
val blackBarHeightPx = with(density) { BLACK_BAR_HEIGHT_DP.dp.toPx() }
|
||||
|
||||
val notchRadiusPx = remember(notchInfo) {
|
||||
@@ -913,14 +946,13 @@ fun ProfileMetaballOverlayCpu(
|
||||
val notchCenterX = remember(notchInfo, screenWidthPx) {
|
||||
if (hasRealNotch && notchInfo != null) notchInfo.bounds.centerX() else screenWidthPx / 2f
|
||||
}
|
||||
val notchCenterY = remember(notchInfo) {
|
||||
if (hasRealNotch && notchInfo != null && notchInfo.isLikelyCircle) {
|
||||
notchInfo.bounds.bottom - notchInfo.bounds.width() / 2f
|
||||
} else if (hasRealNotch && notchInfo != null) {
|
||||
notchInfo.bounds.centerY()
|
||||
} else {
|
||||
blackBarHeightPx / 2f
|
||||
}
|
||||
val notchCenterY = remember(notchInfo, hasRealNotch, statusBarHeightPx, blackBarHeightPx) {
|
||||
resolveSafeNotchCenterY(
|
||||
notchInfo = notchInfo,
|
||||
hasCenteredCutout = hasRealNotch,
|
||||
statusBarHeightPx = statusBarHeightPx,
|
||||
fallbackCenterY = blackBarHeightPx / 2f
|
||||
)
|
||||
}
|
||||
|
||||
val thresholdScale = (avatarSizeExpandedPx / with(density) { 96.dp.toPx() }).coerceIn(1f, 1.35f)
|
||||
|
||||
Reference in New Issue
Block a user