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

Commit 68e0a82a authored by Catherine Liang's avatar Catherine Liang Committed by George Lin
Browse files

Color Picker Section Refactor (1/3)

Refactoring color picker section to use the same clean architecture as
color picker fragment. Also adjusted color picker section for large
screen to hide overflow option and show `more colors` button. This feature is gated by revamped UI flag

Test: unit tests, screen recording: https://drive.google.com/file/d/1HTfZtpEtSOayNn79N66q8GgynhNo3vDn/view?usp=sharing&resourcekey=0-j3W7aCh7_Sc_MhTiSQ6Kiw
Bug: 262924584
Change-Id: I1457da64bb01f504e41ba90565a83ebf6e2adfb8
parent ea078c3f
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
<!--
     Copyright (C) 2019 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.
-->
<!-- Represents the color icon (a palette) -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path android:fillColor="@android:color/white" android:pathData="M12,22Q9.95,22 8.125,21.212Q6.3,20.425 4.938,19.062Q3.575,17.7 2.788,15.875Q2,14.05 2,12Q2,9.925 2.812,8.1Q3.625,6.275 5.013,4.925Q6.4,3.575 8.25,2.787Q10.1,2 12.2,2Q14.2,2 15.975,2.688Q17.75,3.375 19.087,4.588Q20.425,5.8 21.212,7.463Q22,9.125 22,11.05Q22,13.925 20.25,15.462Q18.5,17 16,17H14.15Q13.925,17 13.838,17.125Q13.75,17.25 13.75,17.4Q13.75,17.7 14.125,18.262Q14.5,18.825 14.5,19.55Q14.5,20.8 13.812,21.4Q13.125,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM6.5,13Q7.15,13 7.575,12.575Q8,12.15 8,11.5Q8,10.85 7.575,10.425Q7.15,10 6.5,10Q5.85,10 5.425,10.425Q5,10.85 5,11.5Q5,12.15 5.425,12.575Q5.85,13 6.5,13ZM9.5,9Q10.15,9 10.575,8.575Q11,8.15 11,7.5Q11,6.85 10.575,6.425Q10.15,6 9.5,6Q8.85,6 8.425,6.425Q8,6.85 8,7.5Q8,8.15 8.425,8.575Q8.85,9 9.5,9ZM14.5,9Q15.15,9 15.575,8.575Q16,8.15 16,7.5Q16,6.85 15.575,6.425Q15.15,6 14.5,6Q13.85,6 13.425,6.425Q13,6.85 13,7.5Q13,8.15 13.425,8.575Q13.85,9 14.5,9ZM17.5,13Q18.15,13 18.575,12.575Q19,12.15 19,11.5Q19,10.85 18.575,10.425Q18.15,10 17.5,10Q16.85,10 16.425,10.425Q16,10.85 16,11.5Q16,12.15 16.425,12.575Q16.85,13 17.5,13ZM12,20Q12.225,20 12.363,19.875Q12.5,19.75 12.5,19.55Q12.5,19.2 12.125,18.725Q11.75,18.25 11.75,17.3Q11.75,16.25 12.475,15.625Q13.2,15 14.25,15H16Q17.65,15 18.825,14.037Q20,13.075 20,11.05Q20,8.025 17.688,6.012Q15.375,4 12.2,4Q8.8,4 6.4,6.325Q4,8.65 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/>
</vector>
+35 −13
Original line number Diff line number Diff line
@@ -14,18 +14,14 @@
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<com.android.customization.picker.color.ui.view.ColorSectionView2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/color_section_option_container"
<com.android.customization.picker.color.ui.view.ColorSectionView2 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="@dimen/section_bottom_padding"
    android:layout_marginHorizontal="@dimen/section_horizontal_padding"
    android:orientation="horizontal"
    android:background="@drawable/top_connected_section_background"
    android:paddingVertical="24dp"
    android:paddingHorizontal="24dp"
    android:weightSum="@integer/color_section_num_columns">
    android:orientation="vertical"
    android:background="@drawable/top_connected_section_background">

    <!--
        This is just an invisible placeholder put in place so that the parent keeps its height
