Merge "Fix for unresponsive color picker collection" into udc-dev
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index 479bca2..c0e4124 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -63,7 +63,9 @@
wallpaperColorsViewModel: WallpaperColorsViewModel,
): ColorPickerViewModel.Factory
- fun getClockCarouselViewModel(context: Context): ClockCarouselViewModel
+ fun getClockCarouselViewModelFactory(
+ interactor: ClockPickerInteractor,
+ ): ClockCarouselViewModel.Factory
fun getClockViewFactory(activity: Activity): ClockViewFactory
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index da11d51..2d36db4 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -26,6 +26,7 @@
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.get
import com.android.customization.model.color.ColorCustomizationManager
import com.android.customization.model.color.ColorOptionsProvider
import com.android.customization.model.grid.GridOptionsManager
@@ -102,7 +103,7 @@
private var clockRegistry: ClockRegistry? = null
private var clockPickerInteractor: ClockPickerInteractor? = null
private var clockSectionViewModel: ClockSectionViewModel? = null
- private var clockCarouselViewModel: ClockCarouselViewModel? = null
+ private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null
private var clockViewFactory: ClockViewFactory? = null
private var notificationsInteractor: NotificationsInteractor? = null
private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
@@ -119,6 +120,14 @@
private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null
override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
+ val clockCarouselViewModel =
+ ViewModelProvider(
+ activity,
+ getClockCarouselViewModelFactory(
+ getClockPickerInteractor(activity.applicationContext),
+ ),
+ )
+ .get() as ClockCarouselViewModel
return customizationSections
?: DefaultCustomizationSections(
getColorPickerViewModelFactory(
@@ -131,7 +140,7 @@
interactor = getNotificationsInteractor(activity),
),
getFlags(),
- getClockCarouselViewModel(activity),
+ clockCarouselViewModel,
getClockViewFactory(activity),
getDarkModeSnapshotRestorer(activity),
getThemedIconSnapshotRestorer(activity),
@@ -346,10 +355,12 @@
}
}
- override fun getClockCarouselViewModel(context: Context): ClockCarouselViewModel {
- return clockCarouselViewModel
- ?: ClockCarouselViewModel(getClockPickerInteractor(context)).also {
- clockCarouselViewModel = it
+ override fun getClockCarouselViewModelFactory(
+ interactor: ClockPickerInteractor,
+ ): ClockCarouselViewModel.Factory {
+ return clockCarouselViewModelFactory
+ ?: ClockCarouselViewModel.Factory(interactor).also {
+ clockCarouselViewModelFactory = it
}
}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
index a2d5aec..d71e202 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
@@ -38,7 +38,7 @@
var isCarouselInTransition = false
- private val carousel: Carousel
+ val carousel: Carousel
private val motionLayout: MotionLayout
private lateinit var adapter: ClockCarouselAdapter
private lateinit var scalingUpClockController: ClockController
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
index c01f56a..34ccdb6 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -15,30 +15,39 @@
*/
package com.android.customization.picker.clock.ui.viewmodel
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
/**
* Clock carousel view model that provides data for the carousel of clock previews. When there is
* only one item, we should show a single clock preview instead of a carousel.
*/
-class ClockCarouselViewModel(
+class ClockCarouselViewModel
+constructor(
private val interactor: ClockPickerInteractor,
-) {
+) : ViewModel() {
@OptIn(ExperimentalCoroutinesApi::class)
- val allClockIds: Flow<List<String>> =
- interactor.allClocks.mapLatest { allClocks ->
- // Delay to avoid the case that the full list of clocks is not initiated.
- delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
- allClocks.map { it.clockId }
- }
+ val allClockIds: StateFlow<List<String>> =
+ interactor.allClocks
+ .mapLatest { allClocks ->
+ // Delay to avoid the case that the full list of clocks is not initiated.
+ delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+ allClocks.map { it.clockId }
+ }
+ .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
val seedColor: Flow<Int?> = interactor.seedColor
@@ -72,6 +81,18 @@
interactor.setSelectedClock(clockId)
}
+ class Factory(
+ private val interactor: ClockPickerInteractor,
+ ) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return ClockCarouselViewModel(
+ interactor = interactor,
+ )
+ as T
+ }
+ }
+
companion object {
const val CLOCKS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
}
diff --git a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
index 43fb85b..2f83fa7 100644
--- a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
+++ b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
@@ -20,8 +20,10 @@
import android.app.Activity
import android.content.Context
import android.view.View
+import android.view.View.OnAttachStateChangeListener
import android.view.ViewGroup
import android.view.ViewStub
+import androidx.constraintlayout.helper.widget.Carousel
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.android.customization.picker.clock.ui.binder.ClockCarouselViewBinder
@@ -39,6 +41,7 @@
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewView
import com.android.wallpaper.util.DisplayUtils
+import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
/** Controls the screen preview section. */
@@ -90,15 +93,39 @@
val singleClockViewStub: ViewStub = view.requireViewById(R.id.single_clock_view_stub)
singleClockViewStub.layoutResource = R.layout.single_clock_view
val singleClockView = singleClockViewStub.inflate() as ViewGroup
- lifecycleOwner.lifecycleScope.launch {
- ClockCarouselViewBinder.bind(
- carouselView = carouselView,
- singleClockView = singleClockView,
- viewModel = clockCarouselViewModel,
- clockViewFactory = clockViewFactory,
- lifecycleOwner = lifecycleOwner,
- )
- }
+
+ /**
+ * Only bind after [Carousel.onAttachedToWindow]. This is to avoid the race condition
+ * that the flow emits before attached to window where [Carousel.mMotionLayout] is still
+ * null.
+ */
+ var onAttachStateChangeListener: OnAttachStateChangeListener? = null
+ var bindJob: Job? = null
+ onAttachStateChangeListener =
+ object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(view: View?) {
+ bindJob =
+ lifecycleOwner.lifecycleScope.launch {
+ ClockCarouselViewBinder.bind(
+ carouselView = carouselView,
+ singleClockView = singleClockView,
+ viewModel = clockCarouselViewModel,
+ clockViewFactory = clockViewFactory,
+ lifecycleOwner = lifecycleOwner,
+ )
+ if (onAttachStateChangeListener != null) {
+ carouselView.carousel.removeOnAttachStateChangeListener(
+ onAttachStateChangeListener,
+ )
+ }
+ }
+ }
+
+ override fun onViewDetachedFromWindow(view: View?) {
+ bindJob?.cancel()
+ }
+ }
+ carouselView.carousel.addOnAttachStateChangeListener(onAttachStateChangeListener)
}
return view