diff options
Diffstat (limited to 'src/com/android/customization/picker/clock/ui/view')
5 files changed, 341 insertions, 0 deletions
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt new file mode 100644 index 00000000..90d7c42d --- /dev/null +++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt @@ -0,0 +1,86 @@ +/* + * 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.clock.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.constraintlayout.helper.widget.Carousel +import androidx.core.view.get +import com.android.wallpaper.R + +class ClockCarouselView( + context: Context, + attrs: AttributeSet, +) : + FrameLayout( + context, + attrs, + ) { + + private val carousel: Carousel + private lateinit var adapter: ClockCarouselAdapter + + init { + val view = LayoutInflater.from(context).inflate(R.layout.clock_carousel, this) + carousel = view.requireViewById(R.id.carousel) + } + + fun setUpClockCarouselView( + clockIds: List<String>, + onGetClockPreview: (clockId: String) -> View, + onClockSelected: (clockId: String) -> Unit, + ) { + adapter = ClockCarouselAdapter(clockIds, onGetClockPreview, onClockSelected) + carousel.setAdapter(adapter) + carousel.refresh() + } + + fun setSelectedClockIndex( + index: Int, + ) { + carousel.jumpToIndex(index) + } + + class ClockCarouselAdapter( + val clockIds: List<String>, + val onGetClockPreview: (clockId: String) -> View, + val onClockSelected: (clockId: String) -> Unit, + ) : Carousel.Adapter { + + override fun count(): Int { + return clockIds.size + } + + override fun populate(view: View?, index: Int) { + val viewRoot = view as ViewGroup + val clockHostView = viewRoot[0] as ViewGroup + clockHostView.removeAllViews() + val clockView = onGetClockPreview(clockIds[index]) + // The clock view might still be attached to an existing parent. Detach before adding to + // another parent. + (clockView.parent as? ViewGroup)?.removeView(clockView) + clockHostView.addView(clockView) + } + + override fun onNewItem(index: Int) { + onClockSelected.invoke(clockIds[index]) + } + } +} diff --git a/src/com/android/customization/picker/clock/ui/view/ClockSectionView.kt b/src/com/android/customization/picker/clock/ui/view/ClockSectionView.kt new file mode 100644 index 00000000..cca107c4 --- /dev/null +++ b/src/com/android/customization/picker/clock/ui/view/ClockSectionView.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 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.clock.ui.view + +import android.content.Context +import android.util.AttributeSet +import com.android.wallpaper.picker.SectionView + +/** The [SectionView] for app clock. */ +class ClockSectionView(context: Context?, attrs: AttributeSet?) : SectionView(context, attrs) diff --git a/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt b/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt new file mode 100644 index 00000000..909491a3 --- /dev/null +++ b/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt @@ -0,0 +1,50 @@ +/* + * 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.clock.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.RadioButton +import com.android.customization.picker.clock.shared.ClockSize +import com.android.wallpaper.R + +/** The radio button group to pick the clock size. */ +class ClockSizeRadioButtonGroup( + context: Context, + attrs: AttributeSet?, +) : FrameLayout(context, attrs) { + + interface OnRadioButtonClickListener { + fun onClick(size: ClockSize) + } + + val radioButtonDynamic: RadioButton + val radioButtonSmall: RadioButton + var onRadioButtonClickListener: OnRadioButtonClickListener? = null + + init { + LayoutInflater.from(context).inflate(R.layout.clock_size_radio_button_group, this, true) + radioButtonDynamic = requireViewById(R.id.radio_button_dynamic) + val buttonDynamic = requireViewById<View>(R.id.button_container_dynamic) + buttonDynamic.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.DYNAMIC) } + radioButtonSmall = requireViewById(R.id.radio_button_large) + val buttonLarge = requireViewById<View>(R.id.button_container_small) + buttonLarge.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.SMALL) } + } +} diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt new file mode 100644 index 00000000..7f480dee --- /dev/null +++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt @@ -0,0 +1,75 @@ +/* + * 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.clock.ui.view + +import android.app.Activity +import android.view.View +import androidx.annotation.ColorInt +import com.android.systemui.plugins.ClockController +import com.android.systemui.shared.clocks.ClockRegistry +import com.android.wallpaper.R +import com.android.wallpaper.util.ScreenSizeCalculator +import com.android.wallpaper.util.TimeUtils.TimeTicker + +class ClockViewFactory( + private val activity: Activity, + private val registry: ClockRegistry, +) { + private val clockControllers: HashMap<String, ClockController> = HashMap() + private var ticker: TimeTicker? = null + + fun getView(clockId: String): View { + return (clockControllers[clockId] ?: initClockController(clockId)).largeClock.view + } + + fun updateColorForAllClocks(@ColorInt seedColor: Int?) { + clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) } + } + + fun updateColor(clockId: String, @ColorInt seedColor: Int?) { + return (clockControllers[clockId] ?: initClockController(clockId)) + .events + .onSeedColorChanged(seedColor) + } + + fun registerTimeTicker() { + ticker = + TimeTicker.registerNewReceiver(activity.applicationContext) { + clockControllers.values.forEach { it.largeClock.events.onTimeTick() } + } + } + + fun unregisterTimeTicker() { + activity.applicationContext.unregisterReceiver(ticker) + } + + private fun initClockController(clockId: String): ClockController { + val controller = + registry.createExampleClock(clockId).also { it?.initialize(activity.resources, 0f, 0f) } + checkNotNull(controller) + val screenSizeCalculator = ScreenSizeCalculator.getInstance() + val screenSize = screenSizeCalculator.getScreenSize(activity.windowManager.defaultDisplay) + val ratio = + activity.resources.getDimensionPixelSize(R.dimen.screen_preview_height).toFloat() / + screenSize.y.toFloat() + controller.largeClock.events.onFontSettingChanged( + activity.resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() * + ratio + ) + clockControllers[clockId] = controller + return controller + } +} diff --git a/src/com/android/customization/picker/clock/ui/view/SaturationProgressDrawable.kt b/src/com/android/customization/picker/clock/ui/view/SaturationProgressDrawable.kt new file mode 100644 index 00000000..9e1d7890 --- /dev/null +++ b/src/com/android/customization/picker/clock/ui/view/SaturationProgressDrawable.kt @@ -0,0 +1,107 @@ +/* + * 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.clock.ui.view + +import android.content.pm.ActivityInfo +import android.content.res.Resources +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.graphics.drawable.DrawableWrapper +import android.graphics.drawable.InsetDrawable + +/** + * [DrawableWrapper] to use in the progress of brightness slider. + * + * This drawable is used to change the bounds of the enclosed drawable depending on the level to + * simulate a sliding progress, instead of using clipping or scaling. That way, the shape of the + * edges is maintained. + * + * Meant to be used with a rounded ends background, it will also prevent deformation when the slider + * is meant to be smaller than the rounded corner. The background should have rounded corners that + * are half of the height. + * + * This class also assumes that a "thumb" icon exists within the end's edge of the progress + * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb + * icon which puts the icon directly underneath the user's finger. + */ +class SaturationProgressDrawable @JvmOverloads constructor(drawable: Drawable? = null) : + InsetDrawable(drawable, 0) { + + companion object { + private const val MAX_LEVEL = 10000 + } + + override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean { + onLevelChange(level) + return super.onLayoutDirectionChanged(layoutDirection) + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + onLevelChange(level) + } + + override fun onLevelChange(level: Int): Boolean { + val drawableBounds = drawable?.bounds!! + + // The thumb offset shifts the sun icon directly under the user's thumb + val thumbOffset = bounds.height() / 2 + val width = bounds.width() * level / MAX_LEVEL + thumbOffset + + // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width + // TODO (b/268541542) Test if RTL devices also works for the slider + drawable?.setBounds( + bounds.left, + drawableBounds.top, + width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()), + drawableBounds.bottom + ) + return super.onLevelChange(level) + } + + override fun getConstantState(): ConstantState { + // This should not be null as it was created with a state in the constructor. + return RoundedCornerState(super.getConstantState()!!) + } + + override fun getChangingConfigurations(): Int { + return super.getChangingConfigurations() or ActivityInfo.CONFIG_DENSITY + } + + override fun canApplyTheme(): Boolean { + return (drawable?.canApplyTheme() ?: false) || super.canApplyTheme() + } + + private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() { + override fun newDrawable(): Drawable { + return newDrawable(null, null) + } + + override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable { + val wrapper = wrappedState.newDrawable(res, theme) as DrawableWrapper + return SaturationProgressDrawable(wrapper.drawable) + } + + override fun getChangingConfigurations(): Int { + return wrappedState.changingConfigurations + } + + override fun canApplyTheme(): Boolean { + return true + } + } +} |