@@ -36,10 +32,36 @@

        It's critical for any TextViews inside the included layout to have text.
        -->
    <LinearLayout
        android:id="@+id/color_section_option_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingVertical="24dp"
        android:paddingHorizontal="24dp"
        android:weightSum="@integer/color_section_num_columns">
        <include
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            layout="@layout/color_option_overflow_no_background"
            android:visibility="invisible"
            android:layout_weight="1"/>
    </LinearLayout>

    <TextView
        android:id="@+id/more_colors"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        android:minHeight="48dp"
        android:gravity="center"
        android:drawablePadding="12dp"
        android:drawableStart="@drawable/ic_nav_color"
        android:drawableTint="@color/text_color_primary"
        android:text="@string/more_colors"
        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
        android:textColor="@color/text_color_primary"
        android:visibility="gone"
        tools:ignore="UseCompatTextViewDrawableXml" />
</com.android.customization.picker.color.ui.view.ColorSectionView2>
+8 −0
Original line number Diff line number Diff line
@@ -412,4 +412,12 @@
    [CHAR LIMIT=64].
    -->
    <string name="more_settings_section_description">Text on lock screen, Now Playing, and more</string>

    <!--
    Label for button that lets the user navigate to a full-screen experience of selecting
    system colors.

    [CHAR LIMIT=128].
    -->
    <string name="more_colors">More Colors</string>
</resources>
+0 −243
Original line number Diff line number Diff line
/*
 * 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.model.color;

import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_HOME;
import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_LOCK;
import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET;

import android.app.Activity;
import android.app.WallpaperColors;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.stats.style.StyleEnums;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;

import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;

import com.android.customization.model.CustomizationManager;
import com.android.customization.model.theme.OverlayManagerCompat;
import com.android.customization.module.CustomizationInjector;
import com.android.customization.module.ThemesUserEventLogger;
import com.android.customization.picker.color.ui.fragment.ColorPickerFragment;
import com.android.customization.picker.color.ui.view.ColorSectionView2;
import com.android.wallpaper.R;
import com.android.wallpaper.model.CustomizationSectionController;
import com.android.wallpaper.model.WallpaperColorsViewModel;
import com.android.wallpaper.module.InjectorProvider;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.List;

/**
 * Color section view's controller for the logic of color customization.
 *
 * TODO (b/262924584): Convert ColorSectionController2 into Kotlin & use new architecture
 */
public class ColorSectionController2 implements CustomizationSectionController<ColorSectionView2> {

    private static final String TAG = "ColorSectionController";
    private static final long MIN_COLOR_APPLY_PERIOD = 500L;

    private final ThemesUserEventLogger mEventLogger;
    private final ColorCustomizationManager mColorManager;
    private final WallpaperColorsViewModel mWallpaperColorsViewModel;
    private final LifecycleOwner mLifecycleOwner;
    private final CustomizationSectionNavigationController mSectionNavigationController;

    private List<ColorOption> mWallpaperColorOptions = new ArrayList<>();
    private List<ColorOption> mPresetColorOptions = new ArrayList<>();
    private ColorOption mSelectedColor;
    @Nullable private WallpaperColors mHomeWallpaperColors;
    @Nullable private WallpaperColors mLockWallpaperColors;
    // Uses a boolean value to indicate whether wallpaper color is ready because WallpaperColors
    // maybe be null when it's ready.
    private boolean mHomeWallpaperColorsReady;
    private boolean mLockWallpaperColorsReady;
    private long mLastColorApplyingTime = 0L;
    private ColorSectionView2 mColorSectionView;

    public ColorSectionController2(Activity activity, WallpaperColorsViewModel viewModel,
            LifecycleOwner lifecycleOwner,
            CustomizationSectionNavigationController sectionNavigationController) {
        CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
        mEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(activity);
        mColorManager = ColorCustomizationManager.getInstance(activity,
                new OverlayManagerCompat(activity));
        mWallpaperColorsViewModel = viewModel;
        mLifecycleOwner = lifecycleOwner;
        mSectionNavigationController = sectionNavigationController;
    }

    @Override
    public boolean isAvailable(@Nullable Context context) {
        return context != null && ColorUtils.isMonetEnabled(context) && mColorManager.isAvailable();
    }

    @Override
    public ColorSectionView2 createView(Context context) {
        mColorSectionView = (ColorSectionView2) LayoutInflater.from(context).inflate(
                R.layout.color_section_view2, /* root= */ null);

        mWallpaperColorsViewModel.getHomeWallpaperColorsLiveData().observe(mLifecycleOwner,
                homeColors -> {
                    mHomeWallpaperColors = homeColors;
                    mHomeWallpaperColorsReady = true;
                    maybeLoadColors();
                });
        mWallpaperColorsViewModel.getLockWallpaperColorsLiveData().observe(mLifecycleOwner,
                lockColors -> {
                    mLockWallpaperColors = lockColors;
                    mLockWallpaperColorsReady = true;
                    maybeLoadColors();
                });
        return mColorSectionView;
    }

