Skip to content

Commit f72de45

Browse files
committed
Add head-tracked spatial delete confirmation dialog
1 parent 8286dcf commit f72de45

File tree

14 files changed

+242
-29
lines changed

14 files changed

+242
-29
lines changed

Showcases/media_view/app/src/main/AndroidManifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@
6464
android:exported="true"
6565
android:launchMode="standard"
6666
android:theme="@style/Theme.MediaView"
67+
/>
68+
<activity
69+
android:name=".app.player.menu.minimized.SpatialDeleteConfirmationActivity"
70+
android:allowEmbedded="true"
71+
android:exported="true"
72+
android:launchMode="standard"
73+
android:theme="@style/Theme.MediaView"
6774
/>
6875
<activity
6976
android:name=".app.gallery.menu.GalleryMenuActivity"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"nodes":[{"extras":{"meta_spatial":{"entity_id":"privacyPopup","components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"keyString":"dimensions","type":"Vector2","value":[0.20000000298023224,0.20000000298023224]}},"com.meta.levinriegner.mediaview.LookAtHead":{"zOffset":{"keyString":"zOffset","type":"Float","value":0.7}},"com.meta.spatial.toolkit.Panel":{"panel":{"keyString":"panel","type":"String","value":"@integer/5"}}},"version":1}},"name":"privacyPopup"},{"extras":{"meta_spatial":{"entity_id":"galleryMenu","components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"keyString":"dimensions","type":"Vector2","value":[0.5,0.10000000149011612]}},"com.meta.spatial.toolkit.Panel":{"panel":{"keyString":"panel","type":"String","value":"@integer/2"}}},"version":1}},"translation":[0,0.2977507710456848,0],"name":"galleryMenu"},{"extras":{"meta_spatial":{"entity_id":"mediaTypesFilter","components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"keyString":"dimensions","type":"Vector2","value":[0.17499999701976776,0.49000000953674316]}},"com.meta.spatial.toolkit.Panel":{"panel":{"keyString":"panel","type":"String","value":"@integer/3"}}},"version":1}},"translation":[0.49000000953674316,0,0],"name":"mediaTypesFilter"},{"extras":{"meta_spatial":{"entity_id":"whatsNew","components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"keyString":"dimensions","type":"Vector2","value":[0.8999999761581421,0.44999998807907104]}},"com.meta.spatial.toolkit.Panel":{"panel":{"keyString":"panel","type":"String","value":"@integer/7"}}},"version":1}},"translation":[0,-0.004070401191711426,0],"name":"whatsNew"},{"extras":{"meta_spatial":{"entity_id":"onboarding","components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"keyString":"dimensions","type":"Vector2","value":[0.8999999761581421,0.44999998807907104]}},"com.meta.spatial.toolkit.Panel":{"panel":{"keyString":"panel","type":"String","value":"@integer/6"}}},"version":1}},"name":"onboarding"},{"extras":{"meta_spatial":{"entity_id":"gallery","components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.Grabbable":{"type":{"keyString":"type","alias":"FACE","type":"GrabbableType","value":0},"maxHeight":{"keyString":"maxHeight","type":"Float","value":3.4028234663852886E38},"minHeight":{"keyString":"minHeight","type":"Float","value":-3.4028234663852886E38},"enabled":{"keyString":"enabled","type":"Boolean","value":true}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"keyString":"dimensions","type":"Vector2","value":[0.7799999713897705,0.49000000953674316]}},"com.meta.levinriegner.mediaview.LookAtHead":{"zOffset":{"keyString":"zOffset","type":"Float","value":1.1},"once":{"keyString":"once","type":"Boolean","value":true}},"com.meta.spatial.toolkit.Panel":{"panel":{"keyString":"panel","type":"String","value":"@integer/1"}}},"version":1}},"children":[4,3,2,1],"name":"gallery"}],"scenes":[{"nodes":[5,0]}],"scene":0,"asset":{"minVersion":"2.0","version":"2.0"}}
1+
{"nodes":[{"name":"privacyPopup","extras":{"meta_spatial":{"components":{"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"value":[0.20000000298023224,0.20000000298023224],"type":"Vector2","keyString":"dimensions"}},"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.Panel":{"panel":{"value":"@integer/5","type":"String","keyString":"panel"}},"com.meta.levinriegner.mediaview.LookAtHead":{"hasLooked":{"value":false,"type":"Boolean","keyString":"hasLooked"},"once":{"value":false,"type":"Boolean","keyString":"once"},"zOffset":{"value":0.7,"type":"Float","keyString":"zOffset"}}},"version":1}}},{"name":"whatsNew","translation":[0,-0.004070401191711426,0],"extras":{"meta_spatial":{"components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"value":[0.8999999761581421,0.44999998807907104],"type":"Vector2","keyString":"dimensions"}},"com.meta.spatial.toolkit.Panel":{"panel":{"value":"@integer/7","type":"String","keyString":"panel"}}},"version":1}}},{"name":"mediaTypesFilter","translation":[0.49000000953674316,0,0],"extras":{"meta_spatial":{"components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"value":[0.17499999701976776,0.49000000953674316],"type":"Vector2","keyString":"dimensions"}},"com.meta.spatial.toolkit.Panel":{"panel":{"value":"@integer/3","type":"String","keyString":"panel"}}},"version":1}}},{"name":"onboarding","extras":{"meta_spatial":{"components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"value":[0.8999999761581421,0.44999998807907104],"type":"Vector2","keyString":"dimensions"}},"com.meta.spatial.toolkit.Panel":{"panel":{"value":"@integer/6","type":"String","keyString":"panel"}}},"version":1}}},{"name":"galleryMenu","translation":[0,0.2977507710456848,0],"extras":{"meta_spatial":{"components":{"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"value":[0.5,0.10000000149011612],"type":"Vector2","keyString":"dimensions"}},"com.meta.spatial.toolkit.Panel":{"panel":{"value":"@integer/2","type":"String","keyString":"panel"}}},"version":1}}},{"name":"spatialDeleteConfirmation","extras":{"meta_spatial":{"components":{"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"value":[0.4000000059604645,0.30000001192092896],"type":"Vector2","keyString":"dimensions"}},"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.Panel":{"panel":{"value":"@integer/8","type":"String","keyString":"panel"}},"com.meta.levinriegner.mediaview.LookAtHead":{"hasLooked":{"value":false,"type":"Boolean","keyString":"hasLooked"},"once":{"value":false,"type":"Boolean","keyString":"once"},"zOffset":{"value":0.7,"type":"Float","keyString":"zOffset"}}},"version":1}}},{"name":"gallery","children":[3,1,2,4],"extras":{"meta_spatial":{"components":{"com.meta.spatial.toolkit.PanelDimensions":{"dimensions":{"value":[0.7799999713897705,0.49000000953674316],"type":"Vector2","keyString":"dimensions"}},"com.meta.spatial.toolkit.Grabbable":{},"com.meta.spatial.toolkit.Visible":{"isVisible":{"value":false,"type":"Boolean","keyString":"isVisible"}},"com.meta.spatial.toolkit.Panel":{"panel":{"value":"@integer/1","type":"String","keyString":"panel"}},"com.meta.levinriegner.mediaview.LookAtHead":{"hasLooked":{"value":false,"type":"Boolean","keyString":"hasLooked"},"once":{"value":true,"type":"Boolean","keyString":"once"},"zOffset":{"value":1.1,"type":"Float","keyString":"zOffset"}}},"version":1}}}],"scenes":[{"name":"","nodes":[5,0]}],"scene":0,"asset":{"experience":false,"copyright":"","version":"2.0","generator":"","minVersion":"2.0"}}

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/events/AppEvent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ sealed class MediaPlayerEvent : AppEvent() {
2424
data class Deleted(val mediaId: Long) : MediaPlayerEvent()
2525

2626
data object CloseAll : MediaPlayerEvent()
27+
28+
data class ShowDeleteConfirmation(val mediaModel: com.meta.levinriegner.mediaview.data.gallery.model.MediaModel) : MediaPlayerEvent()
2729
}
2830

