Skip to content

Commit 54993ab

Browse files
Merge pull request #19 from amyZepp/haxr_updates
Hello Android XR Updates
2 parents 9d9c0a8 + e7f5aa5 commit 54993ab

File tree

10 files changed

+1139
-112
lines changed

10 files changed

+1139
-112
lines changed

app/src/main/java/com/example/helloandroidxr/MainActivity.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import com.example.helloandroidxr.ui.HelloAndroidXRApp
2424
import com.example.helloandroidxr.ui.theme.HelloAndroidXRTheme
2525

2626
class MainActivity : ComponentActivity() {
27-
2827
override fun onCreate(savedInstanceState: Bundle?) {
2928
super.onCreate(savedInstanceState)
3029
enableEdgeToEdge()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.helloandroidxr.bugdroid
18+
19+
import android.annotation.SuppressLint
20+
import android.content.Context
21+
import android.util.Log
22+
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.mutableStateOf
24+
import androidx.compose.runtime.setValue
25+
import androidx.xr.runtime.Session
26+
import androidx.xr.scenecore.GltfModel
27+
import com.example.helloandroidxr.R
28+
import kotlinx.coroutines.CoroutineScope
29+
import kotlinx.coroutines.launch
30+
import java.io.InputStream
31+
32+
class BugdroidController(
33+
private val xrSession: Session?,
34+
private val context: Context,
35+
private val coroutineScope: CoroutineScope
36+
) {
37+
var gltfModel by mutableStateOf<GltfModel?>(null)
38+
39+
init {
40+
loadBugdroidModel()
41+
}
42+
43+
private fun loadBugdroidModel() {
44+
coroutineScope.launch {
45+
gltfModel = BugdroidGltfModelCache.getOrLoadModel(xrSession, context)
46+
}
47+
}
48+
}
49+
50+
private object BugdroidGltfModelCache {
51+
private var cachedModel: GltfModel? = null
52+
@SuppressLint("RestrictedApi")
53+
suspend fun getOrLoadModel(
54+
xrCoreSession: Session?, context: Context
55+
): GltfModel? {
56+
xrCoreSession ?: run {
57+
Log.w(TAG, "Cannot load model, session is null.")
58+
return null
59+
}
60+
return if (cachedModel == null) {
61+
try {
62+
val inputStream: InputStream =
63+
context.resources.openRawResource(R.raw.bugdroid_animated_wave)
64+
cachedModel = GltfModel.create(
65+
xrCoreSession, inputStream.readBytes(), "BUGDROID"
66+
)
67+
cachedModel
68+
} catch (e: Exception) {
69+
Log.e(TAG, "Error loading GLTF model", e)
70+
null
71+
}
72+
} else {
73+
cachedModel
74+
}
75+
}
76+
77+
fun clearCache() {
78+
cachedModel = null
79+
}
80+
81+
const val TAG = "BugdroidGltfModelCache"
82+
}

app/src/main/java/com/example/helloandroidxr/ui/HelloAndroidXRApp.kt

Lines changed: 167 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,9 @@ import androidx.compose.material3.Text
4343
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
4444
import androidx.compose.runtime.Composable
4545
import androidx.compose.runtime.LaunchedEffect
46+
import androidx.compose.runtime.collectAsState
4647
import androidx.compose.runtime.getValue
47-
import androidx.compose.runtime.mutableStateOf
4848
import androidx.compose.runtime.remember
49-
import androidx.compose.runtime.saveable.rememberSaveable
50-
import androidx.compose.runtime.setValue
5149
import androidx.compose.ui.Alignment
5250
import androidx.compose.ui.Modifier
5351
import androidx.compose.ui.draw.alpha
@@ -59,9 +57,12 @@ import androidx.compose.ui.unit.dp
5957
import androidx.window.core.layout.WindowSizeClass
6058
import androidx.window.core.layout.WindowWidthSizeClass
6159
import androidx.xr.compose.platform.LocalSpatialCapabilities
60+
import androidx.xr.compose.platform.LocalSpatialConfiguration
6261
import androidx.xr.compose.spatial.ContentEdge
6362
import androidx.xr.compose.spatial.Orbiter
6463
import androidx.xr.compose.spatial.Subspace
64+
import androidx.xr.compose.subspace.MovePolicy
65+
import androidx.xr.compose.subspace.ResizePolicy
6566
import androidx.xr.compose.subspace.SpatialColumn
6667
import androidx.xr.compose.subspace.SpatialPanel
6768
import androidx.xr.compose.subspace.SpatialRow
@@ -70,34 +71,87 @@ import androidx.xr.compose.subspace.layout.alpha
7071
import androidx.xr.compose.subspace.layout.fillMaxSize
7172
import androidx.xr.compose.subspace.layout.fillMaxWidth
7273
import androidx.xr.compose.subspace.layout.height
73-
import androidx.xr.compose.subspace.layout.movable
7474
import androidx.xr.compose.subspace.layout.offset
7575
import androidx.xr.compose.subspace.layout.padding
76-
import androidx.xr.compose.subspace.layout.resizable
76+
import androidx.xr.compose.subspace.layout.rotate
7777
import androidx.xr.compose.subspace.layout.size
7878
import androidx.xr.compose.subspace.layout.width
79+
import androidx.xr.runtime.math.Quaternion
7980
import com.example.helloandroidxr.R
81+
import com.example.helloandroidxr.ui.components.BugdroidControls
8082
import com.example.helloandroidxr.ui.components.BugdroidModel
83+
import com.example.helloandroidxr.ui.components.BugdroidSliderControls
8184
import com.example.helloandroidxr.ui.components.EnvironmentControls
8285
import com.example.helloandroidxr.ui.components.SearchBar
86+
import com.example.helloandroidxr.ui.components.TextPane
8387
import com.example.helloandroidxr.ui.theme.HelloAndroidXRTheme
88+
import com.example.helloandroidxr.viewmodel.BugdroidUiState
89+
import com.example.helloandroidxr.viewmodel.BugdroidViewModel
90+
import com.example.helloandroidxr.viewmodel.ModelMaterialColor
91+
import com.example.helloandroidxr.viewmodel.ModelMaterialProperties
92+
import com.example.helloandroidxr.viewmodel.ModelOffset
93+
import com.example.helloandroidxr.viewmodel.ModelRotation
94+
import com.example.helloandroidxr.viewmodel.SliderGroup
8495
import kotlinx.coroutines.launch
8596

8697
@Composable
8798
fun HelloAndroidXRApp() {
99+
val viewModel = BugdroidViewModel()
100+
val uiState by viewModel.uiState.collectAsState()
88101
if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
89102
SpatialLayout(
90-
primaryContent = { PrimaryContent() },
91-
firstSupportingContent = { BlockOfContentOne() },
92-
secondSupportingContent = { BlockOfContentTwo() }
103+
primaryContent = {
104+
PrimaryContent(
105+
uiState = uiState,
106+
onShowBugdroidToggle = viewModel::updateShowBugdroid,
107+
onAnimateBugdroidToggle = viewModel::updateAnimateBugdroid
108+
)
109+
},
110+
firstSupportingContent = {
111+
BlockOfContentOne(
112+
showBugdroid = uiState.showBugdroid,
113+
onSliderGroupSelected = viewModel::updateShownSliderGroup,
114+
onResetModel = viewModel::resetModel
115+
)
116+
},
117+
secondSupportingContent = {
118+
BlockOfContentTwo(
119+
uiState = uiState,
120+
showBugdroid = uiState.showBugdroid,
121+
onScaleChange = viewModel::updateScale,
122+
onRotationChange = viewModel::updateRotation,
123+
onOffsetChange = viewModel::updateOffset,
124+
onMaterialColorChange = viewModel::updateMaterialColor,
125+
onMaterialPropertiesChange = viewModel::updateMaterialProperties
126+
)
127+
}
93128
)
94129
} else {
95130
NonSpatialTwoPaneLayout(
96131
secondaryPane = {
97-
BlockOfContentOne()
98-
BlockOfContentTwo()
132+
BlockOfContentOne(
133+
modifier = Modifier.height(240.dp),
134+
showBugdroid = uiState.showBugdroid,
135+
onSliderGroupSelected = viewModel::updateShownSliderGroup,
136+
onResetModel = viewModel::resetModel
137+
)
138+
BlockOfContentTwo(
139+
uiState = uiState,
140+
showBugdroid = uiState.showBugdroid,
141+
onScaleChange = viewModel::updateScale,
142+
onRotationChange = viewModel::updateRotation,
143+
onOffsetChange = viewModel::updateOffset,
144+
onMaterialColorChange = viewModel::updateMaterialColor,
145+
onMaterialPropertiesChange = viewModel::updateMaterialProperties
146+
)
99147
},
100-
primaryPane = { PrimaryContent() }
148+
primaryPane = {
149+
PrimaryContent(
150+
uiState = uiState,
151+
onShowBugdroidToggle = viewModel::updateShowBugdroid,
152+
onAnimateBugdroidToggle = viewModel::updateAnimateBugdroid
153+
)
154+
}
101155
)
102156
}
103157
}
@@ -127,18 +181,18 @@ private fun SpatialLayout(
127181
SubspaceModifier
128182
.alpha(animatedAlpha.value)
129183
.size(400.dp)
130-
.padding(bottom = 16.dp)
131-
.movable()
132-
.resizable()
184+
.padding(bottom = 16.dp),
185+
dragPolicy = MovePolicy(isEnabled = true),
186+
resizePolicy = ResizePolicy(isEnabled = true)
133187
) {
134188
firstSupportingContent()
135189
}
136190
SpatialPanel(
137191
SubspaceModifier
138192
.alpha(animatedAlpha.value)
139-
.weight(1f)
140-
.movable()
141-
.resizable()
193+
.weight(1f),
194+
dragPolicy = MovePolicy(isEnabled = true),
195+
resizePolicy = ResizePolicy(isEnabled = true)
142196
) {
143197
secondSupportingContent()
144198
}
@@ -147,9 +201,9 @@ private fun SpatialLayout(
147201
modifier = SubspaceModifier
148202
.alpha(animatedAlpha.value)
149203
.fillMaxSize()
150-
.padding(left = 16.dp)
151-
.movable()
152-
.resizable()
204+
.padding(left = 16.dp),
205+
dragPolicy = MovePolicy(isEnabled = true),
206+
resizePolicy = ResizePolicy(isEnabled = true)
153207
) {
154208
Column {
155209
TopAppBar()
@@ -279,29 +333,63 @@ private fun TopAppBar() {
279333
}
280334

281335
@Composable
282-
private fun PrimaryContent(modifier: Modifier = Modifier) {
283-
var showBugdroid by rememberSaveable { mutableStateOf(false) }
284-
val stringResId = if (showBugdroid) R.string.hide_bugdroid else R.string.show_bugdroid
285-
336+
private fun PrimaryContent(
337+
uiState: BugdroidUiState,
338+
onShowBugdroidToggle: () -> Unit,
339+
onAnimateBugdroidToggle: () -> Unit,
340+
modifier: Modifier = Modifier,
341+
) {
286342
if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
343+
val showStringResId =
344+
if (uiState.showBugdroid) R.string.hide_bugdroid else R.string.show_bugdroid
345+
val animateStringResId =
346+
if (uiState.animateBugdroid) R.string.stop_animation_bugdroid else R.string.animate_bugdroid
347+
val modelTransform = uiState.modelTransform
287348
Surface(modifier.fillMaxSize()) {
288-
Box(modifier.padding(48.dp), contentAlignment = Alignment.Center) {
289-
Button(
290-
onClick = {
291-
showBugdroid = !showBugdroid
292-
},
293-
modifier = modifier
294-
) {
295-
Text(
296-
text = stringResource(id = stringResId),
297-
style = MaterialTheme.typography.labelLarge
298-
)
349+
Column(modifier.padding(48.dp), horizontalAlignment = Alignment.CenterHorizontally) {
350+
Box(modifier.padding(48.dp), contentAlignment = Alignment.Center) {
351+
Button(
352+
onClick = onShowBugdroidToggle,
353+
modifier = modifier
354+
) {
355+
Text(
356+
text = stringResource(id = showStringResId),
357+
style = MaterialTheme.typography.labelLarge
358+
)
359+
}
360+
}
361+
Box(modifier.padding(48.dp), contentAlignment = Alignment.Center) {
362+
if (uiState.showBugdroid) {
363+
Button(
364+
onClick = onAnimateBugdroidToggle,
365+
modifier = modifier
366+
) {
367+
Text(
368+
text = stringResource(id = animateStringResId),
369+
style = MaterialTheme.typography.labelLarge
370+
)
371+
}
372+
}
299373
}
300374
BugdroidModel(
301-
showBugdroid = showBugdroid,
375+
modelTransform = modelTransform,
376+
showBugdroid = uiState.showBugdroid,
377+
animateBugdroid = uiState.animateBugdroid,
302378
modifier = SubspaceModifier
303379
.fillMaxSize()
304-
.offset(z = 400.dp) // Relative position from the panel
380+
.rotate(
381+
Quaternion(
382+
x = modelTransform.rotation.x,
383+
y = modelTransform.rotation.y,
384+
z = modelTransform.rotation.z,
385+
w = modelTransform.rotation.w
386+
)
387+
)
388+
.offset(
389+
x = modelTransform.offset.x.dp,
390+
y = modelTransform.offset.y.dp,
391+
z = modelTransform.offset.z.dp // Relative position from the panel
392+
)
305393
)
306394
}
307395
}
@@ -314,13 +402,51 @@ private fun PrimaryContent(modifier: Modifier = Modifier) {
314402
}
315403

316404
@Composable
317-
private fun BlockOfContentOne(modifier: Modifier = Modifier) {
318-
TextPane(stringResource(R.string.block_of_content_1), modifier = modifier.height(240.dp))
405+
private fun BlockOfContentOne(
406+
modifier: Modifier = Modifier,
407+
showBugdroid: Boolean,
408+
onSliderGroupSelected: (SliderGroup) -> Unit,
409+
onResetModel: () -> Unit
410+
) {
411+
if (LocalSpatialConfiguration.current.hasXrSpatialFeature && showBugdroid) {
412+
BugdroidControls(
413+
onSliderGroupSelected = onSliderGroupSelected,
414+
onResetModel = {
415+
onResetModel()
416+
onSliderGroupSelected(SliderGroup.NONE)
417+
},
418+
modifier = modifier
419+
)
420+
} else {
421+
TextPane(stringResource(R.string.block_of_content_1), modifier = modifier.fillMaxHeight())
422+
}
319423
}
320424

321425
@Composable
322-
private fun BlockOfContentTwo(modifier: Modifier = Modifier) {
323-
TextPane(stringResource(R.string.block_of_content_2), modifier = modifier.fillMaxHeight())
426+
private fun BlockOfContentTwo(
427+
modifier: Modifier = Modifier,
428+
uiState: BugdroidUiState,
429+
showBugdroid: Boolean,
430+
onScaleChange: (Float) -> Unit,
431+
onRotationChange: (ModelRotation) -> Unit,
432+
onOffsetChange: (ModelOffset) -> Unit,
433+
onMaterialColorChange: (ModelMaterialColor) -> Unit,
434+
onMaterialPropertiesChange: (ModelMaterialProperties) -> Unit,
435+
) {
436+
if (LocalSpatialConfiguration.current.hasXrSpatialFeature && showBugdroid) {
437+
BugdroidSliderControls(
438+
visibleSliderGroup = uiState.visibleSliderGroup,
439+
modelTransform = uiState.modelTransform,
440+
onScaleChange = onScaleChange,
441+
onRotationChange = onRotationChange,
442+
onOffsetChange = onOffsetChange,
443+
onMaterialColorChange = onMaterialColorChange,
444+
onMaterialPropertiesChange = onMaterialPropertiesChange,
445+
modifier = modifier
446+
)
447+
} else {
448+
TextPane(stringResource(R.string.block_of_content_2), modifier = modifier.fillMaxHeight())
449+
}
324450
}
325451

326452
@Composable

0 commit comments

Comments
 (0)