    private void maybeLoadColors() {
        if (mHomeWallpaperColorsReady && mLockWallpaperColorsReady) {
            mColorManager.setWallpaperColors(mHomeWallpaperColors, mLockWallpaperColors);
            loadColorOptions(/* reload= */ false);
        }
    }

    private void loadColorOptions(boolean reload) {
        mColorManager.fetchOptions(new CustomizationManager.OptionsFetchedListener<ColorOption>() {
            @Override
            public void onOptionsLoaded(List<ColorOption> options) {
                List<ColorOption> wallpaperColorOptions = new ArrayList<>();
                List<ColorOption> presetColorOptions = new ArrayList<>();
                for (ColorOption option : options) {
                    if (option instanceof ColorSeedOption) {
                        wallpaperColorOptions.add(option);
                    } else if (option instanceof ColorBundle) {
                        presetColorOptions.add(option);
                    }
                }
                mWallpaperColorOptions = wallpaperColorOptions;
                mPresetColorOptions = presetColorOptions;
                mSelectedColor = findActiveColorOption(mWallpaperColorOptions,
                        mPresetColorOptions);

                mColorSectionView.post(() -> setUpColorSectionView(mWallpaperColorOptions,
                        mPresetColorOptions));
            }

            @Override
            public void onError(@Nullable Throwable throwable) {
                if (throwable != null) {
                    Log.e(TAG, "Error loading theme bundles", throwable);
                }
            }
        }, reload);
    }

    private void setUpColorSectionView(List<ColorOption> wallpaperColorOptions,
            List<ColorOption> presetColorOptions) {
        int wallpaperOptionSize = wallpaperColorOptions.size();

        List<ColorOption> subOptions = wallpaperColorOptions.subList(0,
                Math.min(5, wallpaperOptionSize));
        // add additional options based on preset colors if there are less than 5 wallpaper colors
        List<ColorOption> additionalSubOptions = presetColorOptions.subList(0,
                Math.min(Math.max(0, 5 - wallpaperOptionSize), presetColorOptions.size()));
        subOptions.addAll(additionalSubOptions);

        mColorSectionView.setOverflowOnClick(() -> {
            mSectionNavigationController.navigateTo(new ColorPickerFragment());
            return null;
        });
        mColorSectionView.setColorOptionOnClick(selectedOption -> {
            if (mSelectedColor.equals(selectedOption)) {
                return null;
            }
            mSelectedColor = (ColorOption) selectedOption;
            // Post with delay for color option to run ripple.
            new Handler().postDelayed(()-> applyColor(mSelectedColor), /* delayMillis= */ 100);
            return null;
        });
        mColorSectionView.setItems(subOptions, mColorManager);
    }

    private ColorOption findActiveColorOption(List<ColorOption> wallpaperColorOptions,
            List<ColorOption> presetColorOptions) {
        ColorOption activeColorOption = null;
        for (ColorOption colorOption : Lists.newArrayList(
                Iterables.concat(wallpaperColorOptions, presetColorOptions))) {
            if (colorOption.isActive(mColorManager)) {
                activeColorOption = colorOption;
                break;
            }
        }
        // Use the first one option by default. This should not happen as above should have an
        // active option found.
        if (activeColorOption == null) {
            activeColorOption = wallpaperColorOptions.isEmpty()
                    ? presetColorOptions.get(0)
                    : wallpaperColorOptions.get(0);
        }
        return activeColorOption;
    }

    private void applyColor(ColorOption colorOption) {
        if (SystemClock.elapsedRealtime() - mLastColorApplyingTime < MIN_COLOR_APPLY_PERIOD) {
            return;
        }
        mLastColorApplyingTime = SystemClock.elapsedRealtime();
        mColorManager.apply(colorOption, new CustomizationManager.Callback() {
            @Override
            public void onSuccess() {
                mColorSectionView.announceForAccessibility(
                        mColorSectionView.getContext().getString(R.string.color_changed));
                mEventLogger.logColorApplied(getColorAction(colorOption), colorOption);
            }

            @Override
            public void onError(@Nullable Throwable throwable) {
                Log.w(TAG, "Apply theme with error: " + throwable);
            }
        });
    }

