Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,11 @@

package com.example.maps3d.common

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.example.maps3dcommon.R
import com.google.android.gms.maps3d.GoogleMap3D
import com.google.android.gms.maps3d.model.Map3DMode
Expand Down Expand Up @@ -57,7 +52,7 @@ class Map3DQuickSettingsButton @JvmOverloads constructor(
val map = attachedMap ?: mapProvider?.invoke()
map?.let { updatedSettings.applyTo(it) }
updateTopMarginForUiControls(updatedSettings.uiControls)
applyStatusBarInsetsToSiblingMapView()
requestSiblingMapViewInsets()
onSettingsChangedListener?.onSettingsChanged(updatedSettings)
}

Expand Down Expand Up @@ -99,6 +94,7 @@ class Map3DQuickSettingsButton @JvmOverloads constructor(
private var mapProvider: (() -> GoogleMap3D?)? = null
private var activeDialog: AlertDialog? = null
private var onSettingsChangedListener: OnSettingsChangedListener? = null
private var lastAppliedUiControls: Map3DUiControls? = null

init {
setImageResource(R.drawable.settings_24px)
Expand All @@ -107,7 +103,7 @@ class Map3DQuickSettingsButton @JvmOverloads constructor(

ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
updateTopMarginForUiControls()
applyStatusBarInsetsToSiblingMapView()
requestSiblingMapViewInsets()
insets
}

Expand All @@ -118,39 +114,15 @@ class Map3DQuickSettingsButton @JvmOverloads constructor(

override fun onAttachedToWindow() {
super.onAttachedToWindow()
hideHostSystemBars()
settingsController.addListener(controllerListener)
updateTopMarginForUiControls()
applyStatusBarInsetsToSiblingMapView()
requestSiblingMapViewInsets()
ViewCompat.requestApplyInsets(this)
post {
hideHostSystemBars()
updateTopMarginForUiControls()
}
}

override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
super.onWindowFocusChanged(hasWindowFocus)
if (hasWindowFocus) {
hideHostSystemBars()
}
}

private fun hideHostSystemBars() {
var ctx: Context? = context
while (ctx is ContextWrapper) {
if (ctx is Activity) {
val window = ctx.window
val insetsController = WindowCompat.getInsetsController(window, window.decorView)
insetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
insetsController.hide(WindowInsetsCompat.Type.systemBars())
break
}
ctx = ctx.baseContext
}
}

override fun onDetachedFromWindow() {
activeDialog?.dismiss()
activeDialog = null
Expand All @@ -163,12 +135,9 @@ class Map3DQuickSettingsButton @JvmOverloads constructor(
return parentGroup.findViewById(R.id.map3dView)
}

private fun applyStatusBarInsetsToSiblingMapView() {
private fun requestSiblingMapViewInsets() {
val mapView = findSiblingMapView() ?: return
Map3DSettings.applyStatusBarInsetsToMapView(
mapView = mapView,
explicitStatusBarTop = statusBarTopOffsetPx.takeIf { it > 0 },
)
ViewCompat.requestApplyInsets(mapView)
}

private fun resolveEffectiveStatusBarOffsetPx(): Int {
Expand All @@ -178,12 +147,25 @@ class Map3DQuickSettingsButton @JvmOverloads constructor(
return Map3DSettings.resolveStatusBarOverlapPx(mapView ?: parentGroup)
}

/**
* Re-applies the top margin for the most recently configured [Map3DUiControls] (or the
* active [settingsController] controls if none have been explicitly set yet).
*/
fun updateTopMarginForUiControls() {
val controls = lastAppliedUiControls ?: getSettings().uiControls
applyTopMargin(controls)
}

/**
* Adjusts this button's top margin so it sits directly below the active trailing camera UI
* controls ([Map3DUiControls.zoom], [Map3DUiControls.tilt], [Map3DUiControls.rotate]).
*/
@JvmOverloads
fun updateTopMarginForUiControls(uiControls: Map3DUiControls = getSettings().uiControls) {
fun updateTopMarginForUiControls(uiControls: Map3DUiControls) {
lastAppliedUiControls = uiControls
applyTopMargin(uiControls)
}

private fun applyTopMargin(uiControls: Map3DUiControls) {
val lp = layoutParams as? ViewGroup.MarginLayoutParams ?: return
val density = resources.displayMetrics.density
val baseMarginDp = uiControls.calculateFabMarginDp()
Expand All @@ -203,17 +185,18 @@ class Map3DQuickSettingsButton @JvmOverloads constructor(
fun attachMap(googleMap3D: GoogleMap3D?) {
this.attachedMap = googleMap3D
val currentSettings = settingsController.getSettings()
updateTopMarginForUiControls(currentSettings.uiControls)
updateTopMarginForUiControls()
if (googleMap3D != null) {
currentSettings.applyTo(googleMap3D)
applyStatusBarInsetsToSiblingMapView()
requestSiblingMapViewInsets()
// Re-apply after a short delay to ensure native engine layout pass preserves controls
postDelayed({
if (attachedMap == googleMap3D) {
val latest = settingsController.getSettings()
latest.applyTo(googleMap3D)
updateTopMarginForUiControls(latest.uiControls)
applyStatusBarInsetsToSiblingMapView()
updateTopMarginForUiControls()
requestSiblingMapViewInsets()
onSettingsChangedListener?.onSettingsChanged(latest)
}
}, 350L)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@ package com.example.maps3d.common

import android.content.Context
import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.Button
import android.widget.RadioGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.example.maps3dcommon.R
import com.google.android.gms.maps3d.GoogleMap3D
import com.google.android.gms.maps3d.model.Map3DMode
Expand Down Expand Up @@ -188,18 +184,7 @@ object Map3DQuickSettingsDialog {
dialog.dismiss()
}

dialog.window?.setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
)
dialog.show()
dialog.window?.let { dialogWindow ->
val insetsController = WindowCompat.getInsetsController(dialogWindow, dialogWindow.decorView)
insetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
insetsController.hide(WindowInsetsCompat.Type.systemBars())
dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
return dialog
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@

package com.example.maps3d.common

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -51,7 +48,6 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
Expand All @@ -67,9 +63,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.ViewCompat
import com.example.maps3dcommon.R
import com.google.android.gms.maps3d.GoogleMap3D
import com.google.android.gms.maps3d.model.Map3DMode
Expand All @@ -94,23 +88,9 @@ fun rememberMap3DSessionSettings(
): State<Map3DSettings> {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
var ctx: Context? = view.context
while (ctx is ContextWrapper) {
if (ctx is Activity) {
val window = ctx.window
val insetsController = WindowCompat.getInsetsController(window, window.decorView)
insetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
insetsController.hide(WindowInsetsCompat.Type.systemBars())
break
}
ctx = ctx.baseContext
}
}
LaunchedEffect(Unit) {
Map3DSettings.findMap3DView(view.rootView)?.let { mapView ->
Map3DSettings.applyStatusBarInsetsToMapView(mapView)
ViewCompat.requestApplyInsets(mapView)
}
}
}
Expand Down Expand Up @@ -180,8 +160,8 @@ fun Map3DQuickSettingsWidget(
}
effectiveSettings.applyTo(googleMap3D)
Map3DSettings.findMap3DView(view.rootView)?.let { mapView ->
Map3DSettings.applyStatusBarInsetsToMapView(mapView)
mapView.post { Map3DSettings.applyStatusBarInsetsToMapView(mapView) }
ViewCompat.requestApplyInsets(mapView)
mapView.post { ViewCompat.requestApplyInsets(mapView) }
}
}
}
Expand All @@ -197,7 +177,7 @@ fun Map3DQuickSettingsWidget(
}
effectiveSettings.applyTo(googleMap3D)
Map3DSettings.findMap3DView(view.rootView)?.let { mapView ->
Map3DSettings.applyStatusBarInsetsToMapView(mapView)
ViewCompat.requestApplyInsets(mapView)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ package com.example.maps3d.common

import android.annotation.SuppressLint
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
Expand Down Expand Up @@ -216,11 +213,6 @@ data class Map3DSettings(
return null
}

private data class ControlMarginState(
val baseTopMargin: Int,
val appliedTopMargin: Int,
)

/**
* Resolves the status bar height (in pixels) for the given [view].
*/
Expand Down Expand Up @@ -282,116 +274,6 @@ data class Map3DSettings(
statusBarTop
}
}

/**
* Offsets top-anchored SDK UI control containers (`ControlPlacement.LEADING` and
* `ControlPlacement.TRAILING`) inside [mapView] so the status bar does not overlap them
* when [mapView] is rendered full-screen.
*
* Workaround: [Map3DView] does not yet respect window insets or expose a padding API for
* its internal UI control overlays on edge-to-edge screens. Tracked in
* https://issuetracker.google.com/issues/575850758.
*/
@JvmStatic
@JvmOverloads
fun applyStatusBarInsetsToMapView(
mapView: ViewGroup,
explicitStatusBarTop: Int? = null,
) {
// Workaround: Until Map3DView natively handles window insets or exposes map padding
// (https://issuetracker.google.com/issues/575850758), offset its top-aligned UI
// control containers when the map draws under the status bar.
if (explicitStatusBarTop != null) {
mapView.setTag(
R.id.map3d_status_bar_top_tag,
explicitStatusBarTop.coerceAtLeast(0),
)
}
val effectiveExplicitTop =
explicitStatusBarTop ?: (mapView.getTag(R.id.map3d_status_bar_top_tag) as? Int)
val statusBarOverlap = resolveStatusBarOverlapPx(mapView, effectiveExplicitTop)
ensureRootLayoutListener(mapView)
offsetTopControlContainers(mapView, statusBarOverlap, mapView)
}

private fun ensureRootLayoutListener(rootMapView: ViewGroup) {
if (rootMapView.getTag(R.id.map3d_insets_listener_tag) == true) return
rootMapView.setTag(R.id.map3d_insets_listener_tag, true)

rootMapView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
val cachedTop = rootMapView.getTag(R.id.map3d_status_bar_top_tag) as? Int
val overlap = resolveStatusBarOverlapPx(rootMapView, cachedTop)
offsetTopControlContainers(rootMapView, overlap, rootMapView)
}
}

private fun ensureContainerLayoutListener(container: LinearLayout, rootMapView: ViewGroup) {
if (container.getTag(R.id.map3d_insets_listener_tag) == true) return
container.setTag(R.id.map3d_insets_listener_tag, true)

container.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
val cachedTop = rootMapView.getTag(R.id.map3d_status_bar_top_tag) as? Int
val overlap = resolveStatusBarOverlapPx(rootMapView, cachedTop)
applyMarginToControlContainer(container, overlap)
}
}

private fun applyMarginToControlContainer(
container: LinearLayout,
statusBarOverlap: Int,
) {
val lp = container.layoutParams as? ViewGroup.MarginLayoutParams ?: return
val state = container.getTag(R.id.map3d_base_top_margin_tag) as? ControlMarginState
val baseTopMargin = if (state == null || lp.topMargin != state.appliedTopMargin) {
lp.topMargin
} else {
state.baseTopMargin
}
val targetTopMargin = baseTopMargin + statusBarOverlap.coerceAtLeast(0)
container.setTag(
R.id.map3d_base_top_margin_tag,
ControlMarginState(baseTopMargin, targetTopMargin),
)
if (lp.topMargin != targetTopMargin) {
lp.topMargin = targetTopMargin
container.layoutParams = lp
container.post {
if (container.isAttachedToWindow) {
container.requestLayout()
}
}
}
}

private fun offsetTopControlContainers(
group: ViewGroup,
statusBarOverlap: Int,
rootMapView: ViewGroup,
) {
for (i in 0 until group.childCount) {
val child = group.getChildAt(i) ?: continue
if (child is LinearLayout) {
val lp = child.layoutParams as? ViewGroup.MarginLayoutParams
val isTopAligned = when (lp) {
is FrameLayout.LayoutParams -> {
val vGravity = lp.gravity and Gravity.VERTICAL_GRAVITY_MASK
vGravity != Gravity.BOTTOM && vGravity != Gravity.CENTER_VERTICAL
}
null -> false
else -> !child.isLaidOut || child.top <= group.height / 2
}
if (lp != null && isTopAligned) {
ensureContainerLayoutListener(child, rootMapView)
applyMarginToControlContainer(child, statusBarOverlap)
// Do not recurse into individual control views inside a control container
continue
}
}
if (child is ViewGroup) {
offsetTopControlContainers(child, statusBarOverlap, rootMapView)
}
}
}
}
}
// [END maps_3d_quick_settings_model]
Loading
Loading