feat: Add fallback method for notch info retrieval using DisplayCutout API

This commit is contained in:
k1ngsterr1
2026-02-08 20:48:11 +05:00
parent 2d96f6f390
commit aa47a46334
4 changed files with 88 additions and 11 deletions

View File

@@ -217,7 +217,7 @@ fun ChatsListScreen(
focusManager.clearFocus()
}
// Update status bar and completely hide navigation bar
// Update status bar appearance
LaunchedEffect(isDarkTheme) {
if (!view.isInEditMode) {
val window = (view.context as android.app.Activity).window
@@ -228,13 +228,11 @@ fun ChatsListScreen(
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
// Completely hide navigation bar
insetsController.hide(
// Navigation bar — keep visible, match theme
insetsController.show(
androidx.core.view.WindowInsetsCompat.Type.navigationBars()
)
insetsController.systemBarsBehavior =
androidx.core.view.WindowInsetsControllerCompat
.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
}
}

View File

@@ -5,6 +5,7 @@ import android.graphics.Path
import android.graphics.RectF
import android.os.Build
import android.view.Gravity
import android.view.View
import androidx.core.graphics.PathParser
/**
@@ -128,4 +129,56 @@ object NotchInfoUtils {
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
}
}
}

View File

@@ -47,6 +47,7 @@ import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -364,6 +365,7 @@ fun ProfileMetaballOverlay(
avatarContent: @Composable BoxScope.() -> Unit = {},
) {
val context = LocalContext.current
val view = LocalView.current
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val density = LocalDensity.current
@@ -379,11 +381,14 @@ fun ProfileMetaballOverlay(
val avatarSizeMinPx = with(density) { ProfileMetaballConstants.AVATAR_SIZE_MIN.toPx() }
// 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
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")
}
@@ -845,6 +850,7 @@ fun ProfileMetaballOverlayCpu(
avatarContent: @Composable BoxScope.() -> Unit = {},
) {
val context = LocalContext.current
val view = LocalView.current
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val density = LocalDensity.current
@@ -856,7 +862,10 @@ fun ProfileMetaballOverlayCpu(
val avatarSizeMinPx = with(density) { ProfileMetaballConstants.AVATAR_SIZE_MIN.toPx() }
// 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 &&
notchInfo != null && notchInfo.gravity == Gravity.CENTER && notchInfo.bounds.width() > 0
val blackBarHeightPx = with(density) { BLACK_BAR_HEIGHT_DP.dp.toPx() }
@@ -1231,6 +1240,24 @@ fun MetaballDebugPanel(modifier: Modifier = Modifier) {
fontSize = 11.sp,
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
)
}
}

View File

@@ -1,6 +1,5 @@
package com.rosetta.messenger.baselineprofile
import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -31,7 +30,7 @@ class BaselineProfileGenerator {
val baselineProfileRule = BaselineProfileRule()
@Test
fun generateBaselineProfile() = baselineProfileRule.collectBaselineProfile(
fun generateBaselineProfile() = baselineProfileRule.collect(
packageName = "com.rosetta.messenger"
) {
// 1. Cold start — запуск приложения