diff options
11 files changed, 324 insertions, 7 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 87a8c35388fa..31cc2e80e121 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -3,12 +3,15 @@ package com.android.systemui.communal.ui.compose import android.appwidget.AppWidgetHostView import android.os.Bundle import android.util.SizeF +import android.widget.FrameLayout import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid @@ -25,6 +28,8 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.model.CommunalContentUiModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.media.controls.ui.MediaHierarchyManager +import com.android.systemui.media.controls.ui.MediaHostState @Composable fun CommunalHub( @@ -33,6 +38,7 @@ fun CommunalHub( ) { val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false) val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList()) + val isMediaPlaying by viewModel.isMediaPlaying.collectAsState(initial = false) Box( modifier = modifier.fillMaxSize().background(Color.White), ) { @@ -68,6 +74,33 @@ fun CommunalHub( } } } + + if (isMediaPlaying) { + Box( + modifier = + Modifier.width(Dimensions.CardWidth) + .height(Dimensions.CardHeightThird) + .padding(Dimensions.Spacing) + ) { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { + viewModel.mediaHost.expansion = MediaHostState.EXPANDED + viewModel.mediaHost.showsOnlyActiveMedia = false + viewModel.mediaHost.falsingProtectionNeeded = false + viewModel.mediaHost.init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB) + viewModel.mediaHost.hostView.layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + viewModel.mediaHost.hostView + }, + // For reusing composition in lazy lists. + onReset = {}, + ) + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index b8e2de404628..e3bd829029cf 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.dagger +import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule @@ -24,6 +25,7 @@ import dagger.Module @Module( includes = [ + CommunalMediaRepositoryModule::class, CommunalRepositoryModule::class, CommunalTutorialRepositoryModule::class, CommunalWidgetRepositoryModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt new file mode 100644 index 000000000000..e41c32261c11 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.controls.models.player.MediaData +import com.android.systemui.media.controls.pipeline.MediaDataManager +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart + +/** Encapsulates the state of smartspace in communal. */ +interface CommunalMediaRepository { + val mediaPlaying: Flow<Boolean> +} + +@SysUISingleton +class CommunalMediaRepositoryImpl +@Inject +constructor( + private val mediaDataManager: MediaDataManager, +) : CommunalMediaRepository { + + private val mediaDataListener = + object : MediaDataManager.Listener { + override fun onMediaDataLoaded( + key: String, + oldKey: String?, + data: MediaData, + immediately: Boolean, + receivedSmartspaceCardLatency: Int, + isSsReactivated: Boolean + ) { + if (!mediaDataManager.hasAnyMediaOrRecommendation()) { + return + } + _mediaPlaying.value = true + } + + override fun onMediaDataRemoved(key: String) { + if (mediaDataManager.hasAnyMediaOrRecommendation()) { + return + } + _mediaPlaying.value = false + } + } + + private val _mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false) + + override val mediaPlaying: Flow<Boolean> = + _mediaPlaying + .onStart { + mediaDataManager.addListener(mediaDataListener) + _mediaPlaying.value = mediaDataManager.hasAnyMediaOrRecommendation() + } + .onCompletion { mediaDataManager.removeListener(mediaDataListener) } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryModule.kt new file mode 100644 index 000000000000..2c6d9e4c39c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryModule.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import dagger.Binds +import dagger.Module + +@Module +interface CommunalMediaRepositoryModule { + @Binds fun communalMediaRepository(impl: CommunalMediaRepositoryImpl): CommunalMediaRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index ccccbb67c6c0..42d967782bb0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.domain.interactor +import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo @@ -34,6 +35,7 @@ class CommunalInteractor constructor( private val communalRepository: CommunalRepository, widgetRepository: CommunalWidgetRepository, + mediaRepository: CommunalMediaRepository, ) { /** Whether communal features are enabled. */ @@ -51,6 +53,9 @@ constructor( */ val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets + /** A flow indicating whether or not media is playing. */ + val mediaPlaying: Flow<Boolean> = mediaRepository.mediaPlaying + /** * Target scene as requested by the underlying [SceneTransitionLayout] or through * [onSceneChanged]. diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index de9b56364c24..4b32c321e504 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -24,7 +24,10 @@ import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.model.CommunalContentUiModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.media.controls.ui.MediaHost +import com.android.systemui.media.dagger.MediaModule.COMMUNAL_HUB import javax.inject.Inject +import javax.inject.Named import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -36,6 +39,7 @@ constructor( @Application private val context: Context, private val appWidgetHost: AppWidgetHost, private val communalInteractor: CommunalInteractor, + @Named(COMMUNAL_HUB) val mediaHost: MediaHost, tutorialInteractor: CommunalTutorialInteractor, ) { /** Whether communal hub should show tutorial content. */ @@ -57,6 +61,8 @@ constructor( } } + val isMediaPlaying = communalInteractor.mediaPlaying + val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene fun onSceneChanged(scene: CommunalSceneKey) { communalInteractor.onSceneChanged(scene) diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt new file mode 100644 index 000000000000..455f9865edf3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.media.controls.models.player.MediaData +import com.android.systemui.media.controls.pipeline.MediaDataManager +import com.android.systemui.util.mockito.KotlinArgumentCaptor +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalMediaRepositoryImplTest : SysuiTestCase() { + @Mock private lateinit var mediaDataManager: MediaDataManager + @Mock private lateinit var mediaData: MediaData + + private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy { + KotlinArgumentCaptor(MediaDataManager.Listener::class.java) + } + + private lateinit var mediaRepository: CommunalMediaRepository + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun mediaPlaying_defaultsToFalse() = + testScope.runTest { + mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager) + + val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying) + runCurrent() + assertThat(isMediaPlaying()).isFalse() + } + + @Test + fun mediaPlaying_emitsInitialValue() = + testScope.runTest { + // Start with media available. + whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true) + + mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager) + + val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying) + runCurrent() + assertThat(isMediaPlaying()).isTrue() + } + + @Test + fun mediaPlaying_updatesWhenMediaDataLoaded() = + testScope.runTest { + mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager) + + // Initial value is false. + var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying) + runCurrent() + assertThat(isMediaPlaying()).isFalse() + + // Listener is added + verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture()) + + // Change to media available and notify the listener. + whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true) + mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData) + + // mediaPlaying now returns true. + isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying) + runCurrent() + assertThat(isMediaPlaying()).isTrue() + } + + @Test + fun mediaPlaying_updatesWhenMediaDataRemoved() = + testScope.runTest { + // Start with media available. + whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true) + + mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager) + + // Initial value is true. + var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying) + runCurrent() + assertThat(isMediaPlaying()).isTrue() + + // Listener is added. + verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture()) + + // Change to media unavailable and notify the listener. + whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false) + mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData) + + // mediaPlaying now returns false. + isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying) + runCurrent() + assertThat(isMediaPlaying()).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 2f17b6fa35ed..4a089a2d9476 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.communal.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo @@ -46,6 +47,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var communalRepository: FakeCommunalRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository + private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var interactor: CommunalInteractor @Before @@ -55,7 +57,8 @@ class CommunalInteractorTest : SysuiTestCase() { testScope = TestScope() communalRepository = FakeCommunalRepository() widgetRepository = FakeCommunalWidgetRepository() - interactor = CommunalInteractor(communalRepository, widgetRepository) + mediaRepository = FakeCommunalMediaRepository() + interactor = CommunalInteractor(communalRepository, widgetRepository, mediaRepository) } @Test @@ -75,7 +78,8 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { communalRepository.setIsCommunalEnabled(true) - val interactor = CommunalInteractor(communalRepository, widgetRepository) + val interactor = + CommunalInteractor(communalRepository, widgetRepository, mediaRepository) assertThat(interactor.isCommunalEnabled).isTrue() } @@ -84,14 +88,16 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { communalRepository.setIsCommunalEnabled(false) - val interactor = CommunalInteractor(communalRepository, widgetRepository) + val interactor = + CommunalInteractor(communalRepository, widgetRepository, mediaRepository) assertThat(interactor.isCommunalEnabled).isFalse() } @Test fun listensToSceneChange() = testScope.runTest { - val interactor = CommunalInteractor(communalRepository, widgetRepository) + val interactor = + CommunalInteractor(communalRepository, widgetRepository, mediaRepository) var desiredScene = collectLastValue(interactor.desiredScene) runCurrent() assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank) @@ -106,7 +112,8 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun updatesScene() = testScope.runTest { - val interactor = CommunalInteractor(communalRepository, widgetRepository) + val interactor = + CommunalInteractor(communalRepository, widgetRepository, mediaRepository) val targetScene = CommunalSceneKey.Communal interactor.onSceneChanged(targetScene) @@ -119,7 +126,8 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun isCommunalShowing() = testScope.runTest { - val interactor = CommunalInteractor(communalRepository, widgetRepository) + val interactor = + CommunalInteractor(communalRepository, widgetRepository, mediaRepository) var isCommunalShowing = collectLastValue(interactor.isCommunalShowing) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 5bfe56931bb4..2023de363100 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -25,6 +25,7 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -106,7 +107,11 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private val configurationController = FakeConfigurationController() private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true) private val communalInteractor = - CommunalInteractor(communalRepository, FakeCommunalWidgetRepository()) + CommunalInteractor( + communalRepository, + FakeCommunalWidgetRepository(), + FakeCommunalMediaRepository() + ) private val notifPanelEvents = ShadeExpansionStateManager() private val settings = FakeSettings() private lateinit var testableLooper: TestableLooper diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt new file mode 100644 index 000000000000..3ab1b6c11e02 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeCommunalMediaRepository( + override val mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false) +) : CommunalMediaRepository diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index bdddc042009d..fa060d3bfb1a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -34,6 +34,7 @@ import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.common.shared.model.Text import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -102,6 +103,9 @@ class SceneTestUtils( private val communalWidgetRepository: FakeCommunalWidgetRepository by lazy { FakeCommunalWidgetRepository() } + private val communalMediaRepository: FakeCommunalMediaRepository by lazy { + FakeCommunalMediaRepository() + } val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() } val powerRepository: FakePowerRepository by lazy { FakePowerRepository() } @@ -205,6 +209,7 @@ class SceneTestUtils( return CommunalInteractor( communalRepository = communalRepository, widgetRepository = communalWidgetRepository, + mediaRepository = communalMediaRepository, ) } |