Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +22 −1 Original line number Diff line number Diff line Loading @@ -94,6 +94,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier Loading Loading @@ -171,7 +172,11 @@ fun CommunalHub( var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } val gridState = rememberLazyGridState() val gridState = rememberLazyGridState(viewModel.savedFirstScrollIndex, viewModel.savedFirstScrollOffset) viewModel.clearPersistedScrollPosition() val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel) val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle() val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle() Loading @@ -187,6 +192,8 @@ fun CommunalHub( val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() ObserveScrollEffect(gridState, viewModel) if (!viewModel.isEditMode) { ScrollOnUpdatedLiveContentEffect(communalContent, gridState) } Loading Loading @@ -420,6 +427,20 @@ private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) { } } @Composable private fun ObserveScrollEffect( gridState: LazyGridState, communalViewModel: BaseCommunalViewModel ) { LaunchedEffect(gridState) { snapshotFlow { Pair(gridState.firstVisibleItemIndex, gridState.firstVisibleItemScrollOffset) } .collect { communalViewModel.onScrollPositionUpdated(it.first, it.second) } } } /** * Observes communal content and scrolls to any added or updated live content, e.g. a new media * session is started, or a paused timer is resumed. Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +16 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalPrefsInteractor Loading Loading @@ -76,6 +77,8 @@ import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.kotlin.eq import org.mockito.kotlin.spy @SmallTest @RunWith(AndroidJUnit4::class) Loading @@ -94,6 +97,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var communalSceneInteractor: CommunalSceneInteractor private lateinit var communalInteractor: CommunalInteractor private val testableResources = context.orCreateTestableResources Loading @@ -108,6 +112,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository mediaRepository = kosmos.fakeCommunalMediaRepository communalSceneInteractor = kosmos.communalSceneInteractor communalInteractor = spy(kosmos.communalInteractor) kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) kosmos.fakeUserTracker.set( userInfos = listOf(MAIN_USER_INFO), Loading @@ -119,7 +124,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { underTest = CommunalEditModeViewModel( communalSceneInteractor, kosmos.communalInteractor, communalInteractor, kosmos.communalSettingsInteractor, kosmos.keyguardTransitionInteractor, mediaHost, Loading Loading @@ -346,6 +351,16 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { assertThat(showDisclaimer).isFalse() } @Test fun scrollPosition_persistedOnEditCleanup() { val index = 2 val offset = 30 underTest.onScrollPositionUpdated(index, offset) underTest.cleanupEditModeState() verify(communalInteractor).setScrollPosition(eq(index), eq(offset)) } private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name" Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +17 −1 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor Loading Loading @@ -97,7 +98,9 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters Loading @@ -121,6 +124,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private lateinit var shadeTestUtil: ShadeTestUtil private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var communalRepository: FakeCommunalSceneRepository private lateinit var communalInteractor: CommunalInteractor private lateinit var underTest: CommunalViewModel Loading Loading @@ -154,6 +158,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.powerInteractor.setAwakeForTest() communalInteractor = spy(kosmos.communalInteractor) underTest = CommunalViewModel( kosmos.testDispatcher, Loading @@ -164,7 +170,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.keyguardInteractor, mock<KeyguardIndicationController>(), kosmos.communalSceneInteractor, kosmos.communalInteractor, communalInteractor, kosmos.communalSettingsInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, Loading Loading @@ -779,6 +785,16 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java) } @Test fun scrollPosition_persistedOnEditEntry() { val index = 2 val offset = 30 underTest.onScrollPositionUpdated(index, offset) underTest.onOpenWidgetEditor(false) verify(communalInteractor).setScrollPosition(eq(index), eq(offset)) } private suspend fun setIsMainUser(isMainUser: Boolean) { val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO with(userRepository) { Loading packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +25 −0 Original line number Diff line number Diff line Loading @@ -541,4 +541,29 @@ constructor( ) } } /** * {@link #setScrollPosition} persists the current communal grid scroll position (to volatile * memory) so that the next presentation of the grid (either as glanceable hub or edit mode) can * restore position. */ fun setScrollPosition(firstVisibleItemIndex: Int, firstVisibleItemOffset: Int) { _firstVisibleItemIndex = firstVisibleItemIndex _firstVisibleItemOffset = firstVisibleItemOffset } fun resetScrollPosition() { _firstVisibleItemIndex = 0 _firstVisibleItemOffset = 0 } val firstVisibleItemIndex: Int get() = _firstVisibleItemIndex private var _firstVisibleItemIndex: Int = 0 val firstVisibleItemOffset: Int get() = _firstVisibleItemOffset private var _firstVisibleItemOffset: Int = 0 } packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +34 −0 Original line number Diff line number Diff line Loading @@ -59,6 +59,18 @@ abstract class BaseCommunalViewModel( /** Accessibility delegate to be set on CommunalAppWidgetHostView. */ open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null /** * The up-to-date value of the grid scroll offset. persisted to interactor on * {@link #persistScrollPosition} */ private var currentScrollOffset = 0 /** * The up-to-date value of the grid scroll index. persisted to interactor on * {@link #persistScrollPosition} */ private var currentScrollIndex = 0 fun signalUserInteraction() { communalInteractor.signalUserInteraction() } Loading Loading @@ -147,6 +159,28 @@ abstract class BaseCommunalViewModel( /** Called as the user request to show the customize widget button. */ open fun onLongClick() {} /** Called when the grid scroll position has been updated. */ open fun onScrollPositionUpdated(firstVisibleItemIndex: Int, firstVisibleItemScroll: Int) { currentScrollIndex = firstVisibleItemIndex currentScrollOffset = firstVisibleItemScroll } /** Stores scroll values to interactor. */ protected fun persistScrollPosition() { communalInteractor.setScrollPosition(currentScrollIndex, currentScrollOffset) } /** Invoked after scroll values are used to initialize grid position. */ open fun clearPersistedScrollPosition() { communalInteractor.setScrollPosition(0, 0) } val savedFirstScrollIndex: Int get() = communalInteractor.firstVisibleItemIndex val savedFirstScrollOffset: Int get() = communalInteractor.firstVisibleItemOffset /** Set the key of the currently selected item */ fun setSelectedKey(key: String?) { _selectedKey.value = key Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +22 −1 Original line number Diff line number Diff line Loading @@ -94,6 +94,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier Loading Loading @@ -171,7 +172,11 @@ fun CommunalHub( var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } val gridState = rememberLazyGridState() val gridState = rememberLazyGridState(viewModel.savedFirstScrollIndex, viewModel.savedFirstScrollOffset) viewModel.clearPersistedScrollPosition() val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel) val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle() val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle() Loading @@ -187,6 +192,8 @@ fun CommunalHub( val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() ObserveScrollEffect(gridState, viewModel) if (!viewModel.isEditMode) { ScrollOnUpdatedLiveContentEffect(communalContent, gridState) } Loading Loading @@ -420,6 +427,20 @@ private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) { } } @Composable private fun ObserveScrollEffect( gridState: LazyGridState, communalViewModel: BaseCommunalViewModel ) { LaunchedEffect(gridState) { snapshotFlow { Pair(gridState.firstVisibleItemIndex, gridState.firstVisibleItemScrollOffset) } .collect { communalViewModel.onScrollPositionUpdated(it.first, it.second) } } } /** * Observes communal content and scrolls to any added or updated live content, e.g. a new media * session is started, or a paused timer is resumed. Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +16 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalPrefsInteractor Loading Loading @@ -76,6 +77,8 @@ import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.kotlin.eq import org.mockito.kotlin.spy @SmallTest @RunWith(AndroidJUnit4::class) Loading @@ -94,6 +97,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var communalSceneInteractor: CommunalSceneInteractor private lateinit var communalInteractor: CommunalInteractor private val testableResources = context.orCreateTestableResources Loading @@ -108,6 +112,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository mediaRepository = kosmos.fakeCommunalMediaRepository communalSceneInteractor = kosmos.communalSceneInteractor communalInteractor = spy(kosmos.communalInteractor) kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) kosmos.fakeUserTracker.set( userInfos = listOf(MAIN_USER_INFO), Loading @@ -119,7 +124,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { underTest = CommunalEditModeViewModel( communalSceneInteractor, kosmos.communalInteractor, communalInteractor, kosmos.communalSettingsInteractor, kosmos.keyguardTransitionInteractor, mediaHost, Loading Loading @@ -346,6 +351,16 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { assertThat(showDisclaimer).isFalse() } @Test fun scrollPosition_persistedOnEditCleanup() { val index = 2 val offset = 30 underTest.onScrollPositionUpdated(index, offset) underTest.cleanupEditModeState() verify(communalInteractor).setScrollPosition(eq(index), eq(offset)) } private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name" Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +17 −1 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor Loading Loading @@ -97,7 +98,9 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters Loading @@ -121,6 +124,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private lateinit var shadeTestUtil: ShadeTestUtil private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var communalRepository: FakeCommunalSceneRepository private lateinit var communalInteractor: CommunalInteractor private lateinit var underTest: CommunalViewModel Loading Loading @@ -154,6 +158,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.powerInteractor.setAwakeForTest() communalInteractor = spy(kosmos.communalInteractor) underTest = CommunalViewModel( kosmos.testDispatcher, Loading @@ -164,7 +170,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.keyguardInteractor, mock<KeyguardIndicationController>(), kosmos.communalSceneInteractor, kosmos.communalInteractor, communalInteractor, kosmos.communalSettingsInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, Loading Loading @@ -779,6 +785,16 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java) } @Test fun scrollPosition_persistedOnEditEntry() { val index = 2 val offset = 30 underTest.onScrollPositionUpdated(index, offset) underTest.onOpenWidgetEditor(false) verify(communalInteractor).setScrollPosition(eq(index), eq(offset)) } private suspend fun setIsMainUser(isMainUser: Boolean) { val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO with(userRepository) { Loading
packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +25 −0 Original line number Diff line number Diff line Loading @@ -541,4 +541,29 @@ constructor( ) } } /** * {@link #setScrollPosition} persists the current communal grid scroll position (to volatile * memory) so that the next presentation of the grid (either as glanceable hub or edit mode) can * restore position. */ fun setScrollPosition(firstVisibleItemIndex: Int, firstVisibleItemOffset: Int) { _firstVisibleItemIndex = firstVisibleItemIndex _firstVisibleItemOffset = firstVisibleItemOffset } fun resetScrollPosition() { _firstVisibleItemIndex = 0 _firstVisibleItemOffset = 0 } val firstVisibleItemIndex: Int get() = _firstVisibleItemIndex private var _firstVisibleItemIndex: Int = 0 val firstVisibleItemOffset: Int get() = _firstVisibleItemOffset private var _firstVisibleItemOffset: Int = 0 }
packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +34 −0 Original line number Diff line number Diff line Loading @@ -59,6 +59,18 @@ abstract class BaseCommunalViewModel( /** Accessibility delegate to be set on CommunalAppWidgetHostView. */ open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null /** * The up-to-date value of the grid scroll offset. persisted to interactor on * {@link #persistScrollPosition} */ private var currentScrollOffset = 0 /** * The up-to-date value of the grid scroll index. persisted to interactor on * {@link #persistScrollPosition} */ private var currentScrollIndex = 0 fun signalUserInteraction() { communalInteractor.signalUserInteraction() } Loading Loading @@ -147,6 +159,28 @@ abstract class BaseCommunalViewModel( /** Called as the user request to show the customize widget button. */ open fun onLongClick() {} /** Called when the grid scroll position has been updated. */ open fun onScrollPositionUpdated(firstVisibleItemIndex: Int, firstVisibleItemScroll: Int) { currentScrollIndex = firstVisibleItemIndex currentScrollOffset = firstVisibleItemScroll } /** Stores scroll values to interactor. */ protected fun persistScrollPosition() { communalInteractor.setScrollPosition(currentScrollIndex, currentScrollOffset) } /** Invoked after scroll values are used to initialize grid position. */ open fun clearPersistedScrollPosition() { communalInteractor.setScrollPosition(0, 0) } val savedFirstScrollIndex: Int get() = communalInteractor.firstVisibleItemIndex val savedFirstScrollOffset: Int get() = communalInteractor.firstVisibleItemOffset /** Set the key of the currently selected item */ fun setSelectedKey(key: String?) { _selectedKey.value = key Loading