summaryrefslogtreecommitdiff
path: root/src/com/android/customization/picker/color
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/customization/picker/color')
-rw-r--r--src/com/android/customization/picker/color/ColorPickerFragment.kt41
-rw-r--r--src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt53
-rw-r--r--src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt61
-rw-r--r--src/com/android/customization/picker/color/ui/adapter/ColorOptionAdapter.kt103
-rw-r--r--src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt2
-rw-r--r--src/com/android/customization/picker/color/ui/binder/ColorOptionIconBinder.kt32
-rw-r--r--src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt58
-rw-r--r--src/com/android/customization/picker/color/ui/binder/ColorSectionViewBinder.kt18
-rw-r--r--src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt111
-rw-r--r--src/com/android/customization/picker/color/ui/view/ColorOptionIconView.kt125
-rw-r--r--src/com/android/customization/picker/color/ui/viewmodel/ColorOptionIconViewModel.kt12
-rw-r--r--src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt229
12 files changed, 478 insertions, 367 deletions
diff --git a/src/com/android/customization/picker/color/ColorPickerFragment.kt b/src/com/android/customization/picker/color/ColorPickerFragment.kt
deleted file mode 100644
index c8ecb7f9..00000000
--- a/src/com/android/customization/picker/color/ColorPickerFragment.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.customization.picker.color
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.android.wallpaper.R
-import com.android.wallpaper.picker.AppbarFragment
-
-// TODO (b/262924623): Color Picker Fragment
-class ColorPickerFragment : AppbarFragment() {
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- val view =
- inflater.inflate(
- R.layout.fragment_color_picker,
- container,
- false,
- )
- setUpToolbar(view)
- return view
- }
-}
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 512a5007..41ef3a57 100644
--- a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
@@ -16,16 +16,15 @@
*/
package com.android.customization.picker.color.data.repository
-import android.app.WallpaperColors
import android.util.Log
import com.android.customization.model.CustomizationManager
-import com.android.customization.model.color.ColorBundle
import com.android.customization.model.color.ColorCustomizationManager
import com.android.customization.model.color.ColorOption
-import com.android.customization.model.color.ColorSeedOption
+import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
import com.android.systemui.monet.Style
+import com.android.wallpaper.model.WallpaperColorsModel
import com.android.wallpaper.model.WallpaperColorsViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -40,9 +39,9 @@ class ColorPickerRepositoryImpl(
private val colorManager: ColorCustomizationManager,
) : ColorPickerRepository {
- private val homeWallpaperColors: StateFlow<WallpaperColors?> =
+ private val homeWallpaperColors: StateFlow<WallpaperColorsModel?> =
wallpaperColorsViewModel.homeWallpaperColors
- private val lockWallpaperColors: StateFlow<WallpaperColors?> =
+ private val lockWallpaperColors: StateFlow<WallpaperColorsModel?> =
wallpaperColorsViewModel.lockWallpaperColors
override val colorOptions: Flow<Map<ColorType, List<ColorOptionModel>>> =
@@ -51,8 +50,27 @@ class ColorPickerRepositoryImpl(
}
.map { (homeColors, lockColors) ->
suspendCancellableCoroutine { continuation ->
- colorManager.setWallpaperColors(homeColors, lockColors)
- colorManager.fetchOptions(
+ if (
+ homeColors is WallpaperColorsModel.Loading ||
+ lockColors is WallpaperColorsModel.Loading
+ ) {
+ continuation.resumeWith(
+ Result.success(
+ mapOf(
+ ColorType.WALLPAPER_COLOR to listOf(),
+ ColorType.PRESET_COLOR to listOf()
+ )
+ )
+ )
+ return@suspendCancellableCoroutine
+ }
+ val homeColorsLoaded = homeColors as WallpaperColorsModel.Loaded
+ val lockColorsLoaded = lockColors as WallpaperColorsModel.Loaded
+ colorManager.setWallpaperColors(
+ homeColorsLoaded.colors,
+ lockColorsLoaded.colors
+ )
+ colorManager.fetchRevampedUIOptions(
object : CustomizationManager.OptionsFetchedListener<ColorOption?> {
override fun onOptionsLoaded(options: MutableList<ColorOption?>?) {
val wallpaperColorOptions: MutableList<ColorOptionModel> =
@@ -60,10 +78,11 @@ class ColorPickerRepositoryImpl(
val presetColorOptions: MutableList<ColorOptionModel> =
mutableListOf()
options?.forEach { option ->
- when (option) {
- is ColorSeedOption ->
+ when ((option as ColorOptionImpl).type) {
+ ColorType.WALLPAPER_COLOR ->
wallpaperColorOptions.add(option.toModel())
- is ColorBundle -> presetColorOptions.add(option.toModel())
+ ColorType.PRESET_COLOR ->
+ presetColorOptions.add(option.toModel())
}
}
continuation.resumeWith(
@@ -113,16 +132,16 @@ class ColorPickerRepositoryImpl(
val overlays = colorManager.currentOverlays
val styleOrNull = colorManager.currentStyle
val style = styleOrNull?.let { Style.valueOf(it) } ?: Style.TONAL_SPOT
- val colorOptionBuilder =
- // Does not matter whether ColorSeedOption or ColorBundle builder is used here
- // because to apply the color, one just needs a generic ColorOption
- ColorSeedOption.Builder().setSource(colorManager.currentColorSource).setStyle(style)
+ val source = colorManager.currentColorSource
+ val colorOptionBuilder = ColorOptionImpl.Builder()
+ colorOptionBuilder.source = source
+ colorOptionBuilder.style = style
for (overlay in overlays) {
colorOptionBuilder.addOverlayPackage(overlay.key, overlay.value)
}
val colorOption = colorOptionBuilder.build()
return ColorOptionModel(
- key = "${colorOption.style}::${colorOption.serializedPackages}",
+ key = "",
colorOption = colorOption,
isSelected = false,
)
@@ -132,9 +151,9 @@ class ColorPickerRepositoryImpl(
return colorManager.currentColorSource
}
- private fun ColorOption.toModel(): ColorOptionModel {
+ private fun ColorOptionImpl.toModel(): ColorOptionModel {
return ColorOptionModel(
- key = "${this.style}::${this.serializedPackages}",
+ key = "${this.type}::${this.style}::${this.serializedPackages}",
colorOption = this,
isSelected = isActive(colorManager),
)
diff --git a/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt b/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt
index edbf6dcf..714129df 100644
--- a/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt
+++ b/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt
@@ -19,9 +19,8 @@ package com.android.customization.picker.color.data.repository
import android.content.Context
import android.graphics.Color
import android.text.TextUtils
-import com.android.customization.model.color.ColorBundle
+import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.model.color.ColorOptionsProvider
-import com.android.customization.model.color.ColorSeedOption
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
import kotlinx.coroutines.flow.MutableStateFlow
@@ -82,9 +81,7 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos
ColorOptionModel(
key = "${ColorType.PRESET_COLOR}::$index",
colorOption = buildPresetOption(index),
- isSelected =
- selectedColorOptionType == ColorType.PRESET_COLOR &&
- selectedColorOptionIndex == index,
+ isSelected = isSelected,
)
if (isSelected) {
selectedColorOption = colorOption
@@ -95,36 +92,36 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos
)
}
- private fun buildPresetOption(index: Int): ColorBundle {
- return ColorBundle.Builder()
+ private fun buildPresetOption(index: Int): ColorOptionImpl {
+ val builder = ColorOptionImpl.Builder()
+ builder.lightColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.darkColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.index = index
+ builder.type = ColorType.PRESET_COLOR
+ builder.source = ColorOptionsProvider.COLOR_SOURCE_PRESET
+ builder.title = "Preset"
+ builder
.addOverlayPackage("TEST_PACKAGE_TYPE", "preset_color")
.addOverlayPackage("TEST_PACKAGE_INDEX", "$index")
- .setIndex(index)
- .build(context)
+ return builder.build()
}
- private fun buildWallpaperOption(index: Int): ColorSeedOption {
- return ColorSeedOption.Builder()
- .setLightColors(
- intArrayOf(
- Color.TRANSPARENT,
- Color.TRANSPARENT,
- Color.TRANSPARENT,
- Color.TRANSPARENT
- )
- )
- .setDarkColors(
- intArrayOf(
- Color.TRANSPARENT,
- Color.TRANSPARENT,
- Color.TRANSPARENT,
- Color.TRANSPARENT
- )
- )
+ private fun buildWallpaperOption(index: Int): ColorOptionImpl {
+ val builder = ColorOptionImpl.Builder()
+ builder.lightColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.darkColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.index = index
+ builder.type = ColorType.WALLPAPER_COLOR
+ builder.source = ColorOptionsProvider.COLOR_SOURCE_HOME
+ builder.title = "Dynamic"
+ builder
.addOverlayPackage("TEST_PACKAGE_TYPE", "wallpaper_color")
.addOverlayPackage("TEST_PACKAGE_INDEX", "$index")
- .setIndex(index)
- .build()
+ return builder.build()
}
override suspend fun select(colorOptionModel: ColorOptionModel) {
@@ -163,9 +160,9 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos
override fun getCurrentColorOption(): ColorOptionModel = selectedColorOption
override fun getCurrentColorSource(): String? =
- when (selectedColorOption.colorOption) {
- is ColorSeedOption -> ColorOptionsProvider.COLOR_SOURCE_HOME
- is ColorBundle -> ColorOptionsProvider.COLOR_SOURCE_PRESET
+ when ((selectedColorOption.colorOption as ColorOptionImpl).type) {
+ ColorType.WALLPAPER_COLOR -> ColorOptionsProvider.COLOR_SOURCE_HOME
+ ColorType.PRESET_COLOR -> ColorOptionsProvider.COLOR_SOURCE_PRESET
else -> null
}
diff --git a/src/com/android/customization/picker/color/ui/adapter/ColorOptionAdapter.kt b/src/com/android/customization/picker/color/ui/adapter/ColorOptionAdapter.kt
deleted file mode 100644
index 7aa390df..00000000
--- a/src/com/android/customization/picker/color/ui/adapter/ColorOptionAdapter.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.customization.picker.color.ui.adapter
-
-import android.graphics.BlendMode
-import android.graphics.BlendModeColorFilter
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.core.view.isVisible
-import androidx.recyclerview.widget.RecyclerView
-import com.android.customization.picker.color.ui.viewmodel.ColorOptionViewModel
-import com.android.wallpaper.R
-
-/**
- * Adapts between color option items and views.
- *
- * TODO (b/272109171): Remove after clock settings is refactored to use OptionItemAdapter
- */
-class ColorOptionAdapter : RecyclerView.Adapter<ColorOptionAdapter.ViewHolder>() {
-
- private val items = mutableListOf<ColorOptionViewModel>()
- private var isTitleVisible = false
-
- fun setItems(items: List<ColorOptionViewModel>) {
- this.items.clear()
- this.items.addAll(items)
- isTitleVisible = items.any { item -> item.title != null }
- notifyDataSetChanged()
- }
-
- class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val borderView: View = itemView.requireViewById(R.id.selection_border)
- val backgroundView: View = itemView.requireViewById(R.id.background)
- val color0View: ImageView = itemView.requireViewById(R.id.color_preview_0)
- val color1View: ImageView = itemView.requireViewById(R.id.color_preview_1)
- val color2View: ImageView = itemView.requireViewById(R.id.color_preview_2)
- val color3View: ImageView = itemView.requireViewById(R.id.color_preview_3)
- val optionTitleView: TextView = itemView.requireViewById(R.id.option_title)
- }
-
- override fun getItemCount(): Int {
- return items.size
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- return ViewHolder(
- LayoutInflater.from(parent.context)
- .inflate(
- R.layout.color_option_with_background,
- parent,
- false,
- )
- )
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val item = items[position]
-
- holder.itemView.setOnClickListener(
- if (item.onClick != null) {
- View.OnClickListener { item.onClick.invoke() }
- } else {
- null
- }
- )
- if (item.isSelected) {
- holder.borderView.alpha = 1f
- holder.borderView.scaleX = 1f
- holder.borderView.scaleY = 1f
- holder.backgroundView.scaleX = 0.86f
- holder.backgroundView.scaleY = 0.86f
- } else {
- holder.borderView.alpha = 0f
- holder.backgroundView.scaleX = 1f
- holder.backgroundView.scaleY = 1f
- }
- holder.color0View.drawable.colorFilter = BlendModeColorFilter(item.color0, BlendMode.SRC)
- holder.color1View.drawable.colorFilter = BlendModeColorFilter(item.color1, BlendMode.SRC)
- holder.color2View.drawable.colorFilter = BlendModeColorFilter(item.color2, BlendMode.SRC)
- holder.color3View.drawable.colorFilter = BlendModeColorFilter(item.color3, BlendMode.SRC)
- holder.itemView.contentDescription = item.contentDescription
- holder.optionTitleView.isVisible = isTitleVisible
- holder.optionTitleView.text = item.title
- }
-}
diff --git a/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt b/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt
index bb9f0823..553f5869 100644
--- a/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt
+++ b/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt
@@ -55,7 +55,7 @@ class ColorTypeTabAdapter : RecyclerView.Adapter<ColorTypeTabAdapter.ViewHolder>
val item = items[position]
holder.itemView.isSelected = item.isSelected
holder.textView.text = item.name
- holder.textView.setOnClickListener(
+ holder.itemView.setOnClickListener(
if (item.onClick != null) {
View.OnClickListener { item.onClick.invoke() }
} else {
diff --git a/src/com/android/customization/picker/color/ui/binder/ColorOptionIconBinder.kt b/src/com/android/customization/picker/color/ui/binder/ColorOptionIconBinder.kt
index 1478cc40..31d3dc81 100644
--- a/src/com/android/customization/picker/color/ui/binder/ColorOptionIconBinder.kt
+++ b/src/com/android/customization/picker/color/ui/binder/ColorOptionIconBinder.kt
@@ -17,25 +17,29 @@
package com.android.customization.picker.color.ui.binder
-import android.graphics.BlendMode
-import android.graphics.BlendModeColorFilter
-import android.view.ViewGroup
-import android.widget.ImageView
+import com.android.customization.picker.color.ui.view.ColorOptionIconView
import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
-import com.android.wallpaper.R
object ColorOptionIconBinder {
fun bind(
- view: ViewGroup,
+ view: ColorOptionIconView,
viewModel: ColorOptionIconViewModel,
+ darkTheme: Boolean,
) {
- val color0View: ImageView = view.requireViewById(R.id.color_preview_0)
- val color1View: ImageView = view.requireViewById(R.id.color_preview_1)
- val color2View: ImageView = view.requireViewById(R.id.color_preview_2)
- val color3View: ImageView = view.requireViewById(R.id.color_preview_3)
- color0View.drawable.colorFilter = BlendModeColorFilter(viewModel.color0, BlendMode.SRC)
- color1View.drawable.colorFilter = BlendModeColorFilter(viewModel.color1, BlendMode.SRC)
- color2View.drawable.colorFilter = BlendModeColorFilter(viewModel.color2, BlendMode.SRC)
- color3View.drawable.colorFilter = BlendModeColorFilter(viewModel.color3, BlendMode.SRC)
+ if (darkTheme) {
+ view.bindColor(
+ viewModel.darkThemeColor0,
+ viewModel.darkThemeColor1,
+ viewModel.darkThemeColor2,
+ viewModel.darkThemeColor3,
+ )
+ } else {
+ view.bindColor(
+ viewModel.lightThemeColor0,
+ viewModel.lightThemeColor1,
+ viewModel.lightThemeColor2,
+ viewModel.lightThemeColor3,
+ )
+ }
}
}
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 7623048f..cd9dd540 100644
--- a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
+++ b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
@@ -17,8 +17,10 @@
package com.android.customization.picker.color.ui.binder
+import android.content.res.Configuration
+import android.os.Bundle
+import android.os.Parcelable
import android.view.View
-import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -27,12 +29,12 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.customization.picker.color.ui.adapter.ColorTypeTabAdapter
+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.ColorPickerViewModel
import com.android.customization.picker.common.ui.view.ItemSpacing
import com.android.wallpaper.R
import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -48,7 +50,7 @@ object ColorPickerBinder {
view: View,
viewModel: ColorPickerViewModel,
lifecycleOwner: LifecycleOwner,
- ) {
+ ): Binding {
val colorTypeTabView: RecyclerView = view.requireViewById(R.id.color_type_tabs)
val colorTypeTabSubheaderView: TextView = view.requireViewById(R.id.color_type_tab_subhead)
val colorOptionContainerView: RecyclerView = view.requireViewById(R.id.color_options)
@@ -63,8 +65,11 @@ object ColorPickerBinder {
layoutResourceId = R.layout.color_option_2,
lifecycleOwner = lifecycleOwner,
bindIcon = { foregroundView: View, colorIcon: ColorOptionIconViewModel ->
- val viewGroup = foregroundView as? ViewGroup
- viewGroup?.let { ColorOptionIconBinder.bind(viewGroup, colorIcon) }
+ val colorOptionIconView = foregroundView as? ColorOptionIconView
+ val night =
+ (view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+ Configuration.UI_MODE_NIGHT_YES)
+ colorOptionIconView?.let { ColorOptionIconBinder.bind(it, colorIcon, night) }
}
)
colorOptionContainerView.adapter = colorOptionAdapter
@@ -89,9 +94,52 @@ object ColorPickerBinder {
launch {
viewModel.colorOptions.collect { colorOptions ->
colorOptionAdapter.setItems(colorOptions)
+ // the same recycler view is used for different color types tabs
+ // the scroll state of each tab should be independent of others
+ if (layoutManagerSavedState != null) {
+ colorOptionContainerView.post {
+ (colorOptionContainerView.layoutManager as LinearLayoutManager)
+ .onRestoreInstanceState(layoutManagerSavedState)
+ layoutManagerSavedState = null
+ }
+ } else {
+ var indexToFocus = colorOptions.indexOfFirst { it.isSelected.value }
+ indexToFocus = if (indexToFocus < 0) 0 else indexToFocus
+ val linearLayoutManager =
+ object : LinearLayoutManager(view.context, HORIZONTAL, false) {
+ override fun onLayoutCompleted(state: RecyclerView.State?) {
+ super.onLayoutCompleted(state)
+ // scrollToPosition seems to be inconsistently moving
+ // selected
+ // color to different positions
+ scrollToPositionWithOffset(indexToFocus, 0)
+ }
+ }
+ colorOptionContainerView.layoutManager = linearLayoutManager
+ }
}
}
}
}
+ return object : Binding {
+ override fun saveInstanceState(savedState: Bundle) {
+ savedState.putParcelable(
+ LAYOUT_MANAGER_SAVED_STATE,
+ colorOptionContainerView.layoutManager?.onSaveInstanceState()
+ )
+ }
+
+ override fun restoreInstanceState(savedState: Bundle) {
+ layoutManagerSavedState = savedState.getParcelable(LAYOUT_MANAGER_SAVED_STATE)
+ }
+ }
}
+
+ interface Binding {
+ fun saveInstanceState(savedState: Bundle)
+ fun restoreInstanceState(savedState: Bundle)
+ }
+
+ private val LAYOUT_MANAGER_SAVED_STATE: String = "layout_manager_state"
+ private var layoutManagerSavedState: Parcelable? = null
}
diff --git a/src/com/android/customization/picker/color/ui/binder/ColorSectionViewBinder.kt b/src/com/android/customization/picker/color/ui/binder/ColorSectionViewBinder.kt
index 9e232d70..2daefe47 100644
--- a/src/com/android/customization/picker/color/ui/binder/ColorSectionViewBinder.kt
+++ b/src/com/android/customization/picker/color/ui/binder/ColorSectionViewBinder.kt
@@ -17,9 +17,9 @@
package com.android.customization.picker.color.ui.binder
+import android.content.res.Configuration
import android.view.LayoutInflater
import android.view.View
-import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.view.isVisible
@@ -30,6 +30,7 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
import com.android.wallpaper.R
+import com.android.wallpaper.picker.common.icon.ui.viewbinder.ContentDescriptionViewBinder
import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
import kotlinx.coroutines.launch
@@ -91,10 +92,23 @@ object ColorSectionViewBinder {
})
.let { if (it < 0) 0 else it }
options.subList(0, colorOptionSlotSize).forEach { item ->
+ val night =
+ (view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+ Configuration.UI_MODE_NIGHT_YES)
val itemView =
LayoutInflater.from(view.context)
.inflate(R.layout.color_option_no_background, view, false)
- item.payload?.let { ColorOptionIconBinder.bind(itemView as ViewGroup, item.payload) }
+ item.payload?.let {
+ ColorOptionIconBinder.bind(
+ itemView.requireViewById(R.id.option_tile),
+ item.payload,
+ night
+ )
+ ContentDescriptionViewBinder.bind(
+ view = itemView.requireViewById(R.id.option_tile),
+ viewModel = item.text,
+ )
+ }
val optionSelectedView = itemView.requireViewById<ImageView>(R.id.option_selected)
lifecycleOwner.lifecycleScope.launch {
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 c6b2023e..78bfa43e 100644
--- a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
+++ b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
@@ -15,6 +15,7 @@
*/
package com.android.customization.picker.color.ui.fragment
+import android.app.WallpaperManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -23,21 +24,30 @@ import android.widget.FrameLayout
import androidx.cardview.widget.CardView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get
+import androidx.lifecycle.lifecycleScope
import com.android.customization.model.mode.DarkModeSectionController
import com.android.customization.module.ThemePickerInjector
import com.android.customization.picker.color.ui.binder.ColorPickerBinder
import com.android.wallpaper.R
+import com.android.wallpaper.model.WallpaperColorsModel
+import com.android.wallpaper.model.WallpaperColorsViewModel
+import com.android.wallpaper.module.CustomizationSections
import com.android.wallpaper.module.InjectorProvider
import com.android.wallpaper.picker.AppbarFragment
import com.android.wallpaper.picker.customization.ui.binder.ScreenPreviewBinder
import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
import com.android.wallpaper.util.DisplayUtils
import com.android.wallpaper.util.PreviewUtils
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
class ColorPickerFragment : AppbarFragment() {
+ private var binding: ColorPickerBinder.Binding? = null
+
companion object {
@JvmStatic
fun newInstance(): ColorPickerFragment {
@@ -63,19 +73,25 @@ class ColorPickerFragment : AppbarFragment() {
val wallpaperInfoFactory = injector.getCurrentWallpaperInfoFactory(requireContext())
val displayUtils: DisplayUtils = injector.getDisplayUtils(requireContext())
val wcViewModel = injector.getWallpaperColorsViewModel()
- ColorPickerBinder.bind(
- view = view,
- viewModel =
- ViewModelProvider(
- requireActivity(),
- injector.getColorPickerViewModelFactory(
- context = requireContext(),
- wallpaperColorsViewModel = wcViewModel,
- ),
- )
- .get(),
- lifecycleOwner = this,
- )
+ val wallpaperManager = WallpaperManager.getInstance(requireContext())
+
+ binding =
+ ColorPickerBinder.bind(
+ view = view,
+ viewModel =
+ ViewModelProvider(
+ requireActivity(),
+ injector.getColorPickerViewModelFactory(
+ context = requireContext(),
+ wallpaperColorsViewModel = wcViewModel,
+ ),
+ )
+ .get(),
+ lifecycleOwner = this,
+ )
+
+ savedInstanceState?.let { binding?.restoreInstanceState(it) }
+
ScreenPreviewBinder.bind(
activity = requireActivity(),
previewView = lockScreenView,
@@ -90,23 +106,38 @@ class ColorPickerFragment : AppbarFragment() {
R.string.lock_screen_preview_provider_authority,
),
),
- wallpaperInfoProvider = {
+ wallpaperInfoProvider = { forceReload ->
suspendCancellableCoroutine { continuation ->
wallpaperInfoFactory.createCurrentWallpaperInfos(
{ homeWallpaper, lockWallpaper, _ ->
+ lifecycleScope.launch {
+ if (
+ wcViewModel.lockWallpaperColors.value
+ is WallpaperColorsModel.Loading
+ ) {
+ loadInitialColors(
+ wallpaperManager,
+ wcViewModel,
+ CustomizationSections.Screen.LOCK_SCREEN
+ )
+ }
+ }
continuation.resume(lockWallpaper ?: homeWallpaper, null)
},
- /* forceRefresh= */ true,
+ forceReload,
)
}
},
onWallpaperColorChanged = { colors ->
wcViewModel.setLockWallpaperColors(colors)
},
+ wallpaperInteractor = injector.getWallpaperInteractor(requireContext()),
+ screen = CustomizationSections.Screen.LOCK_SCREEN,
),
lifecycleOwner = this,
offsetToStart =
displayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(requireActivity()),
+ onWallpaperPreviewDirty = { activity?.recreate() },
)
ScreenPreviewBinder.bind(
activity = requireActivity(),
@@ -122,23 +153,38 @@ class ColorPickerFragment : AppbarFragment() {
R.string.grid_control_metadata_name,
),
),
- wallpaperInfoProvider = {
+ wallpaperInfoProvider = { forceReload ->
suspendCancellableCoroutine { continuation ->
wallpaperInfoFactory.createCurrentWallpaperInfos(
{ homeWallpaper, lockWallpaper, _ ->
+ lifecycleScope.launch {
+ if (
+ wcViewModel.homeWallpaperColors.value
+ is WallpaperColorsModel.Loading
+ ) {
+ loadInitialColors(
+ wallpaperManager,
+ wcViewModel,
+ CustomizationSections.Screen.HOME_SCREEN
+ )
+ }
+ }
continuation.resume(homeWallpaper ?: lockWallpaper, null)
},
- /* forceRefresh= */ true,
+ forceReload,
)
}
},
onWallpaperColorChanged = { colors ->
- wcViewModel.setLockWallpaperColors(colors)
+ wcViewModel.setHomeWallpaperColors(colors)
},
+ wallpaperInteractor = injector.getWallpaperInteractor(requireContext()),
+ screen = CustomizationSections.Screen.HOME_SCREEN,
),
lifecycleOwner = this,
offsetToStart =
displayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(requireActivity()),
+ onWallpaperPreviewDirty = { activity?.recreate() },
)
val darkModeToggleContainerView: FrameLayout =
view.requireViewById(R.id.dark_mode_toggle_container)
@@ -154,6 +200,35 @@ class ColorPickerFragment : AppbarFragment() {
return view
}
+ private suspend fun loadInitialColors(
+ wallpaperManager: WallpaperManager,
+ colorViewModel: WallpaperColorsViewModel,
+ screen: CustomizationSections.Screen,
+ ) {
+ withContext(Dispatchers.IO) {
+ val colors =
+ wallpaperManager.getWallpaperColors(
+ if (screen == CustomizationSections.Screen.LOCK_SCREEN) {
+ WallpaperManager.FLAG_LOCK
+ } else {
+ WallpaperManager.FLAG_SYSTEM
+ }
+ )
+ withContext(Dispatchers.Main) {
+ if (screen == CustomizationSections.Screen.LOCK_SCREEN) {
+ colorViewModel.setLockWallpaperColors(colors)
+ } else {
+ colorViewModel.setHomeWallpaperColors(colors)
+ }
+ }
+ }
+ }
+
+ override fun onSaveInstanceState(savedInstanceState: Bundle) {
+ super.onSaveInstanceState(savedInstanceState)
+ binding?.saveInstanceState(savedInstanceState)
+ }
+
override fun getDefaultTitle(): CharSequence {
return requireContext().getString(R.string.color_picker_title)
}
diff --git a/src/com/android/customization/picker/color/ui/view/ColorOptionIconView.kt b/src/com/android/customization/picker/color/ui/view/ColorOptionIconView.kt
new file mode 100644
index 00000000..257bffb2
--- /dev/null
+++ b/src/com/android/customization/picker/color/ui/view/ColorOptionIconView.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.customization.picker.color.ui.view
+
+import android.annotation.ColorInt
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.AttributeSet
+import android.view.View
+
+/**
+ * Draw a color option icon, which is a quadrant circle that can show at most 4 different colors.
+ */
+class ColorOptionIconView(
+ context: Context,
+ attrs: AttributeSet,
+) : View(context, attrs) {
+
+ private val paint = Paint().apply { style = Paint.Style.FILL }
+
+ private val oval = RectF()
+
+ private var color0 = DEFAULT_PLACEHOLDER_COLOR
+ private var color1 = DEFAULT_PLACEHOLDER_COLOR
+ private var color2 = DEFAULT_PLACEHOLDER_COLOR
+ private var color3 = DEFAULT_PLACEHOLDER_COLOR
+
+ private var w = 0
+ private var h = 0
+
+ /**
+ * @param color0 the color in the top left quadrant
+ * @param color1 the color in the top right quadrant
+ * @param color2 the color in the bottom left quadrant
+ * @param color3 the color in the bottom right quadrant
+ */
+ fun bindColor(
+ @ColorInt color0: Int,
+ @ColorInt color1: Int,
+ @ColorInt color2: Int,
+ @ColorInt color3: Int,
+ ) {
+ this.color0 = color0
+ this.color1 = color1
+ this.color2 = color2
+ this.color3 = color3
+ invalidate()
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ this.w = w
+ this.h = h
+ super.onSizeChanged(w, h, oldw, oldh)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ // The w and h need to be an even number to avoid tiny pixel-level gaps between the pies
+ w = w.roundDownToEven()
+ h = h.roundDownToEven()
+
+ val width = w.toFloat()
+ val height = h.toFloat()
+
+ oval.set(0f, 0f, width, height)
+ canvas.apply {
+ paint.color = color3
+ drawArc(
+ oval,
+ 0f,
+ 90f,
+ true,
+ paint,
+ )
+ paint.color = color2
+ drawArc(
+ oval,
+ 90f,
+ 90f,
+ true,
+ paint,
+ )
+ paint.color = color0
+ drawArc(
+ oval,
+ 180f,
+ 90f,
+ true,
+ paint,
+ )
+ paint.color = color1
+ drawArc(
+ oval,
+ 270f,
+ 90f,
+ true,
+ paint,
+ )
+ }
+ }
+
+ companion object {
+ const val DEFAULT_PLACEHOLDER_COLOR = Color.BLACK
+
+ fun Int.roundDownToEven(): Int {
+ return if (this % 2 == 0) this else this - 1
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/color/ui/viewmodel/ColorOptionIconViewModel.kt b/src/com/android/customization/picker/color/ui/viewmodel/ColorOptionIconViewModel.kt
index d32538d8..8723c8c1 100644
--- a/src/com/android/customization/picker/color/ui/viewmodel/ColorOptionIconViewModel.kt
+++ b/src/com/android/customization/picker/color/ui/viewmodel/ColorOptionIconViewModel.kt
@@ -20,8 +20,12 @@ package com.android.customization.picker.color.ui.viewmodel
import android.annotation.ColorInt
data class ColorOptionIconViewModel(
- @ColorInt val color0: Int,
- @ColorInt val color1: Int,
- @ColorInt val color2: Int,
- @ColorInt val color3: Int,
+ @ColorInt val lightThemeColor0: Int,
+ @ColorInt val lightThemeColor1: Int,
+ @ColorInt val lightThemeColor2: Int,
+ @ColorInt val lightThemeColor3: Int,
+ @ColorInt val darkThemeColor0: Int,
+ @ColorInt val darkThemeColor1: Int,
+ @ColorInt val darkThemeColor2: Int,
+ @ColorInt val darkThemeColor3: Int,
)
diff --git a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
index 81a58107..67c68387 100644
--- a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
+++ b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
@@ -20,8 +20,7 @@ import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.android.customization.model.color.ColorBundle
-import com.android.customization.model.color.ColorSeedOption
+import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.shared.model.ColorType
import com.android.wallpaper.R
@@ -31,9 +30,11 @@ import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -92,146 +93,114 @@ private constructor(
/** 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
- when (colorOptionEntry.key) {
- ColorType.WALLPAPER_COLOR -> {
- colorOptionEntry.value.map { colorOptionModel ->
- val colorSeedOption: ColorSeedOption =
- colorOptionModel.colorOption as ColorSeedOption
- val colors =
- colorSeedOption.previewInfo.resolveColors(context.resources)
- val isSelectedFlow: StateFlow<Boolean> =
- interactor.activeColorOption
- .map {
- it?.colorOption?.isEquivalent(
- colorOptionModel.colorOption
- )
- ?: colorOptionModel.isSelected
- }
- .stateIn(viewModelScope)
- OptionItemViewModel<ColorOptionIconViewModel>(
- key =
- MutableStateFlow(colorOptionModel.key)
- as StateFlow<String>,
- payload =
- ColorOptionIconViewModel(
- colors[0],
- colors[1],
- colors[2],
- colors[3]
- ),
- text =
- Text.Loaded(
- colorSeedOption
- .getContentDescription(context)
- .toString()
- ),
- isSelected = isSelectedFlow,
- onClicked =
- isSelectedFlow.map { isSelected ->
- if (isSelected) {
- null
- } else {
- {
- viewModelScope.launch {
- interactor.select(colorOptionModel)
- }
+ 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.activeColorOption
+ .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)
}
}
- },
- )
- }
- }
- ColorType.PRESET_COLOR -> {
- colorOptionEntry.value.map { colorOptionModel ->
- val colorBundle: ColorBundle =
- colorOptionModel.colorOption as ColorBundle
- val primaryColor =
- colorBundle.previewInfo.resolvePrimaryColor(
- context.resources
- )
- val secondaryColor =
- colorBundle.previewInfo.resolveSecondaryColor(
- context.resources
- )
- val isSelectedFlow: StateFlow<Boolean> =
- interactor.activeColorOption
- .map {
- it?.colorOption?.isEquivalent(
- colorOptionModel.colorOption
- )
- ?: colorOptionModel.isSelected
}
- .stateIn(viewModelScope)
- OptionItemViewModel<ColorOptionIconViewModel>(
- key =
- MutableStateFlow(colorOptionModel.key)
- as StateFlow<String>,
- payload =
- ColorOptionIconViewModel(
- primaryColor,
- secondaryColor,
- primaryColor,
- secondaryColor
- ),
- text =
- Text.Loaded(
- colorBundle
- .getContentDescription(context)
- .toString()
- ),
- isSelected = isSelectedFlow,
- onClicked =
- isSelectedFlow.map { isSelected ->
- if (isSelected) {
- null
- } else {
- {
- viewModelScope.launch {
- interactor.select(colorOptionModel)
- }
- }
- }
- },
- )
- }
+ },
+ )
}
- }
- }
- .toMap()
- }
+ }
+ .toMap()
+ }
+ .shareIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/** 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]!!
- }
+ allColorOptions:
+ Map<ColorType, List<OptionItemViewModel<ColorOptionIconViewModel>>>,
+ selectedColorTypeIdOrNull ->
+ val selectedColorTypeId = selectedColorTypeIdOrNull ?: ColorType.WALLPAPER_COLOR
+ allColorOptions[selectedColorTypeId]!!
+ }
+ .shareIn(
+ scope = viewModelScope,
+ started = SharingStarted.Eagerly,
+ replay = 1,
+ )
/** The list of color options for the color section */
val colorSectionOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
- allColorOptions.map { allColorOptions ->
- val wallpaperOptions = allColorOptions[ColorType.WALLPAPER_COLOR]
- val presetOptions = allColorOptions[ColorType.PRESET_COLOR]
- val subOptions =
- wallpaperOptions!!.subList(0, min(COLOR_SECTION_OPTION_SIZE, wallpaperOptions.size))
- // Add additional options based on preset colors if size of wallpaper color options is
- // less than COLOR_SECTION_OPTION_SIZE
- val additionalSubOptions =
- presetOptions!!.subList(
- 0,
- min(
- max(0, COLOR_SECTION_OPTION_SIZE - wallpaperOptions.size),
- presetOptions.size,
+ allColorOptions
+ .map { allColorOptions ->
+ val wallpaperOptions = allColorOptions[ColorType.WALLPAPER_COLOR]
+ val presetOptions = allColorOptions[ColorType.PRESET_COLOR]
+ val subOptions =
+ wallpaperOptions!!.subList(
+ 0,
+ min(COLOR_SECTION_OPTION_SIZE, wallpaperOptions.size)
)
- )
- subOptions + additionalSubOptions
- }
+ // Add additional options based on preset colors if size of wallpaper color options
+ // is
+ // less than COLOR_SECTION_OPTION_SIZE
+ val additionalSubOptions =
+ presetOptions!!.subList(
+ 0,
+ min(
+ max(0, COLOR_SECTION_OPTION_SIZE - wallpaperOptions.size),
+ presetOptions.size,
+ )
+ )
+ subOptions + additionalSubOptions
+ }
+ .shareIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
class Factory(
private val context: Context,