summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Coco Duan <cocod@google.com> 2024-01-10 02:32:27 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-01-10 02:32:27 +0000
commit1609fc2887fecd4e93aa2a34b95b007a28ec3958 (patch)
tree99452db48d0661533ba94d91a7ee8981e5bce1d8
parente799141c5775987f368d0664e760a26065e5d9c6 (diff)
parentb55426b63c12f954b4a6648fc3e7ee82ad1a3c2b (diff)
Merge "Show popup message after dismissing the CTA tile" into main
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt45
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt46
5 files changed, 141 insertions, 2 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 d3d8e4ed3523..d76f0ff3ec18 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
@@ -45,6 +45,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material.icons.outlined.Widgets
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
@@ -76,10 +77,12 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.compose.ui.window.Popup
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
@@ -97,6 +100,8 @@ fun CommunalHub(
onEditDone: (() -> Unit)? = null,
) {
val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
+ val isPopupOnDismissCtaShowing by
+ viewModel.isPopupOnDismissCtaShowing.collectAsState(initial = false)
var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
@@ -133,6 +138,10 @@ fun CommunalHub(
}
}
+ if (isPopupOnDismissCtaShowing) {
+ PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta)
+ }
+
// This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
// touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
// swipe back to the blank scene.
@@ -319,8 +328,40 @@ private fun Toolbar(
}
@Composable
+private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) {
+ Popup(
+ alignment = Alignment.TopCenter,
+ offset = IntOffset(0, 40),
+ onDismissRequest = onHidePopupAfterDismissCta
+ ) {
+ val colors = LocalAndroidColorScheme.current
+ Row(
+ modifier =
+ Modifier.height(56.dp)
+ .background(colors.secondary, RoundedCornerShape(50.dp))
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.TouchApp,
+ contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary,
+ )
+ }
+ }
+}
+
+@Composable
private fun RemoveButtonContent(spacerModifier: Modifier) {
- Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
+ Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_remove_widget))
Spacer(spacerModifier)
Text(
text = stringResource(R.string.button_to_remove_widget),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 16e0bc00ad35..8e3f66464de6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -41,7 +42,9 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -50,6 +53,7 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalViewModelTest : SysuiTestCase() {
@@ -84,6 +88,7 @@ class CommunalViewModelTest : SysuiTestCase() {
underTest =
CommunalViewModel(
+ testScope,
withDeps.communalInteractor,
WidgetInteractionHandler(mock()),
withDeps.tutorialInteractor,
@@ -159,4 +164,44 @@ class CommunalViewModelTest : SysuiTestCase() {
assertThat(communalContent?.get(4))
.isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
+
+ @Test
+ fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ communalRepository.setCtaTileInViewModeVisibility(true)
+
+ val communalContent by collectLastValue(underTest.communalContent)
+ val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
+
+ assertThat(communalContent?.size).isEqualTo(1)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+
+ underTest.onDismissCtaTile()
+
+ // hide CTA tile and show the popup
+ assertThat(communalContent).isEmpty()
+ assertThat(isPopupOnDismissCtaShowing).isEqualTo(true)
+
+ // hide popup after time elapsed
+ advanceTimeBy(POPUP_AUTO_HIDE_TIMEOUT_MS)
+ assertThat(isPopupOnDismissCtaShowing).isEqualTo(false)
+ }
+
+ @Test
+ fun popup_onDismiss_hidesImmediately() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ communalRepository.setCtaTileInViewModeVisibility(true)
+
+ val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
+
+ underTest.onDismissCtaTile()
+ assertThat(isPopupOnDismissCtaShowing).isEqualTo(true)
+
+ // dismiss the popup directly
+ underTest.onHidePopupAfterDismissCta()
+ assertThat(isPopupOnDismissCtaShowing).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3f11faebffb1..5e109053c994 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1087,6 +1087,8 @@
<string name="cta_label_to_edit_widget">Add, remove, and reorder your widgets in this space</string>
<!-- Label for CTA tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
<string name="cta_label_to_open_widget_picker">Add more widgets</string>
+ <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
+ <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
<string name="button_to_remove_widget">Remove</string>
<!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4cb83a3b51dc..28f48ce1e647 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -30,6 +30,7 @@ import com.android.systemui.shade.ShadeViewController
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
/** The base view model for the communal hub. */
abstract class BaseCommunalViewModel(
@@ -96,6 +97,12 @@ abstract class BaseCommunalViewModel(
/** Whether in edit mode for the communal hub. */
open val isEditMode = false
+ /** Whether the popup message triggered by dismissing the CTA tile is showing. */
+ open val isPopupOnDismissCtaShowing: Flow<Boolean> = flowOf(false)
+
+ /** Hide the popup message triggered by dismissing the CTA tile. */
+ open fun onHidePopupAfterDismissCta() {}
+
/** Called as the UI requests deleting a widget. */
open fun onDeleteWidget(id: Int) {}
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 066e897cdfdb..1c696851bb70 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
@@ -23,23 +23,31 @@ import com.android.systemui.communal.domain.interactor.CommunalTutorialInteracto
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.widgets.WidgetInteractionHandler
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
import com.android.systemui.shade.ShadeViewController
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
@SysUISingleton
class CommunalViewModel
@Inject
constructor(
+ @Application private val scope: CoroutineScope,
private val communalInteractor: CommunalInteractor,
private val interactionHandler: WidgetInteractionHandler,
tutorialInteractor: CommunalTutorialInteractor,
@@ -63,9 +71,45 @@ constructor(
}
}
+ private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isPopupOnDismissCtaShowing: Flow<Boolean> =
+ _isPopupOnDismissCtaShowing.asStateFlow()
+
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
- override fun onDismissCtaTile() = communalInteractor.dismissCtaTile()
+ override fun onDismissCtaTile() {
+ communalInteractor.dismissCtaTile()
+ setPopupOnDismissCtaVisibility(true)
+ schedulePopupHiding()
+ }
override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
+
+ override fun onHidePopupAfterDismissCta() {
+ cancelDelayedPopupHiding()
+ setPopupOnDismissCtaVisibility(false)
+ }
+
+ private fun setPopupOnDismissCtaVisibility(isVisible: Boolean) {
+ _isPopupOnDismissCtaShowing.value = isVisible
+ }
+
+ private var delayedHidePopupJob: Job? = null
+ private fun schedulePopupHiding() {
+ cancelDelayedPopupHiding()
+ delayedHidePopupJob =
+ scope.launch {
+ delay(POPUP_AUTO_HIDE_TIMEOUT_MS)
+ onHidePopupAfterDismissCta()
+ }
+ }
+
+ private fun cancelDelayedPopupHiding() {
+ delayedHidePopupJob?.cancel()
+ delayedHidePopupJob = null
+ }
+
+ companion object {
+ const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
+ }
}