feat: enhance versioning and avatar handling with dynamic properties and improved UI interactions
This commit is contained in:
@@ -335,6 +335,7 @@ fun AppleEmojiText(
|
||||
overflow: android.text.TextUtils.TruncateAt? = null,
|
||||
linkColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color(0xFF54A9EB), // Telegram-style blue
|
||||
enableLinks: Boolean = true, // 🔥 Включить кликабельные ссылки
|
||||
onClick: (() -> Unit)? = null, // 🔥 Обычный tap (selection mode в MessageBubble)
|
||||
onLongClick: (() -> Unit)? = null // 🔥 Callback для long press (selection в MessageBubble)
|
||||
) {
|
||||
val fontSizeValue = if (fontSize == androidx.compose.ui.unit.TextUnit.Unspecified) 15f
|
||||
@@ -370,9 +371,12 @@ fun AppleEmojiText(
|
||||
setLinkColor(linkColor.toArgb())
|
||||
enableClickableLinks(true, onLongClick)
|
||||
} else {
|
||||
// 🔥 Даже без ссылок поддерживаем long press
|
||||
onLongClickCallback = onLongClick
|
||||
// 🔥 ВАЖНО: в selection mode полностью отключаем LinkMovementMethod,
|
||||
// иначе тапы по тексту могут "съедаться" и не доходить до onClick.
|
||||
enableClickableLinks(false, onLongClick)
|
||||
}
|
||||
// 🔥 Поддержка обычного tap (например, для selection mode)
|
||||
setOnClickListener(onClick?.let { click -> View.OnClickListener { click.invoke() } })
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
@@ -389,9 +393,12 @@ fun AppleEmojiText(
|
||||
view.setLinkColor(linkColor.toArgb())
|
||||
view.enableClickableLinks(true, onLongClick)
|
||||
} else {
|
||||
// 🔥 Даже без ссылок поддерживаем long press
|
||||
view.onLongClickCallback = onLongClick
|
||||
// 🔥 ВАЖНО: в selection mode полностью отключаем LinkMovementMethod,
|
||||
// иначе тапы по тексту могут "съедаться" и не доходить до onClick.
|
||||
view.enableClickableLinks(false, onLongClick)
|
||||
}
|
||||
// 🔥 Обновляем tap callback, чтобы не было stale lambda
|
||||
view.setOnClickListener(onClick?.let { click -> View.OnClickListener { click.invoke() } })
|
||||
},
|
||||
modifier = modifier
|
||||
)
|
||||
|
||||
@@ -152,7 +152,10 @@ private fun isCenteredTopCutout(
|
||||
if (notchInfo == null || notchInfo.bounds.width() <= 0f || notchInfo.bounds.height() <= 0f) {
|
||||
return false
|
||||
}
|
||||
val tolerancePx = screenWidthPx * 0.20f
|
||||
if (notchInfo.gravity != Gravity.CENTER) {
|
||||
return false
|
||||
}
|
||||
val tolerancePx = screenWidthPx * 0.10f
|
||||
return abs(notchInfo.bounds.centerX() - screenWidthPx / 2f) <= tolerancePx
|
||||
}
|
||||
|
||||
@@ -211,7 +214,6 @@ private fun computeAvatarState(
|
||||
avatarSizeMinPx: Float, // Into notch size (24dp or notch width)
|
||||
hasAvatar: Boolean,
|
||||
// Notch info
|
||||
notchCenterX: Float, // X position of front camera/notch
|
||||
notchCenterY: Float,
|
||||
notchRadiusPx: Float,
|
||||
// Telegram thresholds in pixels
|
||||
@@ -265,21 +267,8 @@ private fun computeAvatarState(
|
||||
val isDrawing = radius <= dp40
|
||||
val isNear = radius <= dp32
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CENTER X - animate towards notch/camera position when collapsing
|
||||
// Normal: screen center, Collapsed: notch center (front camera)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
val startX = screenWidthPx / 2f // Normal position = screen center
|
||||
val endX = notchCenterX // Target = front camera position
|
||||
|
||||
val centerX: Float = when {
|
||||
// Pull-down expansion - stay at screen center
|
||||
hasAvatar && expansionProgress > 0f -> screenWidthPx / 2f
|
||||
// Collapsing - animate X towards notch/camera
|
||||
collapseProgress > 0f -> lerpFloat(endX, startX, diff)
|
||||
// Normal state - screen center
|
||||
else -> screenWidthPx / 2f
|
||||
}
|
||||
// Always lock X to screen center to avoid OEM cutout offset issues on some devices.
|
||||
val centerX: Float = screenWidthPx / 2f
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CENTER Y - Telegram: avatarY = lerp(endY, startY, diff)
|
||||
@@ -450,17 +439,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, hasCenteredNotch) {
|
||||
if (hasCenteredNotch && notchInfo != null) {
|
||||
// Centered notch (like Dynamic Island or punch-hole camera)
|
||||
notchInfo.bounds.centerX()
|
||||
} else {
|
||||
// No notch or off-center notch - always use screen center
|
||||
screenWidthPx / 2f
|
||||
}
|
||||
}
|
||||
// Always use true screen center for X target to keep droplet perfectly centered.
|
||||
val notchCenterX = screenWidthPx / 2f
|
||||
|
||||
val notchCenterY = remember(notchInfo, hasCenteredNotch, statusBarHeightPx) {
|
||||
resolveSafeNotchCenterY(
|
||||
@@ -493,7 +473,6 @@ fun ProfileMetaballOverlay(
|
||||
avatarSizeExpandedPx = avatarSizeExpandedPx,
|
||||
avatarSizeMinPx = avatarSizeMinPx,
|
||||
hasAvatar = hasAvatar,
|
||||
notchCenterX = notchCenterX,
|
||||
notchCenterY = notchCenterY,
|
||||
notchRadiusPx = notchRadiusPx,
|
||||
dp40 = dp40,
|
||||
@@ -596,19 +575,13 @@ fun ProfileMetaballOverlay(
|
||||
// Draw target shape at top (notch or black bar fallback)
|
||||
if (showConnector) {
|
||||
blackPaint.alpha = connectorPaintAlpha
|
||||
if (hasRealNotch && notchInfo != null && notchInfo.isLikelyCircle) {
|
||||
if (hasRealNotch && notchInfo != null) {
|
||||
nativeCanvas.drawCircle(
|
||||
notchCenterX,
|
||||
notchCenterY,
|
||||
notchRadiusPx,
|
||||
blackPaint
|
||||
)
|
||||
} else if (hasRealNotch && notchInfo != null && notchInfo.isAccurate && notchInfo.path != null) {
|
||||
nativeCanvas.drawPath(notchInfo.path, blackPaint)
|
||||
} else if (hasRealNotch && notchInfo != null) {
|
||||
val bounds = notchInfo.bounds
|
||||
val rad = kotlin.math.max(bounds.width(), bounds.height()) / 2f
|
||||
nativeCanvas.drawRoundRect(bounds, rad, rad, blackPaint)
|
||||
} else {
|
||||
// No notch fallback: full-width black bar at top
|
||||
// Like Telegram's ProfileGooeyView when notchInfo == null
|
||||
@@ -798,7 +771,6 @@ fun ProfileMetaballOverlayCompat(
|
||||
avatarSizeExpandedPx = avatarSizeExpandedPx,
|
||||
avatarSizeMinPx = avatarSizeMinPx,
|
||||
hasAvatar = hasAvatar,
|
||||
notchCenterX = notchCenterX,
|
||||
notchCenterY = notchCenterY,
|
||||
notchRadiusPx = notchRadiusPx,
|
||||
dp40 = dp40,
|
||||
@@ -943,9 +915,8 @@ fun ProfileMetaballOverlayCpu(
|
||||
with(density) { ProfileMetaballConstants.FALLBACK_CAMERA_SIZE.toPx() }
|
||||
}
|
||||
}
|
||||
val notchCenterX = remember(notchInfo, screenWidthPx) {
|
||||
if (hasRealNotch && notchInfo != null) notchInfo.bounds.centerX() else screenWidthPx / 2f
|
||||
}
|
||||
// Always use true screen center for X target to keep droplet perfectly centered.
|
||||
val notchCenterX = screenWidthPx / 2f
|
||||
val notchCenterY = remember(notchInfo, hasRealNotch, statusBarHeightPx, blackBarHeightPx) {
|
||||
resolveSafeNotchCenterY(
|
||||
notchInfo = notchInfo,
|
||||
@@ -973,7 +944,6 @@ fun ProfileMetaballOverlayCpu(
|
||||
avatarSizeExpandedPx = avatarSizeExpandedPx,
|
||||
avatarSizeMinPx = avatarSizeMinPx,
|
||||
hasAvatar = hasAvatar,
|
||||
notchCenterX = notchCenterX,
|
||||
notchCenterY = notchCenterY,
|
||||
notchRadiusPx = notchRadiusPx,
|
||||
dp40 = dp40, dp34 = dp34, dp32 = dp32, dp18 = dp18, dp22 = dp22,
|
||||
@@ -1081,19 +1051,13 @@ fun ProfileMetaballOverlayCpu(
|
||||
// Draw target (notch or black bar)
|
||||
if (showConnector) {
|
||||
blackPaint.alpha = connectorPaintAlpha
|
||||
if (hasRealNotch && notchInfo != null && notchInfo.isLikelyCircle) {
|
||||
val rad = min(notchInfo.bounds.width(), notchInfo.bounds.height()) / 2f
|
||||
if (hasRealNotch && notchInfo != null) {
|
||||
offscreenCanvas.drawCircle(
|
||||
notchInfo.bounds.centerX(),
|
||||
notchInfo.bounds.bottom - notchInfo.bounds.width() / 2f,
|
||||
rad, blackPaint
|
||||
notchCenterX,
|
||||
notchCenterY,
|
||||
notchRadiusPx,
|
||||
blackPaint
|
||||
)
|
||||
} else if (hasRealNotch && notchInfo != null && notchInfo.isAccurate && notchInfo.path != null) {
|
||||
offscreenCanvas.drawPath(notchInfo.path, blackPaint)
|
||||
} else if (hasRealNotch && notchInfo != null) {
|
||||
val bounds = notchInfo.bounds
|
||||
val rad = kotlin.math.max(bounds.width(), bounds.height()) / 2f
|
||||
offscreenCanvas.drawRoundRect(bounds, rad, rad, blackPaint)
|
||||
} else {
|
||||
// No notch: draw black bar
|
||||
offscreenCanvas.drawRect(0f, 0f, screenWidthPx, blackBarHeightPx, blackPaint)
|
||||
|
||||
Reference in New Issue
Block a user