Merge "Revert "BC25 Preview current wallpapers (2/2)"" into main
diff --git a/res/drawable/ic_colors.xml b/res/drawable/ic_colors.xml
new file mode 100644
index 0000000..31bf4d9
--- /dev/null
+++ b/res/drawable/ic_colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ ~
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M346,820L100,574Q90,564 85,552Q80,540 80,527Q80,514 85,502Q90,490 100,480L330,251L224,145L286,80L686,480Q696,490 700.5,502Q705,514 705,527Q705,540 700.5,552Q696,564 686,574L440,820Q430,830 418,835Q406,840 393,840Q380,840 368,835Q356,830 346,820ZM393,314L179,528Q179,528 179,528Q179,528 179,528L607,528Q607,528 607,528Q607,528 607,528L393,314ZM792,840Q756,840 731,814.5Q706,789 706,752Q706,725 719.5,701Q733,677 750,654L792,600L836,654Q852,677 866,701Q880,725 880,752Q880,789 854,814.5Q828,840 792,840Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/floating_sheet_colors.xml b/res/layout/floating_sheet_colors.xml
new file mode 100644
index 0000000..8e6ed64
--- /dev/null
+++ b/res/layout/floating_sheet_colors.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="@dimen/floating_sheet_horizontal_padding"
+ android:orientation="vertical"
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/floating_sheet_content_background"
+ android:paddingVertical="@dimen/floating_sheet_list_vertical_padding"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/color_type_tab_subhead"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:layout_marginHorizontal="20dp"
+ android:gravity="center"
+ android:text="@string/wallpaper_color_subheader"
+ android:textColor="@color/system_on_surface"
+ android:textSize="12sp" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/colors_horizontal_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ android:clipChildren="false"
+ android:clipToPadding="false" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:gravity="center_vertical"
+ android:layout_marginHorizontal="20dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/dark_mode_toggle_title"
+ style="@style/SectionTitleTextStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/mode_title" />
+
+ <Switch
+ android:id="@+id/dark_mode_toggle"
+ style="@style/Switch.SettingsLib"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@null"
+ android:clickable="false"
+ android:focusable="false"
+ android:minHeight="0dp" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar
+ android:id="@+id/floating_bar_tabs"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginVertical="@dimen/floating_sheet_tab_toolbar_vertical_margin" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/floating_sheet_shortcut.xml b/res/layout/floating_sheet_shortcut.xml
index 0c5c1eb..44a9b6d 100644
--- a/res/layout/floating_sheet_shortcut.xml
+++ b/res/layout/floating_sheet_shortcut.xml
@@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingHorizontal="@dimen/shortcut_floating_sheet_horizontal_padding"
+ android:paddingHorizontal="@dimen/floating_sheet_horizontal_padding"
android:paddingBottom="16dp"
android:orientation="vertical"
android:clipToPadding="false"
@@ -26,7 +26,7 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingVertical="@dimen/shortcut_floating_sheet_list_vertical_padding"
+ android:paddingVertical="@dimen/floating_sheet_list_vertical_padding"
android:background="@drawable/floating_sheet_content_background"
android:clipToPadding="false"
android:clipChildren="false">
@@ -44,5 +44,5 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
- android:layout_marginVertical="@dimen/shortcut_floating_sheet_tab_toolbar_vertical_margin" />
+ android:layout_marginVertical="@dimen/floating_sheet_tab_toolbar_vertical_margin" />
</LinearLayout>
\ No newline at end of file
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 382224a..059eabb 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -23,7 +23,7 @@
<string name="clock_description" msgid="3563839327378948">"घड़ी आइकॉन चुनें"</string>
<string name="clock_picker_entry_content_description" msgid="8377139273468595734">"पसंद के मुताबिक घड़ी का आइकॉन बदलें"</string>
<string name="select_clock_action_description" msgid="5025888763471843648">"स्मार्टवॉच की स्क्रीन के डिज़ाइन के लिए विकल्प <xliff:g id="ID_1">%1$s</xliff:g>"</string>
- <string name="clock_settings_title" msgid="2050906379377120431">"घड़ी का रंग और साइज़"</string>
+ <string name="clock_settings_title" msgid="2050906379377120431">"वॉच का रंग और साइज़"</string>
<string name="clock_color_and_size_title" msgid="7146791234905111351">"घड़ी का रंग और साइज़"</string>
<string name="clock_color_and_size_description" msgid="6578061553012886817">"<xliff:g id="ID_1">%1$s</xliff:g>, <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="clock_color" msgid="8081608867289156163">"रंग"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2d6fedd..a643c0a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -175,8 +175,8 @@
<!-- Notification item dimensions -->
<dimen name="notification_section_title_padding">8dp</dimen>
- <!-- Shortcut floating sheet dimensions -->
- <dimen name="shortcut_floating_sheet_list_vertical_padding">20dp</dimen>
- <dimen name="shortcut_floating_sheet_horizontal_padding">16dp</dimen>
- <dimen name="shortcut_floating_sheet_tab_toolbar_vertical_margin">8dp</dimen>
+ <!-- Floating sheet dimensions -->
+ <dimen name="floating_sheet_list_vertical_padding">20dp</dimen>
+ <dimen name="floating_sheet_horizontal_padding">16dp</dimen>
+ <dimen name="floating_sheet_tab_toolbar_vertical_margin">8dp</dimen>
</resources>
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index d761598..2696b97 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -22,7 +22,6 @@
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
-import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
import com.android.systemui.shared.clocks.ClockRegistry
@@ -40,15 +39,7 @@
fun getClockPickerInteractor(context: Context): ClockPickerInteractor
- fun getColorPickerInteractor(
- context: Context,
- wallpaperColorsRepository: WallpaperColorsRepository,
- ): ColorPickerInteractor
-
- fun getColorPickerViewModelFactory(
- context: Context,
- wallpaperColorsRepository: WallpaperColorsRepository,
- ): ColorPickerViewModel.Factory
+ fun getColorPickerViewModelFactory(context: Context): ColorPickerViewModel.Factory
fun getClockCarouselViewModelFactory(
interactor: ClockPickerInteractor,
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index a9aa182..c08c4e5 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -47,7 +47,6 @@
import com.android.customization.picker.clock.ui.view.ClockViewFactoryImpl
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
-import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
@@ -105,9 +104,7 @@
private var clockPickerSnapshotRestorer: ClockPickerSnapshotRestorer? = null
private var notificationSettingsInteractor: NotificationSettingsInteractor? = null
private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
- private var colorPickerInteractor: ColorPickerInteractor? = null
private var colorPickerViewModelFactory: ColorPickerViewModel.Factory? = null
- private var colorPickerSnapshotRestorer: ColorPickerSnapshotRestorer? = null
private var colorCustomizationManager: ColorCustomizationManager? = null
private var darkModeSnapshotRestorer: DarkModeSnapshotRestorer? = null
private var themedIconSnapshotRestorer: ThemedIconSnapshotRestorer? = null
@@ -128,6 +125,8 @@
lateinit var keyguardQuickAffordanceSnapshotRestorer:
Lazy<KeyguardQuickAffordanceSnapshotRestorer>
@Inject lateinit var themesUserEventLogger: Lazy<ThemesUserEventLogger>
+ @Inject lateinit var colorPickerInteractor: Lazy<ColorPickerInteractor>
+ @Inject lateinit var colorPickerSnapshotRestorer: Lazy<ColorPickerSnapshotRestorer>
override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
val appContext = activity.applicationContext
@@ -135,10 +134,7 @@
val resources = activity.resources
return customizationSections
?: DefaultCustomizationSections(
- getColorPickerViewModelFactory(
- context = appContext,
- wallpaperColorsRepository = getWallpaperColorsRepository(),
- ),
+ getColorPickerViewModelFactory(appContext),
getKeyguardQuickAffordancePickerViewModelFactory(appContext),
colorContrastSectionViewModelFactory.get(),
getNotificationSectionViewModelFactory(appContext),
@@ -151,7 +147,7 @@
clockViewFactory,
getThemedIconSnapshotRestorer(appContext),
getThemedIconInteractor(),
- getColorPickerInteractor(appContext, getWallpaperColorsRepository()),
+ colorPickerInteractor.get(),
getUserEventLogger(),
)
.also { customizationSections = it }
@@ -192,8 +188,7 @@
this[KEY_DARK_MODE_SNAPSHOT_RESTORER] = getDarkModeSnapshotRestorer(context)
this[KEY_THEMED_ICON_SNAPSHOT_RESTORER] = getThemedIconSnapshotRestorer(context)
this[KEY_APP_GRID_SNAPSHOT_RESTORER] = getGridSnapshotRestorer(context)
- this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] =
- getColorPickerSnapshotRestorer(context, getWallpaperColorsRepository())
+ this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] = colorPickerSnapshotRestorer.get()
this[KEY_CLOCKS_SNAPSHOT_RESTORER] = getClockPickerSnapshotRestorer(context)
}
}
@@ -375,49 +370,16 @@
return ThemedWallpaperColorResources(wallpaperColors, getSecureSettingsRepository(context))
}
- override fun getColorPickerInteractor(
- context: Context,
- wallpaperColorsRepository: WallpaperColorsRepository,
- ): ColorPickerInteractor {
- val appContext = context.applicationContext
- return colorPickerInteractor
- ?: ColorPickerInteractor(
- repository =
- ColorPickerRepositoryImpl(
- wallpaperColorsRepository,
- getColorCustomizationManager(appContext)
- ),
- snapshotRestorer = {
- getColorPickerSnapshotRestorer(appContext, wallpaperColorsRepository)
- }
- )
- .also { colorPickerInteractor = it }
- }
-
- override fun getColorPickerViewModelFactory(
- context: Context,
- wallpaperColorsRepository: WallpaperColorsRepository,
- ): ColorPickerViewModel.Factory {
+ override fun getColorPickerViewModelFactory(context: Context): ColorPickerViewModel.Factory {
return colorPickerViewModelFactory
?: ColorPickerViewModel.Factory(
context.applicationContext,
- getColorPickerInteractor(context, wallpaperColorsRepository),
+ colorPickerInteractor.get(),
getUserEventLogger(),
)
.also { colorPickerViewModelFactory = it }
}
- private fun getColorPickerSnapshotRestorer(
- context: Context,
- wallpaperColorsRepository: WallpaperColorsRepository,
- ): ColorPickerSnapshotRestorer {
- return colorPickerSnapshotRestorer
- ?: ColorPickerSnapshotRestorer(
- getColorPickerInteractor(context, wallpaperColorsRepository)
- )
- .also { colorPickerSnapshotRestorer = it }
- }
-
private fun getColorCustomizationManager(context: Context): ColorCustomizationManager {
return colorCustomizationManager
?: ColorCustomizationManager.getInstance(context, OverlayManagerCompat(context)).also {
@@ -470,10 +432,7 @@
?: ClockSettingsViewModel.Factory(
context.applicationContext,
getClockPickerInteractor(context),
- getColorPickerInteractor(
- context,
- wallpaperColorsRepository,
- ),
+ colorPickerInteractor.get(),
getUserEventLogger(),
) { clockId ->
clockId?.let { clockViewFactory.getController(clockId).config.isReactiveToTone }
diff --git a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
index 942a846..f5b4ac5 100644
--- a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
@@ -26,6 +26,8 @@
import com.android.systemui.monet.Style
import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
+import javax.inject.Inject
+import javax.inject.Singleton
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -36,7 +38,10 @@
// TODO (b/262924623): refactor to remove dependency on ColorCustomizationManager & ColorOption
// TODO (b/268203200): Create test for ColorPickerRepositoryImpl
-class ColorPickerRepositoryImpl(
+@Singleton
+class ColorPickerRepositoryImpl
+@Inject
+constructor(
wallpaperColorsRepository: WallpaperColorsRepository,
private val colorManager: ColorCustomizationManager,
) : ColorPickerRepository {
diff --git a/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt b/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt
index e7759ce..aebc6c2 100644
--- a/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt
+++ b/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt
@@ -18,15 +18,19 @@
import com.android.customization.picker.color.data.repository.ColorPickerRepository
import com.android.customization.picker.color.shared.model.ColorOptionModel
-import javax.inject.Provider
+import javax.inject.Inject
+import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.onEach
/** Single entry-point for all application state and business logic related to system color. */
-class ColorPickerInteractor(
+@Singleton
+class ColorPickerInteractor
+@Inject
+constructor(
private val repository: ColorPickerRepository,
- private val snapshotRestorer: Provider<ColorPickerSnapshotRestorer>,
+ private val snapshotRestorer: ColorPickerSnapshotRestorer,
) {
val isApplyingSystemColor = repository.isApplyingSystemColor
@@ -51,7 +55,7 @@
// actually updated until the picker restarts. Wait to do so when updated color options
// become available
repository.select(colorOptionModel)
- snapshotRestorer.get().storeSnapshot(colorOptionModel)
+ snapshotRestorer.storeSnapshot(colorOptionModel)
} catch (e: Exception) {
_selectingColorOption.value = null
}
diff --git a/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt b/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt
index dce59eb..656663c 100644
--- a/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt
+++ b/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt
@@ -18,14 +18,20 @@
package com.android.customization.picker.color.domain.interactor
import android.util.Log
+import com.android.customization.picker.color.data.repository.ColorPickerRepository
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotStore
import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot
+import javax.inject.Inject
+import javax.inject.Singleton
/** Handles state restoration for the color picker system. */
-class ColorPickerSnapshotRestorer(
- private val interactor: ColorPickerInteractor,
+@Singleton
+class ColorPickerSnapshotRestorer
+@Inject
+constructor(
+ private val repository: ColorPickerRepository,
) : SnapshotRestorer {
private var snapshotStore: SnapshotStore = SnapshotStore.NOOP
@@ -39,7 +45,7 @@
store: SnapshotStore,
): RestorableSnapshot {
snapshotStore = store
- originalOption = interactor.getCurrentColorOption()
+ originalOption = repository.getCurrentColorOption()
return snapshot(originalOption)
}
@@ -60,7 +66,7 @@
)
}
- interactor.select(optionToRestore)
+ repository.select(optionToRestore)
}
}
diff --git a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
index 7b5b598..05e42c9 100644
--- a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
+++ b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
@@ -133,9 +133,10 @@
interface Binding {
fun saveInstanceState(savedState: Bundle)
+
fun restoreInstanceState(savedState: Bundle)
}
- private val LAYOUT_MANAGER_SAVED_STATE: String = "layout_manager_state"
+ private const val LAYOUT_MANAGER_SAVED_STATE: String = "layout_manager_state"
private var layoutManagerSavedState: Parcelable? = null
}
diff --git a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
index a2dc526..c5eb76f 100644
--- a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
+++ b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
@@ -85,10 +85,7 @@
viewModel =
ViewModelProvider(
requireActivity(),
- injector.getColorPickerViewModelFactory(
- context = requireContext(),
- wallpaperColorsRepository = wallpaperColorsRepository,
- ),
+ injector.getColorPickerViewModelFactory(requireContext()),
)
.get(),
lifecycleOwner = this,
diff --git a/src/com/android/customization/picker/common/ui/view/KeyguardQuickAffordanceItemSpacing.kt b/src/com/android/customization/picker/common/ui/view/DoubleRowListItemSpacing.kt
similarity index 89%
rename from src/com/android/customization/picker/common/ui/view/KeyguardQuickAffordanceItemSpacing.kt
rename to src/com/android/customization/picker/common/ui/view/DoubleRowListItemSpacing.kt
index c88a819..746061c 100644
--- a/src/com/android/customization/picker/common/ui/view/KeyguardQuickAffordanceItemSpacing.kt
+++ b/src/com/android/customization/picker/common/ui/view/DoubleRowListItemSpacing.kt
@@ -19,13 +19,13 @@
import android.view.View
import androidx.recyclerview.widget.RecyclerView
-/** Item spacing used by the RecyclerView. */
-class KeyguardQuickAffordanceItemSpacing() : RecyclerView.ItemDecoration() {
+/** Item spacing used by the RecyclerView with 2 rows. */
+class DoubleRowListItemSpacing(private val rowSpaceDp: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
- state: RecyclerView.State
+ state: RecyclerView.State,
) {
val itemIndex = parent.getChildAdapterPosition(view)
val columnIndex = itemIndex / 2
@@ -62,7 +62,7 @@
}
if (itemIndex % 2 == 0) {
- outRect.bottom = FIRST_ROW_BOTTOM_SPACING_DP.toPx(density)
+ outRect.bottom = rowSpaceDp.toPx(density)
}
}
@@ -72,7 +72,6 @@
companion object {
const val EDGE_ITEM_HORIZONTAL_SPACING_DP = 20
- const val COMMON_HORIZONTAL_SPACING_DP = 9
- const val FIRST_ROW_BOTTOM_SPACING_DP = 8
+ const val COMMON_HORIZONTAL_SPACING_DP = 4
}
}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt
new file mode 100644
index 0000000..82f5291
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 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.wallpaper.customization.ui.binder
+
+import android.content.Context
+import android.content.res.Configuration.UI_MODE_NIGHT_MASK
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.view.View
+import android.widget.TextView
+import androidx.core.content.res.ResourcesCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.color.shared.model.ColorType
+import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder
+import com.android.customization.picker.color.ui.view.ColorOptionIconView
+import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
+import com.android.customization.picker.color.ui.viewmodel.ColorTypeTabViewModel
+import com.android.customization.picker.common.ui.view.DoubleRowListItemSpacing
+import com.android.themepicker.R
+import com.android.wallpaper.customization.ui.viewmodel.ColorPickerViewModel2
+import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar
+import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar.Tab.PRIMARY
+import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar.Tab.SECONDARY
+import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter
+import kotlinx.coroutines.launch
+
+object ColorsFloatingSheetBinder {
+
+ fun bind(
+ view: View,
+ viewModel: ColorPickerViewModel2,
+ lifecycleOwner: LifecycleOwner,
+ ) {
+ val subhead = view.requireViewById<TextView>(R.id.color_type_tab_subhead)
+
+ val colorsAdapter =
+ createOptionItemAdapter(view.resources.configuration.uiMode, lifecycleOwner)
+ val colorsList =
+ view.requireViewById<RecyclerView>(R.id.colors_horizontal_list).also {
+ it.initColorsList(view.context.applicationContext, colorsAdapter)
+ }
+
+ val tabs = view.requireViewById<FloatingTabToolbar>(R.id.floating_bar_tabs)
+
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.colorTypeTabs.collect { colorTypes ->
+ colorTypes.forEach { (colorType, tabViewModel) ->
+ bindTab(tabs, colorType, tabViewModel)
+ }
+ colorTypes
+ .filterValues { it.isSelected }
+ .keys
+ .firstOrNull()
+ ?.let {
+ when (it) {
+ ColorType.WALLPAPER_COLOR -> tabs.setTabSelected(PRIMARY)
+ ColorType.PRESET_COLOR -> tabs.setTabSelected(SECONDARY)
+ }
+ }
+ }
+ }
+
+ launch { viewModel.colorTypeTabSubheader.collect { subhead.text = it } }
+
+ launch {
+ viewModel.colorOptions.collect { colorOptions ->
+ colorsAdapter.setItems(colorOptions) {
+ var indexToFocus = colorOptions.indexOfFirst { it.isSelected.value }
+ indexToFocus = if (indexToFocus < 0) 0 else indexToFocus
+ (colorsList.layoutManager as LinearLayoutManager)
+ .scrollToPositionWithOffset(indexToFocus, 0)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun createOptionItemAdapter(
+ uiMode: Int,
+ lifecycleOwner: LifecycleOwner
+ ): OptionItemAdapter<ColorOptionIconViewModel> =
+ OptionItemAdapter(
+ layoutResourceId = R.layout.color_option,
+ lifecycleOwner = lifecycleOwner,
+ bindIcon = { foregroundView: View, colorIcon: ColorOptionIconViewModel ->
+ val colorOptionIconView = foregroundView as? ColorOptionIconView
+ val night = uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
+ colorOptionIconView?.let { ColorOptionIconBinder.bind(it, colorIcon, night) }
+ }
+ )
+
+ private fun RecyclerView.initColorsList(
+ context: Context,
+ adapter: OptionItemAdapter<ColorOptionIconViewModel>,
+ ) {
+ apply {
+ this.adapter = adapter
+ layoutManager =
+ GridLayoutManager(
+ context,
+ 2,
+ GridLayoutManager.HORIZONTAL,
+ false,
+ )
+ addItemDecoration(DoubleRowListItemSpacing(4))
+ }
+ }
+
+ private fun bindTab(
+ tabs: FloatingTabToolbar,
+ colorType: ColorType,
+ viewModel: ColorTypeTabViewModel
+ ) {
+ val tab =
+ when (colorType) {
+ ColorType.WALLPAPER_COLOR -> PRIMARY
+ ColorType.PRESET_COLOR -> SECONDARY
+ }
+ val iconDrawable =
+ ResourcesCompat.getDrawable(
+ tabs.resources,
+ when (colorType) {
+ ColorType.WALLPAPER_COLOR ->
+ com.android.wallpaper.R.drawable.ic_baseline_wallpaper_24
+ ColorType.PRESET_COLOR -> R.drawable.ic_colors
+ },
+ null,
+ )
+ when (colorType) {
+ ColorType.WALLPAPER_COLOR -> tabs.primaryIcon.setImageDrawable(iconDrawable)
+ ColorType.PRESET_COLOR -> tabs.secondaryIcon.setImageDrawable(iconDrawable)
+ }
+ tabs.setTabText(tab, viewModel.name)
+ tabs.setOnTabClick(tab, viewModel.onClick)
+ }
+}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt
index 962278a..6fc1b7f 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt
@@ -26,7 +26,7 @@
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import com.android.customization.picker.common.ui.view.KeyguardQuickAffordanceItemSpacing
+import com.android.customization.picker.common.ui.view.DoubleRowListItemSpacing
import com.android.themepicker.R
import com.android.wallpaper.customization.ui.viewmodel.KeyguardQuickAffordancePickerViewModel2
import com.android.wallpaper.picker.common.dialog.ui.viewbinder.DialogViewBinder
@@ -184,7 +184,7 @@
GridLayoutManager.HORIZONTAL,
false,
)
- addItemDecoration(KeyguardQuickAffordanceItemSpacing())
+ addItemDecoration(DoubleRowListItemSpacing(12))
}
}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index 0fa1283..250c3d1 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -21,6 +21,7 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption
import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption
import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
import com.android.wallpaper.picker.customization.ui.binder.CustomizationOptionsBinder
@@ -62,6 +63,10 @@
lockScreenCustomizationOptionEntries
.find { it.first == ThemePickerLockCustomizationOption.SHORTCUTS }
?.second
+ val optionColors =
+ homeScreenCustomizationOptionEntries
+ .find { it.first == ThemePickerHomeCustomizationOption.COLORS }
+ ?.second
viewModel as ThemePickerCustomizationOptionsViewModel
lifecycleOwner.lifecycleScope.launch {
@@ -77,6 +82,12 @@
optionShortcut?.setOnClickListener { _ -> it?.invoke() }
}
}
+
+ launch {
+ viewModel.onCustomizeColorsClicked.collect {
+ optionColors?.setOnClickListener { _ -> it?.invoke() }
+ }
+ }
}
}
@@ -85,5 +96,11 @@
viewModel.keyguardQuickAffordancePickerViewModel2,
lifecycleOwner,
)
+
+ ColorsFloatingSheetBinder.bind(
+ view,
+ viewModel.colorPickerViewModel2,
+ lifecycleOwner,
+ )
}
}
diff --git a/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt b/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
index 83c4154..5e7f568 100644
--- a/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
+++ b/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
@@ -162,17 +162,27 @@
)
.also { bottomSheetContainer.addView(it) }
)
+ put(
+ ThemePickerHomeCustomizationOption.COLORS,
+ inflateFloatingSheet(
+ ThemePickerHomeCustomizationOption.COLORS,
+ bottomSheetContainer,
+ layoutInflater,
+ )
+ .also { bottomSheetContainer.addView(it) }
+ )
}
}
private fun inflateFloatingSheet(
- option: ThemePickerLockCustomizationOption,
+ option: CustomizationOptionUtil.CustomizationOption,
bottomSheetContainer: FrameLayout,
layoutInflater: LayoutInflater,
): View =
when (option) {
ThemePickerLockCustomizationOption.CLOCK -> R.layout.floating_sheet_clock
ThemePickerLockCustomizationOption.SHORTCUTS -> R.layout.floating_sheet_shortcut
+ ThemePickerHomeCustomizationOption.COLORS -> R.layout.floating_sheet_colors
else ->
throw IllegalStateException(
"Customization option $option does not have a bottom sheet view"
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt
new file mode 100644
index 0000000..3c77423
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 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.wallpaper.customization.ui.viewmodel
+
+import android.content.Context
+import com.android.customization.model.color.ColorOptionImpl
+import com.android.customization.module.logging.ThemesUserEventLogger
+import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
+import com.android.customization.picker.color.shared.model.ColorType
+import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
+import com.android.customization.picker.color.ui.viewmodel.ColorTypeTabViewModel
+import com.android.themepicker.R
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.android.scopes.ViewModelScoped
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Models UI state for a color picker experience. */
+class ColorPickerViewModel2
+@AssistedInject
+constructor(
+ @ApplicationContext context: Context,
+ private val interactor: ColorPickerInteractor,
+ private val logger: ThemesUserEventLogger,
+ @Assisted private val viewModelScope: CoroutineScope,
+) {
+
+ private val selectedColorTypeTabId = MutableStateFlow<ColorType?>(null)
+
+ /** View-models for each color tab. */
+ val colorTypeTabs: Flow<Map<ColorType, ColorTypeTabViewModel>> =
+ combine(
+ interactor.colorOptions,
+ selectedColorTypeTabId,
+ ) { colorOptions, selectedColorTypeIdOrNull ->
+ colorOptions.keys
+ .mapIndexed { index, colorType ->
+ val isSelected =
+ (selectedColorTypeIdOrNull == null && index == 0) ||
+ selectedColorTypeIdOrNull == colorType
+ colorType to
+ ColorTypeTabViewModel(
+ name =
+ when (colorType) {
+ ColorType.WALLPAPER_COLOR ->
+ context.resources.getString(R.string.wallpaper_color_tab)
+ ColorType.PRESET_COLOR ->
+ context.resources.getString(R.string.preset_color_tab_2)
+ },
+ isSelected = isSelected,
+ onClick =
+ if (isSelected) {
+ null
+ } else {
+ { this.selectedColorTypeTabId.value = colorType }
+ },
+ )
+ }
+ .toMap()
+ }
+
+ /** View-models for each color tab subheader */
+ val colorTypeTabSubheader: Flow<String> =
+ selectedColorTypeTabId.map { selectedColorTypeIdOrNull ->
+ when (selectedColorTypeIdOrNull ?: ColorType.WALLPAPER_COLOR) {
+ ColorType.WALLPAPER_COLOR ->
+ context.resources.getString(R.string.wallpaper_color_subheader)
+ ColorType.PRESET_COLOR ->
+ context.resources.getString(R.string.preset_color_subheader)
+ }
+ }
+
+ /** The list of all color options mapped by their color type */
+ private val allColorOptions:
+ Flow<Map<ColorType, List<OptionItemViewModel<ColorOptionIconViewModel>>>> =
+ interactor.colorOptions.map { colorOptions ->
+ colorOptions
+ .map { colorOptionEntry ->
+ colorOptionEntry.key to
+ colorOptionEntry.value.map { colorOptionModel ->
+ val colorOption: ColorOptionImpl =
+ colorOptionModel.colorOption as ColorOptionImpl
+ val lightThemeColors =
+ colorOption.previewInfo.resolveColors(/* darkTheme= */ false)
+ val darkThemeColors =
+ colorOption.previewInfo.resolveColors(/* darkTheme= */ true)
+ val isSelectedFlow: StateFlow<Boolean> =
+ interactor.selectingColorOption
+ .map {
+ it?.colorOption?.isEquivalent(colorOptionModel.colorOption)
+ ?: colorOptionModel.isSelected
+ }
+ .stateIn(viewModelScope)
+ OptionItemViewModel<ColorOptionIconViewModel>(
+ key = MutableStateFlow(colorOptionModel.key) as StateFlow<String>,
+ payload =
+ ColorOptionIconViewModel(
+ lightThemeColor0 = lightThemeColors[0],
+ lightThemeColor1 = lightThemeColors[1],
+ lightThemeColor2 = lightThemeColors[2],
+ lightThemeColor3 = lightThemeColors[3],
+ darkThemeColor0 = darkThemeColors[0],
+ darkThemeColor1 = darkThemeColors[1],
+ darkThemeColor2 = darkThemeColors[2],
+ darkThemeColor3 = darkThemeColors[3],
+ ),
+ text =
+ Text.Loaded(
+ colorOption.getContentDescription(context).toString()
+ ),
+ isTextUserVisible = false,
+ isSelected = isSelectedFlow,
+ onClicked =
+ isSelectedFlow.map { isSelected ->
+ if (isSelected) {
+ null
+ } else {
+ {
+ viewModelScope.launch {
+ interactor.select(colorOptionModel)
+ logger.logThemeColorApplied(
+ colorOptionModel.colorOption
+ .sourceForLogging,
+ colorOptionModel.colorOption
+ .styleForLogging,
+ colorOptionModel.colorOption
+ .seedColorForLogging,
+ )
+ }
+ }
+ }
+ },
+ )
+ }
+ }
+ .toMap()
+ }
+
+ /** The list of all available color options for the selected Color Type. */
+ val colorOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
+ combine(allColorOptions, selectedColorTypeTabId) {
+ allColorOptions: Map<ColorType, List<OptionItemViewModel<ColorOptionIconViewModel>>>,
+ selectedColorTypeIdOrNull ->
+ val selectedColorTypeId = selectedColorTypeIdOrNull ?: ColorType.WALLPAPER_COLOR
+ allColorOptions[selectedColorTypeId]!!
+ }
+
+ @ViewModelScoped
+ @AssistedFactory
+ interface Factory {
+ fun create(viewModelScope: CoroutineScope): ColorPickerViewModel2
+ }
+}
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
index 6628b3d..bc0366a 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
@@ -33,6 +33,7 @@
constructor(
defaultCustomizationOptionsViewModelFactory: DefaultCustomizationOptionsViewModel.Factory,
keyguardQuickAffordancePickerViewModel2Factory: KeyguardQuickAffordancePickerViewModel2.Factory,
+ colorPickerViewModel2Factory: ColorPickerViewModel2.Factory,
@Assisted private val viewModelScope: CoroutineScope,
) : CustomizationOptionsViewModel {
@@ -41,6 +42,7 @@
val keyguardQuickAffordancePickerViewModel2 =
keyguardQuickAffordancePickerViewModel2Factory.create(viewModelScope = viewModelScope)
+ val colorPickerViewModel2 = colorPickerViewModel2Factory.create(viewModelScope = viewModelScope)
override val selectedOption = defaultCustomizationOptionsViewModel.selectedOption
@@ -73,6 +75,19 @@
}
}
+ val onCustomizeColorsClicked: Flow<(() -> Unit)?> =
+ selectedOption.map {
+ if (it == null) {
+ {
+ defaultCustomizationOptionsViewModel.selectOption(
+ ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption.COLORS
+ )
+ }
+ } else {
+ null
+ }
+ }
+
@ViewModelScoped
@AssistedFactory
interface Factory : CustomizationOptionsViewModelFactory {
diff --git a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
index 3f41f0b..b700d2f 100644
--- a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
+++ b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
@@ -23,6 +23,8 @@
import com.android.customization.module.ThemePickerInjector
import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.module.logging.ThemesUserEventLoggerImpl
+import com.android.customization.picker.color.data.repository.ColorPickerRepository
+import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
import com.android.wallpaper.customization.ui.binder.ThemePickerCustomizationOptionsBinder
@@ -80,6 +82,10 @@
impl: ThemePickerCustomizationOptionsBinder
): CustomizationOptionsBinder
+ @Binds
+ @Singleton
+ abstract fun bindColorPickerRepository(impl: ColorPickerRepositoryImpl): ColorPickerRepository
+
companion object {
@Provides
@Singleton
diff --git a/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt b/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt
index caa5029..c0ae6f0 100644
--- a/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt
+++ b/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt
@@ -12,7 +12,6 @@
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
-import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
import com.android.systemui.shared.clocks.ClockRegistry
@@ -58,17 +57,7 @@
throw UnsupportedOperationException("not implemented")
}
- override fun getColorPickerInteractor(
- context: Context,
- wallpaperColorsRepository: WallpaperColorsRepository,
- ): ColorPickerInteractor {
- throw UnsupportedOperationException("not implemented")
- }
-
- override fun getColorPickerViewModelFactory(
- context: Context,
- wallpaperColorsRepository: WallpaperColorsRepository,
- ): ColorPickerViewModel.Factory {
+ override fun getColorPickerViewModelFactory(context: Context): ColorPickerViewModel.Factory {
throw UnsupportedOperationException("not implemented")
}
diff --git a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
index 249f6af..4a484f3 100644
--- a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
+++ b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
@@ -23,6 +23,8 @@
import com.android.customization.module.CustomizationPreferences
import com.android.customization.module.logging.TestThemesUserEventLogger
import com.android.customization.module.logging.ThemesUserEventLogger
+import com.android.customization.picker.color.data.repository.ColorPickerRepository
+import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
import com.android.customization.testing.TestCustomizationInjector
import com.android.customization.testing.TestDefaultCustomizationPreferences
import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
@@ -115,6 +117,10 @@
impl: DefaultCustomizationOptionsBinder
): CustomizationOptionsBinder
+ @Binds
+ @Singleton
+ abstract fun bindColorPickerRepository(impl: ColorPickerRepositoryImpl): ColorPickerRepository
+
companion object {
@Provides
@Singleton
diff --git a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt
index d4f24ee..97d4a9a 100644
--- a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt
@@ -52,11 +52,10 @@
underTest =
ColorPickerInteractor(
repository = repository,
- snapshotRestorer = {
- ColorPickerSnapshotRestorer(interactor = underTest).apply {
+ snapshotRestorer =
+ ColorPickerSnapshotRestorer(repository = repository).apply {
runBlocking { setUpSnapshotRestorer(store = store) }
- }
- },
+ },
)
repository.setOptions(4, 4, ColorType.WALLPAPER_COLOR, 0)
}
diff --git a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt
index 5f3e39e..b050237 100644
--- a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt
@@ -21,7 +21,6 @@
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.customization.picker.color.data.repository.FakeColorPickerRepository
-import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
@@ -51,14 +50,7 @@
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext
repository = FakeColorPickerRepository(context = context)
- underTest =
- ColorPickerSnapshotRestorer(
- interactor =
- ColorPickerInteractor(
- repository = repository,
- snapshotRestorer = { underTest },
- )
- )
+ underTest = ColorPickerSnapshotRestorer(repository = repository)
store = FakeSnapshotStore()
}
diff --git a/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt b/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt
index 889720e..f5878a4 100644
--- a/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt
@@ -76,11 +76,10 @@
interactor =
ColorPickerInteractor(
repository = repository,
- snapshotRestorer = {
- ColorPickerSnapshotRestorer(interactor = interactor).apply {
+ snapshotRestorer =
+ ColorPickerSnapshotRestorer(repository = repository).apply {
runBlocking { setUpSnapshotRestorer(store = store) }
- }
- },
+ },
)
underTest =
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
index d3ae9cb..9d82fc1 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
@@ -70,14 +70,14 @@
}
},
)
+ val colorPickerRepository = FakeColorPickerRepository(context = context)
colorPickerInteractor =
ColorPickerInteractor(
- repository = FakeColorPickerRepository(context = context),
- snapshotRestorer = {
- ColorPickerSnapshotRestorer(interactor = colorPickerInteractor).apply {
+ repository = colorPickerRepository,
+ snapshotRestorer =
+ ColorPickerSnapshotRestorer(repository = colorPickerRepository).apply {
runBlocking { setUpSnapshotRestorer(store = FakeSnapshotStore()) }
- }
- },
+ },
)
underTest =
ClockSettingsViewModel.Factory(
diff --git a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
new file mode 100644
index 0000000..a43a7ce
--- /dev/null
+++ b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2024 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.wallpaper.customization.ui.viewmodel
+
+import android.content.Context
+import android.graphics.Color
+import android.stats.style.StyleEnums
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.customization.model.color.ColorOptionsProvider
+import com.android.customization.module.logging.TestThemesUserEventLogger
+import com.android.customization.picker.color.data.repository.FakeColorPickerRepository
+import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
+import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
+import com.android.customization.picker.color.shared.model.ColorType
+import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
+import com.android.customization.picker.color.ui.viewmodel.ColorTypeTabViewModel
+import com.android.systemui.monet.Style
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
+import com.android.wallpaper.testing.FakeSnapshotStore
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(RobolectricTestRunner::class)
+class ColorPickerViewModel2Test {
+ private val logger = TestThemesUserEventLogger()
+ private lateinit var underTest: ColorPickerViewModel2
+ private lateinit var repository: FakeColorPickerRepository
+ private lateinit var interactor: ColorPickerInteractor
+ private lateinit var store: FakeSnapshotStore
+
+ private lateinit var context: Context
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().targetContext
+ val testDispatcher = UnconfinedTestDispatcher()
+ Dispatchers.setMain(testDispatcher)
+ testScope = TestScope(testDispatcher)
+ repository = FakeColorPickerRepository(context = context)
+ store = FakeSnapshotStore()
+
+ interactor =
+ ColorPickerInteractor(
+ repository = repository,
+ snapshotRestorer =
+ ColorPickerSnapshotRestorer(repository = repository).apply {
+ runBlocking { setUpSnapshotRestorer(store = store) }
+ },
+ )
+
+ underTest =
+ ColorPickerViewModel2(
+ context = context,
+ interactor = interactor,
+ logger = logger,
+ viewModelScope = testScope.backgroundScope,
+ )
+
+ repository.setOptions(4, 4, ColorType.WALLPAPER_COLOR, 0)
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `Log selected wallpaper color`() =
+ testScope.runTest {
+ repository.setOptions(
+ listOf(
+ repository.buildWallpaperOption(
+ ColorOptionsProvider.COLOR_SOURCE_LOCK,
+ Style.EXPRESSIVE,
+ "121212"
+ )
+ ),
+ listOf(repository.buildPresetOption(Style.FRUIT_SALAD, "#ABCDEF")),
+ ColorType.PRESET_COLOR,
+ 0
+ )
+
+ val colorTypes = collectLastValue(underTest.colorTypeTabs)
+ val colorOptions = collectLastValue(underTest.colorOptions)
+
+ // Select "Wallpaper colors" tab
+ colorTypes()?.get(ColorType.WALLPAPER_COLOR)?.onClick?.invoke()
+ // Select a color option
+ selectColorOption(colorOptions, 0)
+
+ assertThat(logger.themeColorSource)
+ .isEqualTo(StyleEnums.COLOR_SOURCE_LOCK_SCREEN_WALLPAPER)
+ assertThat(logger.themeColorStyle).isEqualTo(Style.EXPRESSIVE.toString().hashCode())
+ assertThat(logger.themeSeedColor).isEqualTo(Color.parseColor("#121212"))
+ }
+
+ @Test
+ fun `Log selected preset color`() =
+ testScope.runTest {
+ repository.setOptions(
+ listOf(
+ repository.buildWallpaperOption(
+ ColorOptionsProvider.COLOR_SOURCE_LOCK,
+ Style.EXPRESSIVE,
+ "121212"
+ )
+ ),
+ listOf(repository.buildPresetOption(Style.FRUIT_SALAD, "#ABCDEF")),
+ ColorType.WALLPAPER_COLOR,
+ 0
+ )
+
+ val colorTypes = collectLastValue(underTest.colorTypeTabs)
+ val colorOptions = collectLastValue(underTest.colorOptions)
+
+ // Select "Wallpaper colors" tab
+ colorTypes()?.get(ColorType.PRESET_COLOR)?.onClick?.invoke()
+ // Select a color option
+ selectColorOption(colorOptions, 0)
+
+ assertThat(logger.themeColorSource).isEqualTo(StyleEnums.COLOR_SOURCE_PRESET_COLOR)
+ assertThat(logger.themeColorStyle).isEqualTo(Style.FRUIT_SALAD.toString().hashCode())
+ assertThat(logger.themeSeedColor).isEqualTo(Color.parseColor("#ABCDEF"))
+ }
+
+ @Test
+ fun `Select a preset color`() =
+ testScope.runTest {
+ val colorTypes = collectLastValue(underTest.colorTypeTabs)
+ val colorOptions = collectLastValue(underTest.colorOptions)
+
+ // Initially, the wallpaper color tab should be selected
+ assertPickerUiState(
+ colorTypes = colorTypes(),
+ colorOptions = colorOptions(),
+ selectedColorTypeText = "Wallpaper colors",
+ selectedColorOptionIndex = 0
+ )
+
+ // Select "Basic colors" tab
+ colorTypes()?.get(ColorType.PRESET_COLOR)?.onClick?.invoke()
+ assertPickerUiState(
+ colorTypes = colorTypes(),
+ colorOptions = colorOptions(),
+ selectedColorTypeText = "Basic colors",
+ selectedColorOptionIndex = -1
+ )
+
+ // Select a color option
+ selectColorOption(colorOptions, 2)
+
+ // Check original option is no longer selected
+ colorTypes()?.get(ColorType.WALLPAPER_COLOR)?.onClick?.invoke()
+ assertPickerUiState(
+ colorTypes = colorTypes(),
+ colorOptions = colorOptions(),
+ selectedColorTypeText = "Wallpaper colors",
+ selectedColorOptionIndex = -1
+ )
+
+ // Check new option is selected
+ colorTypes()?.get(ColorType.PRESET_COLOR)?.onClick?.invoke()
+ assertPickerUiState(
+ colorTypes = colorTypes(),
+ colorOptions = colorOptions(),
+ selectedColorTypeText = "Basic colors",
+ selectedColorOptionIndex = 2
+ )
+ }
+
+ /** Simulates a user selecting the affordance at the given index, if that is clickable. */
+ private fun TestScope.selectColorOption(
+ colorOptions: () -> List<OptionItemViewModel<ColorOptionIconViewModel>>?,
+ index: Int,
+ ) {
+ val onClickedFlow = colorOptions()?.get(index)?.onClicked
+ val onClickedLastValueOrNull: (() -> (() -> Unit)?)? =
+ onClickedFlow?.let { collectLastValue(it) }
+ onClickedLastValueOrNull?.let { onClickedLastValue ->
+ val onClickedOrNull: (() -> Unit)? = onClickedLastValue()
+ onClickedOrNull?.let { onClicked -> onClicked() }
+ }
+ }
+
+ /**
+ * Asserts the entire picker UI state is what is expected. This includes the color type tabs and
+ * the color options list.
+ *
+ * @param colorTypes The observed color type view-models, keyed by ColorType
+ * @param colorOptions The observed color options
+ * @param selectedColorTypeText The text of the color type that's expected to be selected
+ * @param selectedColorOptionIndex The index of the color option that's expected to be selected,
+ * -1 stands for no color option should be selected
+ */
+ private fun TestScope.assertPickerUiState(
+ colorTypes: Map<ColorType, ColorTypeTabViewModel>?,
+ colorOptions: List<OptionItemViewModel<ColorOptionIconViewModel>>?,
+ selectedColorTypeText: String,
+ selectedColorOptionIndex: Int,
+ ) {
+ assertColorTypeTabUiState(
+ colorTypes = colorTypes,
+ colorTypeId = ColorType.WALLPAPER_COLOR,
+ isSelected = "Wallpaper colors" == selectedColorTypeText,
+ )
+ assertColorTypeTabUiState(
+ colorTypes = colorTypes,
+ colorTypeId = ColorType.PRESET_COLOR,
+ isSelected = "Basic colors" == selectedColorTypeText,
+ )
+ assertColorOptionUiState(colorOptions, selectedColorOptionIndex)
+ }
+
+ /**
+ * Asserts the picker section UI state is what is expected.
+ *
+ * @param colorOptions The observed color options
+ * @param selectedColorOptionIndex The index of the color option that's expected to be selected,
+ * -1 stands for no color option should be selected
+ */
+ private fun TestScope.assertColorOptionUiState(
+ colorOptions: List<OptionItemViewModel<ColorOptionIconViewModel>>?,
+ selectedColorOptionIndex: Int,
+ ) {
+ var foundSelectedColorOption = false
+ assertThat(colorOptions).isNotNull()
+ if (colorOptions != null) {
+ for (i in colorOptions.indices) {
+ val colorOptionHasSelectedIndex = i == selectedColorOptionIndex
+ val isSelected: Boolean? = collectLastValue(colorOptions[i].isSelected).invoke()
+ assertWithMessage(
+ "Expected color option with index \"${i}\" to have" +
+ " isSelected=$colorOptionHasSelectedIndex but it was" +
+ " ${isSelected}, num options: ${colorOptions.size}"
+ )
+ .that(isSelected)
+ .isEqualTo(colorOptionHasSelectedIndex)
+ foundSelectedColorOption = foundSelectedColorOption || colorOptionHasSelectedIndex
+ }
+ if (selectedColorOptionIndex == -1) {
+ assertWithMessage(
+ "Expected no color options to be selected, but a color option is" +
+ " selected"
+ )
+ .that(foundSelectedColorOption)
+ .isFalse()
+ } else {
+ assertWithMessage(
+ "Expected a color option to be selected, but no color option is" +
+ " selected"
+ )
+ .that(foundSelectedColorOption)
+ .isTrue()
+ }
+ }
+ }
+
+ /**
+ * Asserts that a color type tab has the correct UI state.
+ *
+ * @param colorTypes The observed color type view-models, keyed by ColorType enum
+ * @param colorTypeId the ID of the color type to assert
+ * @param isSelected Whether that color type should be selected
+ */
+ private fun assertColorTypeTabUiState(
+ colorTypes: Map<ColorType, ColorTypeTabViewModel>?,
+ colorTypeId: ColorType,
+ isSelected: Boolean,
+ ) {
+ val viewModel =
+ colorTypes?.get(colorTypeId) ?: error("No color type with ID \"$colorTypeId\"!")
+ assertThat(viewModel.isSelected).isEqualTo(isSelected)
+ }
+}