2931
sealed class NavigationEvent : AppEvent() {

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/gallery/menu/GalleryMenuViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ constructor(
2020
fun closeAll() {
2121
eventBus.post(MediaPlayerEvent.CloseAll)
2222
panelDelegate.closeAllMedia()
23+
panelDelegate.hideSpatialDeleteConfirmation()
2324
}
2425
}

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/immersive/GLXFConstants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ object GLXFConstants {
1414
const val NODE_NAME_GALLERY_MENU = "galleryMenu"
1515
const val NODE_NAME_ONBOARDING = "onboarding"
1616
const val NODE_NAME_WHATS_NEW = "whatsNew"
17+
const val NODE_NAME_SPATIAL_DELETE_CONFIRMATION = "spatialDeleteConfirmation"
1718
}

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/immersive/ImmersiveActivity.kt

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.meta.levinriegner.mediaview.app.immersive.entity.EnvironmentEntities
1111
import com.meta.levinriegner.mediaview.app.immersive.entity.PanelTransformations
1212
import com.meta.levinriegner.mediaview.app.immersive.system.LookAtHeadSystem
1313
import com.meta.levinriegner.mediaview.app.panel.PanelDelegate
14+
import com.meta.levinriegner.mediaview.app.events.EventBus
1415
import com.meta.levinriegner.mediaview.data.gallery.model.MediaModel
1516
import com.meta.spatial.core.Entity
1617
import com.meta.spatial.core.SpatialFeature
@@ -24,21 +25,24 @@ import kotlinx.coroutines.flow.MutableStateFlow
2425
import kotlinx.coroutines.flow.asStateFlow
2526
import kotlinx.coroutines.launch
2627
import timber.log.Timber
28+
import dagger.hilt.EntryPoint
29+
import dagger.hilt.EntryPoints
30+
import dagger.hilt.InstallIn
31+
import dagger.hilt.components.SingletonComponent
2732

2833
class ImmersiveActivity : ComponentAppSystemActivity(), PanelDelegate {
2934

35+
@EntryPoint
36+
@InstallIn(SingletonComponent::class)
37+
interface EventBusEntryPoint {
38+
fun provideEventBus(): EventBus
39+
}
40+
3041
companion object {
3142
const val MAX_OPEN_MEDIA = 3
3243
}
3344

34-
// Dependencies
35-
private val panelManager: PanelManager by lazy {
36-
PanelManager(
37-
PanelTransformations(EnvironmentEntities(), systemManager),
38-
scene,
39-
spatialContext,
40-
)
41-
}
45+
private lateinit var panelManager: PanelManager
4246

4347
// State
4448
private var _openMedia =
@@ -57,11 +61,29 @@ class ImmersiveActivity : ComponentAppSystemActivity(), PanelDelegate {
5761
}
5862

5963
override fun registerPanels(): List<PanelRegistration> {
64+
// Initialize panelManager if not already initialized
65+
if (!::panelManager.isInitialized) {
66+
initializePanelManager()
67+
}
6068
return panelManager.providePanelRegistrations()
6169
}
6270

71+
private fun initializePanelManager() {
72+
// Get EventBus through EntryPoint using application context
73+
val eventBus = EntryPoints.get(applicationContext, EventBusEntryPoint::class.java).provideEventBus()
74+
75+
// Initialize panelManager
76+
panelManager = PanelManager(
77+
PanelTransformations(EnvironmentEntities(), systemManager),
78+
scene,
79+
spatialContext,
80+
eventBus,
81+
)
82+
}
83+
6384
override fun onCreate(savedInstanceState: Bundle?) {
6485
super.onCreate(savedInstanceState)
86+
6587
// Disable Locomotion
6688
systemManager.unregisterSystem<LocomotionSystem>()
6789
// Register elements
@@ -195,6 +217,16 @@ class ImmersiveActivity : ComponentAppSystemActivity(), PanelDelegate {
195217
Timber.i("Toggling Whats New. Show: $show")
196218
panelManager.toggleWhatsNew(show)
197219
}
220+
221+
override fun showSpatialDeleteConfirmation(mediaModel: MediaModel) {
222+
Timber.i("Showing spatial delete confirmation for media: ${mediaModel.id}")
223+
panelManager.showSpatialDeleteConfirmation(mediaModel)
224+
}
225+
226+
override fun hideSpatialDeleteConfirmation() {
227+
Timber.i("Hiding spatial delete confirmation")
228+
panelManager.hideSpatialDeleteConfirmation()
229+
}
198230
// endregion
199231

200232
}

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/immersive/PanelManager.kt

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
package com.meta.levinriegner.mediaview.app.immersive
44

5-
import android.content.Intent
65
import android.os.Handler
76
import android.os.Looper
87
import com.meta.levinriegner.mediaview.BuildConfig
@@ -17,6 +16,7 @@ import com.meta.levinriegner.mediaview.app.onboarding.OnboardingActivity
1716
import com.meta.levinriegner.mediaview.app.player.PlayerActivity
1817
import com.meta.levinriegner.mediaview.app.player.menu.immersive.ImmersiveMenuActivity
1918
import com.meta.levinriegner.mediaview.app.player.menu.minimized.MinimizedMenuActivity
19+
import com.meta.levinriegner.mediaview.app.player.menu.minimized.SpatialDeleteConfirmationActivity
2020
import com.meta.levinriegner.mediaview.app.privacy.PrivacyPolicyActivity
2121
import com.meta.levinriegner.mediaview.app.shared.model.maximizedBottomCenterPanelVector3
2222
import com.meta.levinriegner.mediaview.app.shared.model.maximizedPanelConfigOptions
@@ -54,12 +54,16 @@ import com.meta.spatial.toolkit.Transform
5454
import com.meta.spatial.toolkit.TransformParent
5555
import com.meta.spatial.toolkit.Visible
5656
import com.meta.spatial.toolkit.createPanelEntity
57+
import android.content.Intent
58+
import com.meta.levinriegner.mediaview.app.events.EventBus
59+
import com.meta.levinriegner.mediaview.app.events.MediaPlayerEvent
5760
import timber.log.Timber
5861

5962
class PanelManager(
6063
private val panelTransformations: PanelTransformations,
6164
private val scene: Scene,
6265
private val spatialContext: SpatialContext,
66+
private val eventBus: com.meta.levinriegner.mediaview.app.events.EventBus,
6367
) {
6468
private val zIndexMenu = 99
6569

@@ -85,6 +89,9 @@ class PanelManager(
8589
PanelCreator(PanelRegistrationIds.PRIVACY_POLICY) { ent ->
8690
createPrivacyPolicyPanel(ent)
8791
},
92+
PanelCreator(PanelRegistrationIds.SPATIAL_DELETE_CONFIRMATION) { ent ->
93+
createSpatialDeleteConfirmationPanel(ent)
94+
},
8895
)
8996

9097
panelRegistrations.forEach { panelRegistration ->
@@ -250,6 +257,17 @@ class PanelManager(
250257
return PanelSceneObject(scene, spatialContext, PrivacyPolicyActivity::class.java, ent, config)
251258
}
252259

260+
private fun createSpatialDeleteConfirmationPanel(ent: Entity): PanelSceneObject {
261+
val config =
262+
PanelConfigOptions(
263+
enableLayer = true,
264+
enableTransparent = false,
265+
includeGlass = false,
266+
)
267+
268+
return PanelSceneObject(scene, spatialContext, SpatialDeleteConfirmationActivity::class.java, ent, config)
269+
}
270+
253271
private fun createPlayerPanel(ent: Entity, mediaModel: MediaModel): PanelSceneObject {
254272
val config = mediaModel.minimizedPanelConfigOptions()
255273

@@ -631,6 +649,29 @@ class PanelManager(
631649
panel.entity.setComponent(Transform(Pose(Vector3(-0.09f, 0f, -0.04f), Quaternion(0f, 0f, 0f))))
632650
}
633651

652+
fun showSpatialDeleteConfirmation(mediaModel: MediaModel) {
653+
val panel = getComposition().tryGetNodeByName(GLXFConstants.NODE_NAME_SPATIAL_DELETE_CONFIRMATION)
654+
if (panel?.entity == null) {
655+
return
656+
}
657+
658+
// Position the dialog in front of the user's head with LookAtHead component
659+
panel.entity.setComponent(com.meta.levinriegner.mediaview.LookAtHead(zOffset = 0.7f, once = false))
660+
panel.entity.setComponent(Visible(true))
661+
662+
// Post event to pass MediaModel to the static panel
663+
eventBus.post(MediaPlayerEvent.ShowDeleteConfirmation(mediaModel))
664+
}
665+
666+
fun hideSpatialDeleteConfirmation() {
667+
val panel = getComposition().tryGetNodeByName(GLXFConstants.NODE_NAME_SPATIAL_DELETE_CONFIRMATION)
668+
if (panel?.entity == null) {
669+
return
670+
}
671+
672+
panel.entity.setComponent(Visible(false))
673+
}
674+
634675
fun debugPrintNodes() {
635676
if (!BuildConfig.DEBUG) return
636677
Timber.i("Printing all nodes...")

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/immersive/panel/PanelRegistrationIds.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ object PanelRegistrationIds {
1010
const val PRIVACY_POLICY = 5
1111
const val ONBOARDING = 6
1212
const val WHATS_NEW = 7
13+
const val SPATIAL_DELETE_CONFIRMATION = 8
1314

1415
private const val MEDIA_PREFIX = "00"
1516
private const val MEDIA_POPUP_PREFIX = "11"

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/panel/PanelDelegate.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ interface PanelDelegate {
2626
fun toggleOnboarding(show: Boolean)
2727

2828
fun toggleWhatsNew(show: Boolean)
29+
30+
fun showSpatialDeleteConfirmation(mediaModel: MediaModel)
31+
32+
fun hideSpatialDeleteConfirmation()
2933
}

Showcases/media_view/app/src/main/java/com/meta/levinriegner/mediaview/app/player/menu/minimized/MinimizedMenuActivity.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@
22

33
package com.meta.levinriegner.mediaview.app.player.menu.minimized
44

5+
import android.content.Intent
56
import android.os.Bundle
67
import androidx.activity.ComponentActivity
78
import androidx.activity.compose.setContent
89
import androidx.activity.enableEdgeToEdge
910
import androidx.activity.viewModels
1011
import com.meta.levinriegner.mediaview.app.shared.theme.MediaViewTheme
12+
import com.meta.levinriegner.mediaview.app.events.EventBus
13+
import com.meta.levinriegner.mediaview.app.events.MediaPlayerEvent
1114
import dagger.hilt.android.AndroidEntryPoint
15+
import javax.inject.Inject
1216

1317
@AndroidEntryPoint
1418
class MinimizedMenuActivity : ComponentActivity() {
1519

1620
private val viewModel by viewModels<MinimizedMenuViewModel>()
21+
22+
@Inject
23+
lateinit var eventBus: EventBus
1724

1825
override fun onCreate(savedInstanceState: Bundle?) {
1926
super.onCreate(savedInstanceState)
@@ -31,8 +38,7 @@ class MinimizedMenuActivity : ComponentActivity() {
3138
finish()
3239
},
3340
onDelete = {
34-
viewModel.delete()
35-
finish()
41+
viewModel.showSpatialDeleteConfirmation()
3642
},
3743
)
3844
}

0 commit comments

Comments
 (0)