Skip to content

Commit 4344498

Browse files
committed
feat: enhance ScenariosActivity with immersive mode and compass
This commit introduces several enhancements to the `ScenariosActivity`: - **Immersive Mode:** The activity now hides system UI bars (status and navigation) for a more immersive experience. The screen is also kept on. - **Whiskey Compass:** A compass UI element (`WhiskeyCompass`) is added to the map view. - The compass's alpha (transparency) fades in when the camera heading changes and fades out after a period of inactivity. - **Conditional Top App Bar:** The top app bar is now only displayed when no specific scenario is active (i.e., on the scenario selection screen). - Minor UI adjustments for the close button.
1 parent 7756930 commit 4344498

1 file changed

Lines changed: 84 additions & 11 deletions

File tree

  • Maps3DSamples/advanced/app/src/main/java/com/example/advancedmaps3dsamples/scenarios

Maps3DSamples/advanced/app/src/main/java/com/example/advancedmaps3dsamples/scenarios/ScenariosActivity.kt

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ package com.example.advancedmaps3dsamples.scenarios
1616

1717
import android.content.Intent
1818
import android.os.Bundle
19+
import android.view.WindowManager
1920
import androidx.activity.ComponentActivity
21+
import androidx.activity.SystemBarStyle
2022
import androidx.activity.compose.BackHandler
2123
import androidx.activity.compose.setContent
2224
import androidx.activity.enableEdgeToEdge
2325
import androidx.activity.viewModels
26+
import androidx.compose.animation.core.Animatable
27+
import androidx.compose.animation.core.LinearEasing
28+
import androidx.compose.animation.core.tween
2429
import androidx.compose.foundation.background
2530
import androidx.compose.foundation.layout.Arrangement
2631
import androidx.compose.foundation.layout.Box
@@ -32,6 +37,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
3237
import androidx.compose.foundation.layout.height
3338
import androidx.compose.foundation.layout.offset
3439
import androidx.compose.foundation.layout.padding
40+
import androidx.compose.foundation.layout.safeDrawingPadding
3541
import androidx.compose.foundation.layout.size
3642
import androidx.compose.foundation.shape.CircleShape
3743
import androidx.compose.material.icons.Icons
@@ -46,21 +52,30 @@ import androidx.compose.material3.ExperimentalMaterial3Api
4652
import androidx.compose.material3.Icon
4753
import androidx.compose.material3.MaterialTheme
4854
import androidx.compose.material3.Scaffold
55+
import androidx.compose.material3.SnackbarHostState
4956
import androidx.compose.material3.Text
5057
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
5158
import androidx.compose.runtime.Composable
59+
import androidx.compose.runtime.LaunchedEffect
5260
import androidx.compose.runtime.getValue
5361
import androidx.compose.runtime.remember
62+
import androidx.compose.runtime.rememberCoroutineScope
5463
import androidx.compose.ui.Alignment
5564
import androidx.compose.ui.Modifier
65+
import androidx.compose.ui.draw.alpha
5666
import androidx.compose.ui.graphics.Color
67+
import androidx.compose.ui.graphics.rememberGraphicsLayer
5768
import androidx.compose.ui.graphics.vector.ImageVector
5869
import androidx.compose.ui.res.stringResource
5970
import androidx.compose.ui.text.style.TextAlign
6071
import androidx.compose.ui.tooling.preview.Preview
6172
import androidx.compose.ui.unit.dp
73+
import androidx.core.view.WindowCompat
74+
import androidx.core.view.WindowInsetsCompat
75+
import androidx.core.view.WindowInsetsControllerCompat
6276
import androidx.lifecycle.compose.collectAsStateWithLifecycle
6377
import com.example.advancedmaps3dsamples.R
78+
import com.example.advancedmaps3dsamples.ainavigator.WhiskeyCompass
6479
import com.example.advancedmaps3dsamples.ui.theme.AdvancedMaps3DSamplesTheme
6580
import com.example.advancedmaps3dsamples.utils.DEFAULT_ROLL
6681
import com.example.advancedmaps3dsamples.utils.toHeading
@@ -69,7 +84,10 @@ import com.example.advancedmaps3dsamples.utils.toRoll
6984
import com.example.advancedmaps3dsamples.utils.toTilt
7085
import com.google.android.gms.maps3d.model.Camera
7186
import dagger.hilt.android.AndroidEntryPoint
87+
import kotlinx.coroutines.delay
7288
import kotlinx.coroutines.flow.map
89+
import kotlinx.coroutines.launch
90+
import kotlin.time.Duration.Companion.seconds
7391

