feat: Add fallback method for notch info retrieval using DisplayCutout API
This commit is contained in:
@@ -217,7 +217,7 @@ fun ChatsListScreen(
|
|||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update status bar and completely hide navigation bar
|
// Update status bar appearance
|
||||||
LaunchedEffect(isDarkTheme) {
|
LaunchedEffect(isDarkTheme) {
|
||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
val window = (view.context as android.app.Activity).window
|
val window = (view.context as android.app.Activity).window
|
||||||
@@ -228,13 +228,11 @@ fun ChatsListScreen(
|
|||||||
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
||||||
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
|
||||||
// Completely hide navigation bar
|
// Navigation bar — keep visible, match theme
|
||||||
insetsController.hide(
|
insetsController.show(
|
||||||
androidx.core.view.WindowInsetsCompat.Type.navigationBars()
|
androidx.core.view.WindowInsetsCompat.Type.navigationBars()
|
||||||
)
|
)
|
||||||
insetsController.systemBarsBehavior =
|
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
|
||||||
androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.graphics.Path
|
|||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
import android.view.View
|
||||||
import androidx.core.graphics.PathParser
|
import androidx.core.graphics.PathParser
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,4 +129,56 @@ object NotchInfoUtils {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback: get notch info from DisplayCutout API (Android P+).
|
||||||
|
* Works on Pixel 7 Pro and other devices that don't expose config_mainBuiltInDisplayCutout.
|
||||||
|
* Requires a View that is attached to the window.
|
||||||
|
*/
|
||||||
|
fun getInfoFromCutout(view: View): NotchInfo? {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return null
|
||||||
|
try {
|
||||||
|
val insets = view.rootWindowInsets ?: return null
|
||||||
|
val cutout = insets.displayCutout ?: return null
|
||||||
|
val rects = cutout.boundingRects
|
||||||
|
if (rects.isEmpty()) return null
|
||||||
|
|
||||||
|
val displayWidth = view.resources.displayMetrics.widthPixels
|
||||||
|
val density = view.resources.displayMetrics.density
|
||||||
|
|
||||||
|
// Find the top cutout (front camera / punch-hole)
|
||||||
|
val topRect = rects.firstOrNull { it.top == 0 || it.top < it.height() * 2 }
|
||||||
|
?: rects.first()
|
||||||
|
|
||||||
|
val bounds = RectF(
|
||||||
|
topRect.left.toFloat(),
|
||||||
|
topRect.top.toFloat(),
|
||||||
|
topRect.right.toFloat(),
|
||||||
|
topRect.bottom.toFloat()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Determine gravity
|
||||||
|
val centerX = bounds.centerX()
|
||||||
|
val gravity = when {
|
||||||
|
kotlin.math.abs(centerX - displayWidth / 2f) <= 4 * density -> Gravity.CENTER
|
||||||
|
centerX < displayWidth / 4f -> Gravity.START
|
||||||
|
centerX > displayWidth * 3f / 4f -> Gravity.END
|
||||||
|
else -> Gravity.CENTER // punch-hole near center
|
||||||
|
}
|
||||||
|
|
||||||
|
val dp32 = 32 * density
|
||||||
|
val isLikelyCircle = bounds.width() <= dp32 || bounds.width() <= bounds.height()
|
||||||
|
|
||||||
|
return NotchInfo(
|
||||||
|
gravity = gravity,
|
||||||
|
isAccurate = false, // no path data, just bounding rect
|
||||||
|
isLikelyCircle = isLikelyCircle,
|
||||||
|
path = null,
|
||||||
|
bounds = bounds,
|
||||||
|
rawPath = "cutout:${topRect}"
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import androidx.compose.ui.graphics.nativeCanvas
|
|||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -364,6 +365,7 @@ fun ProfileMetaballOverlay(
|
|||||||
avatarContent: @Composable BoxScope.() -> Unit = {},
|
avatarContent: @Composable BoxScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val view = LocalView.current
|
||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
val screenWidth = configuration.screenWidthDp.dp
|
val screenWidth = configuration.screenWidthDp.dp
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
@@ -379,11 +381,14 @@ fun ProfileMetaballOverlay(
|
|||||||
val avatarSizeMinPx = with(density) { ProfileMetaballConstants.AVATAR_SIZE_MIN.toPx() }
|
val avatarSizeMinPx = with(density) { ProfileMetaballConstants.AVATAR_SIZE_MIN.toPx() }
|
||||||
|
|
||||||
// Get REAL camera/notch info from system (like Telegram does)
|
// Get REAL camera/notch info from system (like Telegram does)
|
||||||
val notchInfo = remember { NotchInfoUtils.getInfo(context) }
|
// Try resource-based first, fallback to DisplayCutout API (for Pixel 7 Pro etc.)
|
||||||
|
val notchInfo = remember(view) {
|
||||||
|
NotchInfoUtils.getInfo(context) ?: NotchInfoUtils.getInfoFromCutout(view)
|
||||||
|
}
|
||||||
|
|
||||||
// Debug log notch info once
|
// Debug log notch info once
|
||||||
LaunchedEffect(notchInfo, screenWidthPx, statusBarHeightPx, headerHeightPx) {
|
LaunchedEffect(notchInfo, screenWidthPx, statusBarHeightPx, headerHeightPx) {
|
||||||
Log.d(TAG, "NotchInfo: gravity=${notchInfo?.gravity}, isCircle=${notchInfo?.isLikelyCircle}, bounds=${notchInfo?.bounds}")
|
Log.d(TAG, "NotchInfo: gravity=${notchInfo?.gravity}, isCircle=${notchInfo?.isLikelyCircle}, bounds=${notchInfo?.bounds}, raw=${notchInfo?.rawPath}")
|
||||||
Log.d(TAG, "Screen: width=${screenWidthPx}px, statusBar=${statusBarHeightPx}px, header=${headerHeightPx}px")
|
Log.d(TAG, "Screen: width=${screenWidthPx}px, statusBar=${statusBarHeightPx}px, header=${headerHeightPx}px")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,6 +850,7 @@ fun ProfileMetaballOverlayCpu(
|
|||||||
avatarContent: @Composable BoxScope.() -> Unit = {},
|
avatarContent: @Composable BoxScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val view = LocalView.current
|
||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
val screenWidth = configuration.screenWidthDp.dp
|
val screenWidth = configuration.screenWidthDp.dp
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
@@ -856,7 +862,10 @@ fun ProfileMetaballOverlayCpu(
|
|||||||
val avatarSizeMinPx = with(density) { ProfileMetaballConstants.AVATAR_SIZE_MIN.toPx() }
|
val avatarSizeMinPx = with(density) { ProfileMetaballConstants.AVATAR_SIZE_MIN.toPx() }
|
||||||
|
|
||||||
// Get notch info (debug override supported)
|
// Get notch info (debug override supported)
|
||||||
val notchInfo = remember { NotchInfoUtils.getInfo(context) }
|
// Try resource-based first, fallback to DisplayCutout API (for Pixel 7 Pro etc.)
|
||||||
|
val notchInfo = remember(view) {
|
||||||
|
NotchInfoUtils.getInfo(context) ?: NotchInfoUtils.getInfoFromCutout(view)
|
||||||
|
}
|
||||||
val hasRealNotch = !MetaballDebug.forceNoNotch &&
|
val hasRealNotch = !MetaballDebug.forceNoNotch &&
|
||||||
notchInfo != null && notchInfo.gravity == Gravity.CENTER && notchInfo.bounds.width() > 0
|
notchInfo != null && notchInfo.gravity == Gravity.CENTER && notchInfo.bounds.width() > 0
|
||||||
val blackBarHeightPx = with(density) { BLACK_BAR_HEIGHT_DP.dp.toPx() }
|
val blackBarHeightPx = with(density) { BLACK_BAR_HEIGHT_DP.dp.toPx() }
|
||||||
@@ -1231,6 +1240,24 @@ fun MetaballDebugPanel(modifier: Modifier = Modifier) {
|
|||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
fontWeight = FontWeight.Medium
|
fontWeight = FontWeight.Medium
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Notch detection info
|
||||||
|
val view = LocalView.current
|
||||||
|
val notchRes = remember { NotchInfoUtils.getInfo(context) }
|
||||||
|
val notchCutout = remember(view) { NotchInfoUtils.getInfoFromCutout(view) }
|
||||||
|
val notchSource = when {
|
||||||
|
notchRes != null -> "resource"
|
||||||
|
notchCutout != null -> "DisplayCutout"
|
||||||
|
else -> "NONE"
|
||||||
|
}
|
||||||
|
val activeNotch = notchRes ?: notchCutout
|
||||||
|
Text(
|
||||||
|
text = "Notch: $notchSource" +
|
||||||
|
if (activeNotch != null) " | ${activeNotch.bounds.width().toInt()}x${activeNotch.bounds.height().toInt()}" +
|
||||||
|
" circle=${activeNotch.isLikelyCircle}" else " (black bar fallback!)",
|
||||||
|
color = if (activeNotch != null) ComposeColor(0xFF4CAF50) else ComposeColor(0xFFFF5722),
|
||||||
|
fontSize = 10.sp
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.rosetta.messenger.baselineprofile
|
package com.rosetta.messenger.baselineprofile
|
||||||
|
|
||||||
import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
|
|
||||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
@@ -31,7 +30,7 @@ class BaselineProfileGenerator {
|
|||||||
val baselineProfileRule = BaselineProfileRule()
|
val baselineProfileRule = BaselineProfileRule()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun generateBaselineProfile() = baselineProfileRule.collectBaselineProfile(
|
fun generateBaselineProfile() = baselineProfileRule.collect(
|
||||||
packageName = "com.rosetta.messenger"
|
packageName = "com.rosetta.messenger"
|
||||||
) {
|
) {
|
||||||
// 1. Cold start — запуск приложения
|
// 1. Cold start — запуск приложения
|
||||||
|
|||||||
Reference in New Issue
Block a user