Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 69d532b7 authored by George Lin's avatar George Lin Committed by Android (Google) Code Review
Browse files

Merge "Shortcut preview update (2/3)" into main

parents 05bf28b2 1806111e
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ constructor(private val defaultCustomizationOptionsBinder: DefaultCustomizationO
        homeScreenCustomizationOptionEntries: List<Pair<CustomizationOption, View>>,
        customizationOptionFloatingSheetViewMap: Map<CustomizationOption, View>?,
        viewModel: CustomizationOptionsViewModel,
        lifecycleOwner: LifecycleOwner
        lifecycleOwner: LifecycleOwner,
    ) {
        defaultCustomizationOptionsBinder.bind(
            view,
@@ -104,7 +104,7 @@ constructor(private val defaultCustomizationOptionsBinder: DefaultCustomizationO
                }

                launch {
                    viewModel.keyguardQuickAffordanceSummery.collect { summary ->
                    viewModel.keyguardQuickAffordancePickerViewModel2.summary.collect { summary ->
                        optionShortcutDescription?.let {
                            TextViewBinder.bind(
                                view = it,
+66 −0
Original line number Diff line number Diff line
/*
 * 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.widget.Button
import android.widget.FrameLayout
import android.widget.Toolbar
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
import com.android.wallpaper.picker.customization.ui.binder.DefaultToolbarBinder
import com.android.wallpaper.picker.customization.ui.binder.ToolbarBinder
import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationOptionsViewModel
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.launch

@Singleton
class ThemePickerToolbarBinder
@Inject
constructor(private val defaultToolbarBinder: DefaultToolbarBinder) : ToolbarBinder {

    override fun bind(
        navButton: FrameLayout,
        toolbar: Toolbar,
        applyButton: Button,
        viewModel: CustomizationOptionsViewModel,
        lifecycleOwner: LifecycleOwner,
    ) {
        defaultToolbarBinder.bind(navButton, toolbar, applyButton, viewModel, lifecycleOwner)

        if (viewModel !is ThemePickerCustomizationOptionsViewModel) {
            throw IllegalArgumentException(
                "viewModel $viewModel is not a ThemePickerCustomizationOptionsViewModel."
            )
        }

        lifecycleOwner.lifecycleScope.launch {
            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    viewModel.keyguardQuickAffordancePickerViewModel2.onApply.collect { onApply ->
                        applyButton.setOnClickListener {
                            onApply?.invoke()?.let { viewModel.deselectOption() }
                        }
                    }
                }
            }
        }
    }
}
+137 −97
Original line number Diff line number Diff line
@@ -25,7 +25,9 @@ import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordanceSlotViewModel
import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordanceSummaryViewModel
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEYGUARD_QUICK_AFFORDANCE_ID_NONE
import com.android.themepicker.R
import com.android.wallpaper.picker.common.button.ui.viewmodel.ButtonStyle
import com.android.wallpaper.picker.common.button.ui.viewmodel.ButtonViewModel
@@ -81,18 +83,28 @@ constructor(
                started = SharingStarted.WhileSubscribed(),
                initialValue = "",
            )
    private val _selectedQuickAffordances = MutableStateFlow<Map<String, String>>(emptyMap())
    val selectedQuickAffordances: Flow<Map<String, String>> =
        _selectedQuickAffordances.asStateFlow()

    fun resetPreview() {
        _selectedQuickAffordances.tryEmit(emptyMap())
        _selectedSlotId.tryEmit(SLOT_ID_BOTTOM_START)
    }

    /** View-models for each slot, keyed by slot ID. */
    private val slots: Flow<Map<String, KeyguardQuickAffordanceSlotViewModel>> =
    private val slots: StateFlow<Map<String, KeyguardQuickAffordanceSlotViewModel>> =
        combine(
                quickAffordanceInteractor.slots,
                quickAffordanceInteractor.affordances,
                quickAffordanceInteractor.selections,
                selectedQuickAffordances,
                selectedSlotId,
        ) { slots, affordances, selections, selectedSlotId ->
            ) { slots, affordances, selections, selectedQuickAffordances, selectedSlotId ->
                slots.associate { slot ->
                    val selectedAffordanceIds =
                    selections
                        selectedQuickAffordances[slot.id]?.let { setOf(it) }
                            ?: selections
                                .filter { selection -> selection.slotId == slot.id }
                                .map { selection -> selection.affordanceId }
                                .toSet()
@@ -100,6 +112,7 @@ constructor(
                        affordances.filter { affordance ->
                            selectedAffordanceIds.contains(affordance.id)
                        }

                    val isSelected = selectedSlotId == slot.id
                    slot.id to
                        KeyguardQuickAffordanceSlotViewModel(
@@ -114,7 +127,9 @@ constructor(
                                        payload =
                                            Icon.Loaded(
                                                drawable =
                                                getAffordanceIcon(affordanceModel.iconResourceId),
                                                    getAffordanceIcon(
                                                        affordanceModel.iconResourceId
                                                    ),
                                                contentDescription =
                                                    Text.Loaded(getSlotContentDescription(slot.id)),
                                            ),
@@ -135,6 +150,11 @@ constructor(
                        )
                }
            }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = emptyMap(),
            )

    val tabs: Flow<List<FloatingToolbarTabViewModel>> =
        slots.map { slotById ->
@@ -171,7 +191,17 @@ constructor(
    /** The list of all available quick affordances for the selected slot. */
    val quickAffordances: Flow<List<OptionItemViewModel<Icon>>> =
        quickAffordanceInteractor.affordances.map { affordances ->
            val isNoneSelected = selectedAffordanceIds.map { it.isEmpty() }.stateIn(viewModelScope)
            val isNoneSelected =
                combine(
                        selectedSlotId,
                        selectedQuickAffordances,
                        selectedAffordanceIds,
                    ) { selectedSlotId, selectedQuickAffordances, selectedAffordanceIds ->
                        selectedQuickAffordances[selectedSlotId]?.let {
                            it == KEYGUARD_QUICK_AFFORDANCE_ID_NONE
                        } ?: selectedAffordanceIds.isEmpty()
                    }
                    .stateIn(viewModelScope)
            listOf(
                none(
                    slotId = selectedSlotId,
@@ -183,15 +213,11 @@ constructor(
                        ) { isSelected, selectedSlotId ->
                            if (!isSelected) {
                                {
                                    viewModelScope.launch {
                                        quickAffordanceInteractor.unselectAllFromSlot(
                                            selectedSlotId
                                        )
                                        logger.logShortcutApplied(
                                            shortcut = "none",
                                            shortcutSlotId = selectedSlotId,
                                        )
                                    val newMap =
                                        _selectedQuickAffordances.value.toMutableMap().apply {
                                            put(selectedSlotId, KEYGUARD_QUICK_AFFORDANCE_ID_NONE)
                                        }
                                    _selectedQuickAffordances.tryEmit(newMap)
                                }
                            } else {
                                null
@@ -202,8 +228,15 @@ constructor(
                affordances.map { affordance ->
                    val affordanceIcon = getAffordanceIcon(affordance.iconResourceId)
                    val isSelectedFlow: StateFlow<Boolean> =
                        selectedAffordanceIds
                            .map { it.contains(affordance.id) }
                        combine(
                                selectedSlotId,
                                selectedQuickAffordances,
                                selectedAffordanceIds,
                            ) { selectedSlotId, selectedQuickAffordances, selectedAffordanceIds ->
                                selectedQuickAffordances[selectedSlotId]?.let {
                                    it == affordance.id
                                } ?: selectedAffordanceIds.contains(affordance.id)
                            }
                            .stateIn(viewModelScope)
                    OptionItemViewModel<Icon>(
                        key =
@@ -221,16 +254,11 @@ constructor(
                                ) { isSelected, selectedSlotId ->
                                    if (!isSelected) {
                                        {
                                            viewModelScope.launch {
                                                quickAffordanceInteractor.select(
                                                    slotId = selectedSlotId,
                                                    affordanceId = affordance.id,
                                                )
                                                logger.logShortcutApplied(
                                                    shortcut = affordance.id,
                                                    shortcutSlotId = selectedSlotId,
                                                )
                                            }
                                            val newMap =
                                                _selectedQuickAffordances.value
                                                    .toMutableMap()
                                                    .apply { put(selectedSlotId, affordance.id) }
                                            _selectedQuickAffordances.tryEmit(newMap)
                                        }
                                    } else {
                                        null
@@ -258,6 +286,34 @@ constructor(
                }
        }

    val onApply: Flow<(() -> Unit)?> =
        selectedQuickAffordances.map {
            if (it.isEmpty()) {
                null
            } else {
                {
                    it.forEach { entry ->
                        val slotId = entry.key
                        val affordanceId = entry.value
                        viewModelScope.launch {
                            if (slotId == KEYGUARD_QUICK_AFFORDANCE_ID_NONE) {
                                quickAffordanceInteractor.unselectAllFromSlot(slotId)
                            } else {
                                quickAffordanceInteractor.select(
                                    slotId = slotId,
                                    affordanceId = affordanceId
                                )
                            }
                            logger.logShortcutApplied(
                                shortcut = affordanceId,
                                shortcutSlotId = slotId,
                            )
                        }
                    }
                }
            }
        }

    private val _dialog = MutableStateFlow<DialogViewModel?>(null)
    /**
     * The current dialog to show. If `null`, no dialog should be shown.
@@ -365,10 +421,8 @@ constructor(
    private fun getSlotName(slotId: String): String {
        return applicationContext.getString(
            when (slotId) {
                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START ->
                    R.string.keyguard_slot_name_bottom_start
                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END ->
                    R.string.keyguard_slot_name_bottom_end
                SLOT_ID_BOTTOM_START -> R.string.keyguard_slot_name_bottom_start
                SLOT_ID_BOTTOM_END -> R.string.keyguard_slot_name_bottom_end
                else -> error("No name for slot with ID of \"$slotId\"!")
            }
        )
@@ -377,10 +431,8 @@ constructor(
    private fun getSlotContentDescription(slotId: String): String {
        return applicationContext.getString(
            when (slotId) {
                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START ->
                    R.string.keyguard_slot_name_bottom_start
                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END ->
                    R.string.keyguard_slot_name_bottom_end
                SLOT_ID_BOTTOM_START -> R.string.keyguard_slot_name_bottom_start
                SLOT_ID_BOTTOM_END -> R.string.keyguard_slot_name_bottom_end
                else -> error("No accessibility label for slot with ID \"$slotId\"!")
            }
        )
@@ -393,15 +445,9 @@ constructor(
    val summary: Flow<KeyguardQuickAffordanceSummaryViewModel> =
        slots.map { slots ->
            val icon2 =
                (slots[KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END]
                        ?.selectedQuickAffordances
                        ?.firstOrNull())
                    ?.payload
                (slots[SLOT_ID_BOTTOM_END]?.selectedQuickAffordances?.firstOrNull())?.payload
            val icon1 =
                (slots[KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START]
                        ?.selectedQuickAffordances
                        ?.firstOrNull())
                    ?.payload
                (slots[SLOT_ID_BOTTOM_START]?.selectedQuickAffordances?.firstOrNull())?.payload

            KeyguardQuickAffordanceSummaryViewModel(
                description = toDescriptionText(applicationContext, slots),
@@ -424,15 +470,9 @@ constructor(
        slots: Map<String, KeyguardQuickAffordanceSlotViewModel>,
    ): Text {
        val bottomStartAffordanceName =
            slots[KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START]
                ?.selectedQuickAffordances
                ?.firstOrNull()
                ?.text
            slots[SLOT_ID_BOTTOM_START]?.selectedQuickAffordances?.firstOrNull()?.text
        val bottomEndAffordanceName =
            slots[KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END]
                ?.selectedQuickAffordances
                ?.firstOrNull()
                ?.text
            slots[SLOT_ID_BOTTOM_END]?.selectedQuickAffordances?.firstOrNull()?.text

        return when {
            bottomStartAffordanceName != null && bottomEndAffordanceName != null -> {
+4 −3
Original line number Diff line number Diff line
@@ -48,7 +48,10 @@ constructor(

    override val selectedOption = defaultCustomizationOptionsViewModel.selectedOption

    override fun deselectOption(): Boolean = defaultCustomizationOptionsViewModel.deselectOption()
    override fun deselectOption(): Boolean {
        keyguardQuickAffordancePickerViewModel2.resetPreview()
        return defaultCustomizationOptionsViewModel.deselectOption()
    }

    val onCustomizeClockClicked: Flow<(() -> Unit)?> =
        selectedOption.map {
@@ -77,8 +80,6 @@ constructor(
            }
        }

    val keyguardQuickAffordanceSummery = keyguardQuickAffordancePickerViewModel2.summary

    val onCustomizeColorsClicked: Flow<(() -> Unit)?> =
        selectedOption.map {
            if (it == null) {
+124 −0
Original line number Diff line number Diff line
/*
 * 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.picker.common.preview.ui.binder

import android.os.Bundle
import android.os.Message
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_QUICK_AFFORDANCE_ID
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_SLOT_ID
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_DEFAULT_PREVIEW
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES
import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption
import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
import com.android.wallpaper.picker.common.preview.ui.binder.WorkspaceCallbackBinder.Companion.sendMessage
import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationOptionsViewModel
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.launch

@Singleton
class ThemePickerWorkspaceCallbackBinder
@Inject
constructor(private val defaultWorkspaceCallbackBinder: DefaultWorkspaceCallbackBinder) :
    WorkspaceCallbackBinder {

    override fun bind(
        workspaceCallback: Message,
        viewModel: CustomizationOptionsViewModel,
        lifecycleOwner: LifecycleOwner,
    ) {
        defaultWorkspaceCallbackBinder.bind(
            workspaceCallback = workspaceCallback,
            viewModel = viewModel,
            lifecycleOwner = lifecycleOwner,
        )

        if (viewModel !is ThemePickerCustomizationOptionsViewModel) {
            throw IllegalArgumentException(
                "viewModel $viewModel is not a ThemePickerCustomizationOptionsViewModel."
            )
        }

        lifecycleOwner.lifecycleScope.launch {
            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    viewModel.selectedOption.collect {
                        when (it) {
                            ThemePickerLockCustomizationOption.SHORTCUTS ->
                                workspaceCallback.sendMessage(
                                    MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES,
                                    Bundle().apply {
                                        putString(
                                            KEY_INITIALLY_SELECTED_SLOT_ID,
                                            SLOT_ID_BOTTOM_START,
                                        )
                                    }
                                )
                            else ->
                                workspaceCallback.sendMessage(
                                    MESSAGE_ID_DEFAULT_PREVIEW,
                                    Bundle.EMPTY,
                                )
                        }
                    }
                }

                launch {
                    viewModel.keyguardQuickAffordancePickerViewModel2.selectedSlotId.collect {
                        workspaceCallback.sendMessage(
                            MESSAGE_ID_SLOT_SELECTED,
                            Bundle().apply { putString(KEY_SLOT_ID, it) },
                        )
                    }
                }

                launch {
                    viewModel.keyguardQuickAffordancePickerViewModel2.selectedQuickAffordances
                        .collect {
                            it[SLOT_ID_BOTTOM_START]?.let {
                                workspaceCallback.sendMessage(
                                    MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED,
                                    Bundle().apply {
                                        putString(KEY_SLOT_ID, SLOT_ID_BOTTOM_START)
                                        putString(KEY_QUICK_AFFORDANCE_ID, it)
                                    },
                                )
                            }
                            it[SLOT_ID_BOTTOM_END]?.let {
                                workspaceCallback.sendMessage(
                                    MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED,
                                    Bundle().apply {
                                        putString(KEY_SLOT_ID, SLOT_ID_BOTTOM_END)
                                        putString(KEY_QUICK_AFFORDANCE_ID, it)
                                    },
                                )
                            }
                        }
                }
            }
        }
    }
}
Loading