Supera i limiti dello spazio sullo schermo e ancora le tue app nel mondo reale con Android XR. Questa sessione ti insegna a creare esperienze Android XR geospaziali utilizzando Jetpack Compose Glimmer e l'API Geospatial di ARCore. Inizieremo spiegando come ottenere la posa geospaziale di un dispositivo con ARCore. Poi, vedrai come i componenti UI minimali di Glimmer possono essere ancorati a posizioni reali tramite il Visual Positioning System (VPS). Combineremo questi elementi per creare un'esperienza spaziale semplice e basata sulla posizione.
Trascrizione
Ciao a tutti, vi diamo il benvenuto alla prima sessione del percorso Android XR qui all'Android Developer Summit.
Sì!
Siamo felici di vedere che così tante persone stanno imparando a conoscere Android XR.
Mi chiamo Yasmine Quintana Evjen e sono qui con Paul Hoover e Dereck Bridie, che si uniranno a noi nel corso della conversazione.
L'argomento di questa sessione è Android XR e come puoi creare esperienze reali ancorate nello spazio con Jetpack Compose Glimmer e ARCore per Jetpack XR.
Ma prima, facciamo una breve introduzione ad Android XR.
Android XR è una nuova piattaforma che ridefinisce il modo in cui gli utenti interagiscono con la tua app, andando oltre lo schermo e nello spazio circostante.
Progettiamo la piattaforma per portare le esperienze digitali direttamente nell'ambiente fisico.
Che si tratti di overlay visibili a colpo d'occhio che migliorano la presenza nel mondo reale o di un ambiente immersivo infinito.
È lo stesso ecosistema Android aperto che abbiamo creato insieme per anni, ma ora la tela è grande quanto la stanza e ampia quanto la tua immaginazione.
Consideriamo l'XR come una gamma di dispositivi che vanno dagli occhiali intelligenti, che consentono esperienze di realtà aumentata leggere su occhiali con audio e display, ai visori immersivi e agli occhiali XR con cavo, che offrono concentrazione, produttività e funzionalità di intrattenimento immersivo.
Il dispositivo più recente della famiglia Android XR è XREAL AURA, già disponibile per l'acquisto.
Questo dispositivo è leggero e potente e consente esperienze XR con la familiarità di Android.
La nostra visione della piattaforma è che se stai già sviluppando per Android, sei prontə per sviluppare per XR.
Vogliamo semplificarti lo sviluppo su Android XR utilizzando lo stesso modello di app, la stessa libreria e gli stessi strumenti che già conosci.
Con poche o nessuna modifica, la tua app 2D compatibile con Android esistente funzionerà sui visori Android XR e sugli occhiali XR con cavo.
Per creare esperienze XR straordinarie che sembrano naturali, forniamo un insieme di librerie create appositamente per la piattaforma XR.
Gli sviluppatori abituati a creare esperienze su altre piattaforme immersive possono utilizzare OpenXR e il nostro supporto per i motori 3D, come Unity, Godot e Unreal.
Parliamo della creazione con le librerie Android.
Forniamo l'SDK Jetpack XR, un insieme di librerie Kotlin che ti aiuta a estendere le tue app alla XR attraverso esperienze immersive e aumentate.
Diamo un'occhiata alle nostre librerie immersive.
Quest'anno le nostre librerie Jetpack XR sono state rilasciate in versione beta e stabile, offrendoti la sicurezza di cui hai bisogno per iniziare a creare le tue esperienze XR immersive.
Il livello di percezione è ARCore per Jetpack XR.
Questa libreria fornisce informazioni sul mondo reale, ad esempio dove si trovano le superfici piane come un tavolo e come sono posizionate le mani degli utenti.
Può monitorare i punti nello spazio 3D e fornire queste informazioni alle app, se vengono concesse le autorizzazioni appropriate, ovviamente.
Poi c'è Jetpack XR SceneCore, che fornisce un grafico della scena per posizionare i contenuti spaziali.
Questa libreria è in grado di presentare entità nello spazio 3D e fornisce comportamenti spaziali per un posizionamento preciso del grafico della scena.
Jetpack Compose per XR fornisce nuovi elementi componibili di sottospazio creati appositamente per il 3D.
Gli sviluppatori Android che hanno già familiarità con i layout di Compose, ad esempio la creazione con righe e colonne, troveranno intuitiva la disposizione della UI spaziale.
Creando una riga spaziale, puoi disporre in modo dichiarativo più pannelli spaziali affiancati senza dover calcolare manualmente la loro posizione o dimensione.
Utilizzando elementi componibili di sottospazio come un pannello spaziale o un orbiter, puoi estendere facilmente i tuoi componenti 2D in questo nuovo spazio 3D.
Naturalmente, i composable di sottospazio sono in grado di fare molto di più che mostrare contenuti 2D.
Compose per XR supporta i video spaziali e i modelli 3D tramite la superficie esterna spaziale e il modello glTF spaziale.
Ora parliamo delle app con funzionalità di realtà aumentata.
Con la libreria Jetpack Projected, puoi migliorare la tua app mobile esistente per utilizzare l'hardware degli occhiali intelligenti, come i sensori e il display.
Infine, Jetpack Compose Glimmer, un toolkit UI per la creazione di esperienze Android XR ottimizzate per gli occhiali.
Jetpack Compose Glimmer è composto da stili e componenti pronti all'uso per la creazione di esperienze XR.
Abbiamo lanciato Glimmer l'anno scorso e abbiamo continuato ad ampliarne le funzionalità, tra cui una skill agentica per aiutarti a creare le tue app per occhiali con display con Glimmer.
android skills add display-glasses-with-jetpack-compose-glimmerDal lancio, abbiamo ascoltato i tuoi feedback.
Ci avete chiesto di estendere Jetpack Compose Glimmer oltre gli occhiali intelligenti.
Siamo felici di annunciare che abbiamo apportato modifiche per supportare meglio le tue esperienze ancorate nello spazio con Glimmer, di cui parleremo più avanti in questo intervento.
Il nostro lavoro per ottimizzare Glimmer è iniziato con la creazione di una nuova superficie per la visualizzazione su una varietà di fattori di forma XR, da un display agli occhiali XR con cavo.
L'aggiunta di questa nuova superficie offre una maggiore flessibilità per la personalizzazione della UI.
Inoltre, per aumentare la riconoscibilità del brand delle tue app, abbiamo aggiunto colori personalizzati ai componenti Glimmer.
Utilizzali per esprimere lo stile del tuo brand nelle tue app XR.
La nostra libreria Jetpack Compose Glimmer fornisce funzioni helper che convertono i colori scelti, i colori del brand in colori sicuri per gli occhiali.
In questo modo, la visibilità sui display ottici trasparenti viene ottimizzata.
Vediamo come funziona.
Vogliamo cambiare il colore del pulsante attivo per questa app per occhiali, quindi selezionerò uno dei miei colori preferiti.
val brandColor = Color(0xFFFFAABB)
val focusedColor = ButtonDefaults.focusedColor(brandColor)
Column(…) {
LeadingImageCard(
focusable = false,
…
) {
…
}
ButtonGroup(...) {
Button(
onClick = {},
leadingIcon = { … },
focusedColor = focusedColor,
) { Text("Add to cart") }
/* Rest of the buttons */
}
}Tieni presente che, quando il colore viene aggiunto al codice, le funzioni helper di Glimmer lo regolano su un colore sicuro per gli occhiali per una visibilità ottimizzata.
Tutto questo è integrato nella nostra libreria Jetpack Compose Glimmer, per garantire la creazione di esperienze che abbiano un aspetto fantastico in vari ambienti.
In Android Studio, puoi utilizzare l'emulatore per visualizzare l'aspetto della UI degli occhiali su vari sfondi, ad esempio ambienti chiari, scuri o affollati.
Questi aggiornamenti sono solo la punta dell'iceberg.
Ok, no.
Il mio team mi ha detto che non avrebbe fatto ridere, ma l'ho tenuta comunque.
Ma ora passo la parola a Paul, che vi parlerà di Jetpack Compose Glance.
Test in corso, tutto ok.
Devo premere il pulsante Avanti.
Ok, grazie Yasmine.
Okay.
Come ha detto Yasmine, Jetpack Compose Glimmer fornisce i componenti di base per lo sviluppo di applicazioni personalizzate per gli occhiali con display.
I nostri componenti integrano le best practice create per i display ottici trasparenti e li abbiamo integrati direttamente nelle API per voi, consentendo a tutti gli sviluppatori di creare esperienze ottiche trasparenti belle e funzionali.
Quando progetti e crei app per dispositivi ottici trasparenti come questo, devi tenere presente alcuni aspetti.
Innanzitutto, si tratta di un display trasparente, il che significa che non c'è il nero.
Quando il mondo è visibile, il nero è trasparente.
Pertanto, non puoi fare affidamento sugli sfondi scuri per mantenere il contrasto per la UI, il testo e i contenuti.
Anche se il nero è invisibile, puoi utilizzarlo per oscurare altri contenuti digitali.
Nel framework UI Glimmer, abbiamo un'API di profondità che ti consente di separare facilmente gli elementi UI l'uno dall'altro e di fornire un senso di gerarchia.
Qui puoi vedere come la profondità delle schede in questa pila le aiuta a rimanere separate l'una dall'altra.
I nostri esperti di visione hanno fornito rapporti di contrasto target per testo e icone visibili nella maggior parte delle condizioni di illuminazione.
Come ha mostrato Yasmine, possiamo creare o abbiamo creato questa funzionalità nel framework in modo che, quando personalizzi i colori della superficie, puoi avere la certezza che l'interfaccia sarà leggibile per gli utenti.
Abbiamo integrato queste e altre best practice nella nostra libreria di componenti in espansione.
Di recente abbiamo rilasciato molti nuovi componenti.
Una delle nostre ultime novità è il componente pager con transizioni di movimento e gesti fluidi integrati.
val pagerState =
rememberGlimmerPagerState(pageCount = { 10 })
GlimmerHorizontalPager(pagerState) { page ->
/* Card Content */
}Se hai familiarità con il pager orizzontale di Compose, questo componente ti sarà molto familiare.
Abbiamo anche introdotto quattro nuove varianti di schede progettate per un'integrazione perfetta con pager, stack e altre UI di Glimmer.
Le schede con immagini iniziali e finali mostrano testo e immagini affiancati.
Utilizza queste varianti di schede quando hai una quantità uguale di testo e informazioni visive.
Abbiamo separato la scheda immagine in una variante di scheda a sé stante.
Questo è l'ideale quando hai un'immagine di grandi dimensioni con magari una singola riga di testo o una didascalia, come questo selettore di video.
La variante della scheda di azione incorpora un singolo pulsante in una scheda.
Utilizza questa opzione quando vuoi che gli utenti sappiano esattamente cosa succederà quando attivano la scheda, come in questo selettore di destinazione.
I gruppi di pulsanti ti consentono di inserire molti pulsanti in uno spazio ridotto.
ButtonGroup {
Button(onClick = {}) { Text("Button 1") }
Button(onClick = {}) { Text("Button 2") }
Button(onClick = {}) { Text("Button 3") }
Button(onClick = {}) { Text("Button 4") }
Button(onClick = {}) { Text("Button 5") }
}Con questo semplice codice, ottieni queste bellissime transizioni, comportamenti di scorrimento, scrim che nascondono i bordi e vengono mostrati solo quando necessario, e input che viene indirizzato automaticamente dagli occhiali al controllo.
I gruppi di pulsanti possono anche contenere i nostri nuovi pulsanti di attivazione/disattivazione e con icone.
var buttonChecked by remember { mutableStateOf(false) }
ToggleButton(
checked = buttonChecked,
leadingIcon = { Icon(FavoriteIcon) },
onCheckedChange = { buttonChecked = it }
) {
Text(if (buttonChecked) "On" else "Off")
}Qui puoi vedere tutti i nostri tipi di pulsanti utilizzati per creare questo controllo multimediale.
Inoltre, ora è facile testare la UI su sfondi diversi.
L'emulatore di occhiali consente di inserire foto sferiche e di spostarsi per testare l'effetto su sfondi diversi, sfondi complessi, chiari, scuri e così via.
I gruppi di pulsanti sono ideali per essere utilizzati in una visualizzazione dettagliata come questa per mostrare azioni contestuali.
Al tocco, puoi utilizzare le transizioni degli elementi condivisi per passare a uno stato di visualizzazione dettagliato, quindi utilizzare il gesto Indietro del sistema per tornare alla visualizzazione precedente.
Molto carino.
Inoltre, abbiamo aggiunto un indicatore di input vocale e finestre di dialogo di avviso.
Quindi, sì, un sacco di novità.
Ora che abbiamo visto alcune delle novità, creiamo qualcosa.
Oltre a utilizzare Glimmer per creare occhiali intelligenti, possiamo utilizzarlo per creare esperienze per occhiali XR con cavo.
Vogliamo che tu crei esperienze che migliorino il mondo che ti circonda, a partire da ancoraggi basati sulla posizione nel mondo reale.
Immagina di trovarti in una nuova città, magari in viaggio per una conferenza per sviluppatori, e di voler trovare un posto dove mangiare.
Puoi chiedere alla tua app di navigazione preferita di trovare i ristoranti nelle vicinanze e le relative valutazioni.
Le informazioni possono essere sovrapposte direttamente alla posizione, fornendo contesto, intelligence e indicazioni.
Qui puoi vedere il nostro ultimo componente Glimmer, chiamato Icon Marker.
Ti consente di evidenziare elementi chiave nel mondo fisico e di abbinarli ad altri componenti di Glimmer, come questa scheda.
In questo esempio, annota i punti d'interesse utilizzando le API Google Maps, Places e Geospatial.
Ehi, Dereck, mostriamo loro come configurarlo.
Iniziamo.
Okay.
Okay.
Quindi, come ha detto Paul, iniziamo a creare.
Quindi, utilizzeremo la documentazione per lo sviluppo su developer.android.com/xr e daremo un'occhiata alla libreria di componenti Jetpack Compose Glimmer.
In questo modo, potrai trovare una panoramica visiva di tutti i componenti disponibili su Glimmer.
Iniziamo con questa parte della UI che Paul ha mostrato in precedenza.
Utilizzeremo quindi Glimmer UI per creare un indicatore spaziale e una casella informativa.
Iniziamo identificando i componenti che utilizzeremo.
A sinistra vedrai un indicatore di icona, mentre a destra utilizzeremo una scheda che descriverà il luogo.
Quindi, se vai su Android Studio, potrai visualizzare l'anteprima di questo riquadro proprio come faresti con qualsiasi altro componente di Compose e potrai vedere le modifiche in tempo reale.
Come accennato in precedenza, puoi visualizzare l'anteprima della UI in tutti i diversi tipi di ambienti di sfondo e assicurarti che il contrasto della UI sia sufficiente in più tipi di scenari, come ambienti scuri e chiari.
Ora che il canvas è stato impostato, creeremo una riga, inseriremo un indicatore di icona con un po' di spaziatura e una scheda con un testo di esempio.
Ed ecco fatto.
Se l'anteprima 2D ti soddisfa, possiamo iniziare a contrassegnare qualcosa nel mondo reale, spazializzando la UI.
Ciò significa che lo disporremo in 3D anziché in 2D.
Quindi, le UI spaziali sono contenute in sottospazi, ovvero uno spazio 3D di fronte all'utente.
Subspace {
SpatialPanel {
VideoPlayer()
}
}Per impostazione predefinita, un sottospazio dispone i suoi contenuti a una distanza confortevole dall'utente.
Quindi, prima di mostrarti come utilizzare l'API Geospatial, posizioniamo un elemento nelle vicinanze.
Quindi, il composable Subspace di cui ho parlato prima ha un parametro facoltativo che ti consente di specificare un target da seguire.
val state by
qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
SpatialPanel {
Row {
IndicatorDot()
Spacer()
QrCodeCard(state)
}
}
}Per collegare un elemento al mondo reale, devi prima creare un'ancora ottenuta da ARCore per Jetpack XR.
In questo esempio, utilizzo la funzionalità di riconoscimento del codice QR.
In questo modo potrai leggere e monitorare i codici QR, così i tuoi contenuti potranno seguire il codice QR nello spazio e noi collegheremo la UI che abbiamo visto in precedenza.
Ok.
Ti ho mostrato come posizionare contenuti digitali nel mondo reale nelle vicinanze.
Torniamo all'esempio di Paolo.
Personalmente, vengo dai Paesi Bassi.
Non conosco bene Mountain View.
Quindi, dopo questo evento, voglio aiutarti a creare un'app che ti aiuti a trovare dei bar divertenti.
Vediamo come possiamo aiutare i nostri utenti a trovarne alcuni con l'API Geospatial, che ora si trova in ARCore per Jetpack XR.
Quindi, l'API Geospatial, lanciata originariamente sugli smartphone, è ora disponibile anche su Android XR.
Utilizza i dati di Google Street View e li combina con le misurazioni GPS per posizionare il dispositivo nelle aree supportate.
Pertanto, sugli occhiali intelligenti, l'API Geospatial può essere utilizzata per determinare con precisione la posizione e l'orientamento dell'utente e consente casi d'uso di navigazione passo passo ad alta precisione.
Inoltre, su dispositivi immersivi come gli occhiali XR con cavo, l'API Geospatial ti consente di creare esperienze posizionate con precisione nel mondo reale.
Ad esempio, puoi ancorare contenuti spaziali agli edifici o creare tour della città specializzati.
Iniziamo a utilizzare l'API Geospatial con questi tre passaggi.
Il primo è creare un progetto Google Cloud e configurare l'autenticazione nella tua app.
Poi, una volta configurata e attivata l'API Geospatial nella tua app, chiederai all'utente l'autorizzazione per recuperare la sua posizione utilizzando la granularità precisa.
Poi, dopo aver configurato la sessione per l'utilizzo dell'API Geospatial, tutto è pronto.
Potrai trovare tutti i dettagli nella nostra documentazione per lo sviluppo sull'API Geospatial nella nostra pagina per gli sviluppatori Android XR appena aggiornata.
Iniziamo utilizzando la posizione dell'utente per chiedere all'API Places di Google Maps Platform di trovare alcuni ottimi bar nelle vicinanze dell'utente.
Innanzitutto, dove si trova l'utente?
Chiederemo all'API Geospatial di ricevere aggiornamenti continui sulla posizione dell'utente.
produceState<AppStatus>(Initializing) {
val geospatial = Geospatial.getInstance(session)
val wait = geospatial.state.onEach {
value = WaitingForGeospatial(it.geospatialTrackingState)
}.first {
it.geospatialTrackingState == Geospatial.GeospatialTrackingState.RUNNING
}
val accuracyWait = geospatial.state.onEach {
value = WaitingForGeospatialAccuracy(it)
}.first { it.horizontalAccuracy < horizontalAccuracyRequirement }
value = WaitingForPlacesResults
val result = client.findNearby(context, accWait.geospatialPose)
value = Done(result)
}Quindi, accederemo all'oggetto geospaziale tramite la sessione XR.
Dopodiché, attenderemo che Geospatial sia operativo mentre inizializza i sensori necessari e si connette all'API Cloud.
Poi, aspetteremo un certo livello di accuratezza.
Pertanto, i livelli di precisione possono migliorare man mano che vengono ricevute le misurazioni GPS o quando il sistema di posizionamento visivo trova corrispondenze nella posizione dell'utente.
Inoltre, attendere un livello di alta precisione è importante per garantire che le esperienze basate sulla posizione siano ancorate nel posto giusto e che i risultati provengano dall'area vicina all'utente.
A questo punto, siamo finalmente pronti per eseguire la ricerca con l'API Places utilizzando i valori di posizione dell'API Geospatial.
val bounds = CircularBounds.newInstance(
LatLng(pose.latitude, pose.longitude),
100.0, // radius, in meters
)
val placeFields = listOf(
Place.Field.ID, Place.Field.DISPLAY_NAME,
Place.Field.RATING, Place.Field.LOCATION,
)
return placesClient.searchNearby(
SearchNearbyRequest.builder(bounds, placeFields)
.setIncludedTypes(listOf(PlaceTypes.CAFE))
.setMaxResultCount(10)
.build()
).await()Quindi, utilizzeremo un SearchNearbyRequest con l'API Places per cercare luoghi intorno alla posizione dell'utente che abbiamo ottenuto in precedenza e troveremo risultati entro 100 m.
Otterremo l'ID del luogo, il nome visualizzato, la valutazione e, naturalmente, la posizione.
Infine, eseguiremo la ricerca con i parametri specificati, incluso un filtro per il tipo di luogo.
Questo è tutto per i dati. Torniamo alla UI.
Quindi, dovremo capire come posizionare l'indicatore e la finestra informativa di prima e, per farlo, utilizzeremo le ancora geospaziali.
Tornando al codice, ancora una volta, accederemo alla nostra API Geospatial utilizzando la nostra sessione XR, quindi creeremo un'ancora geospaziale utilizzando una latitudine e una longitudine specifiche ottenute dai risultati di ricerca precedenti.
val anchorResult by produceState<AnchorResult?>(null) {
val geospatial = Geospatial.getInstance(session)
value = geospatial.createAnchorOnSurface(
place.location.latitude, place.location.longitude,
surface = GeospatialSurface.TERRAIN,
altitudeAboveSurface = 3.0,
eastUpSouthQuaternion = Quaternion()
)
}In questo esempio, abbiamo specificato che l'altitudine deve essere di 3 m sopra il terreno in corrispondenza di queste coordinate, ma puoi anche collegarla ai tetti supportati, il che significa che il valore dell'altitudine verrà determinato in base al tetto dell'edificio in quella posizione.
Puoi anche ruotare l'ancoraggio utilizzando un quaternione, che specifica i valori di rotazione dell'ancoraggio.
Tuttavia, in questo esempio utilizzeremo i valori predefiniti perché faremo ruotare la UI in modo che sia rivolta verso l'utente.
Poi, collegheremo la UI di Glimmer di prima a questo ancoraggio, che ti fornirà gli elementi di base per un'esperienza ancorata nello spazio.
when (anchorResult) {
is AnchorCreateSuccess ->
Subspace(FollowTarget.anchor(anchorResult.anchor)) {
SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
PlaceIndicator(place)
}
}
else ->
Log.e("Demo", "Failed to create anchor $anchorResult")
}Per questo motivo, riteniamo che l'API Geospatial possa consentire di creare esperienze di orientamento eccezionali e che la progettazione con Glimmer aiuterà a creare esperienze utente di facile consultazione, realizzate appositamente per i display trasparenti.
Per saperne di più sull'API Geospatial, visita il sito developer.android.com/xr, aggiornato di recente.
Ora lascio la parola a Yasmine per la conclusione.
In questa sessione abbiamo esaminato alcune funzionalità di Android XR: ARCore per Jetpack XR e Jetpack Compose Glimmer.
Siamo entusiasti delle nuove funzionalità che Android XR porterà sugli occhiali XR con cavo.
Gli sviluppatori stanno già creando per Android XR e speriamo di vedere il tuo lavoro creativo sul Google Play Store.
Per iniziare, consulta la nostra documentazione per lo sviluppo, lasciati ispirare dai nostri esperimenti XR e inizia a utilizzare i codelab.
Abbiamo in programma una serie di sessioni, demo e workshop sulla XR per tutti voi.
Quindi, sintonizzati e scopri come creare per Android XR esperienze immersive e aumentate.
A nome del team di Android XR, vi ringrazio per la vostra presenza e spero che vi godiate il resto dell'Android Developer Summit.
Quindi, grazie.
android skills add display-glasses-with-jetpack-compose-glimmerval brandColor = Color(0xFFFFAABB)
val focusedColor = ButtonDefaults.focusedColor(brandColor)
Column(…) {
LeadingImageCard(
focusable = false,
…
) {
…
}
ButtonGroup(...) {
Button(
onClick = {},
leadingIcon = { … },
focusedColor = focusedColor,
) { Text("Add to cart") }
/* Rest of the buttons */
}
}val pagerState =
rememberGlimmerPagerState(pageCount = { 10 })
GlimmerHorizontalPager(pagerState) { page ->
/* Card Content */
}ButtonGroup {
Button(onClick = {}) { Text("Button 1") }
Button(onClick = {}) { Text("Button 2") }
Button(onClick = {}) { Text("Button 3") }
Button(onClick = {}) { Text("Button 4") }
Button(onClick = {}) { Text("Button 5") }
}var buttonChecked by remember { mutableStateOf(false) }
ToggleButton(
checked = buttonChecked,
leadingIcon = { Icon(FavoriteIcon) },
onCheckedChange = { buttonChecked = it }
) {
Text(if (buttonChecked) "On" else "Off")
}Subspace {
SpatialPanel {
VideoPlayer()
}
}val state by
qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
SpatialPanel {
Row {
IndicatorDot()
Spacer()
QrCodeCard(state)
}
}
}produceState<AppStatus>(Initializing) {
val geospatial = Geospatial.getInstance(session)
val wait = geospatial.state.onEach {
value = WaitingForGeospatial(it.geospatialTrackingState)
}.first {
it.geospatialTrackingState == Geospatial.GeospatialTrackingState.RUNNING
}
val accuracyWait = geospatial.state.onEach {
value = WaitingForGeospatialAccuracy(it)
}.first { it.horizontalAccuracy < horizontalAccuracyRequirement }
value = WaitingForPlacesResults
val result = client.findNearby(context, accWait.geospatialPose)
value = Done(result)
}val bounds = CircularBounds.newInstance(
LatLng(pose.latitude, pose.longitude),
100.0, // radius, in meters
)
val placeFields = listOf(
Place.Field.ID, Place.Field.DISPLAY_NAME,
Place.Field.RATING, Place.Field.LOCATION,
)
return placesClient.searchNearby(
SearchNearbyRequest.builder(bounds, placeFields)
.setIncludedTypes(listOf(PlaceTypes.CAFE))
.setMaxResultCount(10)
.build()
).await()val anchorResult by produceState<AnchorResult?>(null) {
val geospatial = Geospatial.getInstance(session)
value = geospatial.createAnchorOnSurface(
place.location.latitude, place.location.longitude,
surface = GeospatialSurface.TERRAIN,
altitudeAboveSurface = 3.0,
eastUpSouthQuaternion = Quaternion()
)
}when (anchorResult) {
is AnchorCreateSuccess ->
Subspace(FollowTarget.anchor(anchorResult.anchor)) {
SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
PlaceIndicator(place)
}
}
else ->
Log.e("Demo", "Failed to create anchor $anchorResult")
}