    private int getColorAction(ColorOption colorOption) {
        int action = StyleEnums.DEFAULT_ACTION;
        boolean isForBoth = mLockWallpaperColors == null || mLockWallpaperColors.equals(
                mHomeWallpaperColors);

        if (TextUtils.equals(colorOption.getSource(), COLOR_SOURCE_PRESET)) {
            action = StyleEnums.COLOR_PRESET_APPLIED;
        } else if (isForBoth) {
            action = StyleEnums.COLOR_WALLPAPER_HOME_LOCK_APPLIED;
        } else {
            switch (colorOption.getSource()) {
                case COLOR_SOURCE_HOME:
                    action = StyleEnums.COLOR_WALLPAPER_HOME_APPLIED;
                    break;
                case COLOR_SOURCE_LOCK:
                    action = StyleEnums.COLOR_WALLPAPER_LOCK_APPLIED;
                    break;
            }
        }
        return action;
    }
}
+11 −5
Original line number Diff line number Diff line
@@ -8,7 +8,6 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;

import com.android.customization.model.color.ColorSectionController;
import com.android.customization.model.color.ColorSectionController2;
import com.android.customization.model.grid.GridOptionsManager;
import com.android.customization.model.grid.GridSectionController;
import com.android.customization.model.mode.DarkModeSectionController;
@@ -18,6 +17,8 @@ import com.android.customization.model.themedicon.ThemedIconSwitchProvider;
import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor;
import com.android.customization.model.themedicon.domain.interactor.ThemedIconSnapshotRestorer;
import com.android.customization.picker.clock.data.repository.ClockRegistryProvider;
import com.android.customization.picker.color.ui.section.ColorSectionController2;
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel;
import com.android.customization.picker.notifications.ui.section.NotificationSectionController;
import com.android.customization.picker.notifications.ui.viewmodel.NotificationSectionViewModel;
import com.android.customization.picker.preview.ui.section.PreviewWithClockCarouselSectionController;
@@ -48,6 +49,7 @@ import java.util.List;
/** {@link CustomizationSections} for the customization picker. */
public final class DefaultCustomizationSections implements CustomizationSections {

    private final ColorPickerViewModel.Factory mColorPickerViewModelFactory;
    private final KeyguardQuickAffordancePickerInteractor mKeyguardQuickAffordancePickerInteractor;
    private final KeyguardQuickAffordancePickerViewModel.Factory
            mKeyguardQuickAffordancePickerViewModelFactory;
@@ -63,6 +65,7 @@ public final class DefaultCustomizationSections implements CustomizationSections
    private final ThemedIconInteractor mThemedIconInteractor;

    public DefaultCustomizationSections(
            ColorPickerViewModel.Factory colorPickerViewModelFactory,
            KeyguardQuickAffordancePickerInteractor keyguardQuickAffordancePickerInteractor,
            KeyguardQuickAffordancePickerViewModel.Factory
                    keyguardQuickAffordancePickerViewModelFactory,
@@ -74,6 +77,7 @@ public final class DefaultCustomizationSections implements CustomizationSections
            DarkModeSnapshotRestorer darkModeSnapshotRestorer,
            ThemedIconSnapshotRestorer themedIconSnapshotRestorer,
            ThemedIconInteractor themedIconInteractor) {
        mColorPickerViewModelFactory = colorPickerViewModelFactory;
        mKeyguardQuickAffordancePickerInteractor = keyguardQuickAffordancePickerInteractor;
        mKeyguardQuickAffordancePickerViewModelFactory =
                keyguardQuickAffordancePickerViewModelFactory;
@@ -127,10 +131,12 @@ public final class DefaultCustomizationSections implements CustomizationSections
                new ConnectedSectionController(
                        // Theme color section.
                        new ColorSectionController2(
                                sectionNavigationController,
                                new ViewModelProvider(
                                        activity,
                                wallpaperColorsViewModel,
                                lifecycleOwner,
                                sectionNavigationController),
                                        mColorPickerViewModelFactory)
                                        .get(ColorPickerViewModel.class),
                                lifecycleOwner),
                        // Wallpaper quick switch section.
                        new WallpaperQuickSwitchSectionController(
                                screen,
Loading