7492
@AndroidEntryPoint
7593
@OptIn(ExperimentalMaterial3Api::class)
@@ -85,7 +103,14 @@ class ScenariosActivity : ComponentActivity() {
85103

86104
override fun onCreate(savedInstanceState: Bundle?) {
87105
super.onCreate(savedInstanceState)
88-
enableEdgeToEdge()
106+
enableEdgeToEdge(
107+
statusBarStyle = SystemBarStyle.light(android.graphics.Color.TRANSPARENT, android.graphics.Color.TRANSPARENT),
108+
navigationBarStyle = SystemBarStyle.light(android.graphics.Color.TRANSPARENT, android.graphics.Color.TRANSPARENT)
109+
)
110+
111+
hideSystemUI()
112+
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
113+
89114
setContent {
90115
val viewState by viewModel.viewState.collectAsStateWithLifecycle()
91116
val currentCamera by viewModel.currentCamera.collectAsStateWithLifecycle(Camera.DEFAULT_CAMERA)
@@ -95,21 +120,47 @@ class ScenariosActivity : ComponentActivity() {
95120

96121
val cameraAttribute by viewModel.trackedAttribute.collectAsStateWithLifecycle()
97122

123+
val camera by viewModel.currentCamera.collectAsStateWithLifecycle()
124+
val compassAlpha = remember { Animatable(0.55f) }
125+
126+
// This LaunchedEffect controls the compass alpha based on camera heading changes.
127+
LaunchedEffect(camera.heading) {
128+
// When camera.heading changes, this LaunchedEffect is cancelled and restarted.
129+
// Any coroutine launched within its scope (like the one below) is also cancelled.
130+
131+
// Reset alpha to initial state and stop any ongoing animation on compassAlpha.
132+
compassAlpha.snapTo(0.55f)
133+
134+
// Launch a new coroutine within this LaunchedEffect's scope.
135+
// This coroutine will handle the delay and subsequent fade-out animation.
136+
// If camera.heading changes again before this completes, this coroutine will be cancelled.
137+
launch {
138+
delay(2.seconds) // Wait for 2 seconds of stable heading
139+
// If this point is reached, it means camera.heading was stable for 2 seconds.
140+
compassAlpha.animateTo(
141+
targetValue = 0.3f,
142+
animationSpec = tween(durationMillis = 500, easing = LinearEasing)
143+
)
144+
}
145+
}
146+
98147
AdvancedMaps3DSamplesTheme(
99148
dynamicColor = false
100149
) {
101150
Scaffold(
102151
modifier = Modifier.fillMaxSize(),
103152
topBar = {
104-
CenterAlignedTopAppBar(
105-
colors = topAppBarColors(
106-
containerColor = MaterialTheme.colorScheme.primaryContainer,
107-
titleContentColor = MaterialTheme.colorScheme.primary,
108-
),
109-
title = {
110-
Text(stringResource(viewState.scenario?.titleId ?: R.string.scenarios_none))
111-
}
112-
)
153+
if (viewState.scenario == null) {
154+
CenterAlignedTopAppBar(
155+
colors = topAppBarColors(
156+
containerColor = MaterialTheme.colorScheme.primaryContainer,
157+
titleContentColor = MaterialTheme.colorScheme.primary,
158+
),
159+
title = {
160+
Text(stringResource(viewState.scenario?.titleId ?: R.string.scenarios_none))
161+
}
162+
)
163+
}
113164
}
114165
) { innerPadding ->
115166
val modifier = Modifier.padding(innerPadding)
@@ -135,6 +186,17 @@ class ScenariosActivity : ComponentActivity() {
135186
onMap3dViewReady = { viewModel.setGoogleMap3D(it) },
136187
onReleaseMap = { viewModel.releaseGoogleMap3D() },
137188
)
189+
190+
WhiskeyCompass(
191+
heading = camera.heading?.toFloat() ?: 0f,
192+
modifier = Modifier
193+
.fillMaxWidth()
194+
.alpha(compassAlpha.value)
195+
.safeDrawingPadding(),
196+
stripHeight = 90.dp,
197+
pixelsPerDegree = 7f,
198+
degreeLabelInterval = 30,
199+
)
138200
}
139201

140202
if (viewState.countDownVisible) {
@@ -165,6 +227,14 @@ class ScenariosActivity : ComponentActivity() {
165227
}
166228
}
167229
}
230+
231+
private fun hideSystemUI() {
232+
WindowCompat.setDecorFitsSystemWindows(window, false)
233+
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
234+
controller.hide(WindowInsetsCompat.Type.systemBars())
235+
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
236+
}
237+
}
168238
}
169239

170240
/**
@@ -256,7 +326,10 @@ fun FinishedOverlay(
256326
) {
257327
// Close Button at the top end corner
258328
OverlayButton(
259-
modifier = modifier.align(Alignment.TopEnd).offset(x = (-16).dp, y = 16.dp).size(size),
329+
modifier = modifier
330+
.align(Alignment.TopEnd)
331+
.offset(x = (-16).dp, y = 16.dp)
332+
.size(size),
260333
onExitClick = onCloseClick,
261334
imageVector = Icons.Default.Close,
262335
contentDescription = stringResource(R.string.close)

0 commit comments

Comments
 (0)