summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Lin <giolin@google.com>2023-01-26 06:00:28 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-01-26 06:00:28 +0000
commit1ff19de353df4f4dd4d6c18364fc0128f69bd9a8 (patch)
treeb1cbcd3de2ed7cab4b55f72d0c6f2dc6c5571f3f
parent5dad51230d9c66d283dab021a6a566920e5945d4 (diff)
parent906d65e2dd699d95eb13fd3413ec66e8a3f6fe5d (diff)
downloadThemePicker-1ff19de353df4f4dd4d6c18364fc0128f69bd9a8.tar.gz
Merge "[TP] Clock Settings" into tm-qpr-dev am: 906d65e2dd
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/ThemePicker/+/20929223 Change-Id: Ib216697ba1a440dee94fe8e09380bfab7f0f77ee Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--res/layout/clock_size_radio_button_group.xml77
-rw-r--r--res/layout/fragment_clock_settings.xml103
-rwxr-xr-xres/values/strings.xml15
-rw-r--r--src/com/android/customization/picker/clock/ClockFacePickerActivity.java82
-rw-r--r--src/com/android/customization/picker/clock/ClockFragment.java209
-rw-r--r--src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt5
-rw-r--r--src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt11
-rw-r--r--src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt8
-rw-r--r--src/com/android/customization/picker/clock/shared/ClockSize.kt22
-rw-r--r--src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt69
-rw-r--r--src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt92
-rw-r--r--src/com/android/customization/picker/clock/ui/fragment/ClockCustomFragment.java (renamed from src/com/android/customization/picker/clock/ClockCustomFragment.java)2
-rw-r--r--src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt100
-rw-r--r--src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt50
-rw-r--r--src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModel.kt28
-rw-r--r--src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt69
-rw-r--r--src/com/android/customization/picker/common/ui/view/ItemSpacing.kt49
-rw-r--r--src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt33
-rw-r--r--tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt9
-rw-r--r--tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt48
-rw-r--r--tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt68
21 files changed, 826 insertions, 323 deletions
diff --git a/res/layout/clock_size_radio_button_group.xml b/res/layout/clock_size_radio_button_group.xml
new file mode 100644
index 00000000..d520756a
--- /dev/null
+++ b/res/layout/clock_size_radio_button_group.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/button_container_dynamic"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"
+ android:orientation="horizontal">
+
+ <RadioButton
+ android:id="@+id/radio_button_dynamic"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="8dp"
+ android:clickable="false" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/SectionTitleTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/clock_size_dynamic" />
+
+ <TextView
+ style="@style/SectionSubtitleTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/clock_size_dynamic_description" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/button_container_large"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <RadioButton
+ android:id="@+id/radio_button_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="8dp"
+ android:clickable="false" />
+
+ <TextView
+ style="@style/SectionTitleTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/clock_size_large" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/fragment_clock_settings.xml b/res/layout/fragment_clock_settings.xml
new file mode 100644
index 00000000..7268b347
--- /dev/null
+++ b/res/layout/fragment_clock_settings.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/section_header_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/section_header" />
+ </FrameLayout>
+
+ <com.android.wallpaper.picker.DisplayAspectRatioFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:paddingTop="36dp"
+ android:paddingBottom="40dp">
+
+ <include
+ android:id="@+id/preview"
+ layout="@layout/wallpaper_preview_card"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"/>
+ </com.android.wallpaper.picker.DisplayAspectRatioFrameLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginBottom="28dp"
+ android:background="@drawable/picker_fragment_background"
+ android:paddingTop="22dp"
+ android:paddingBottom="62dp">
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/tabs"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:paddingHorizontal="16dp"
+ android:layout_gravity="center_horizontal"/>
+
+ <!--
+ This is just an invisible placeholder put in place so that the parent keeps its height
+ stable as the RecyclerView updates from 0 items to N items. Keeping it stable allows the
+ layout logic to keep the size of the preview container stable as well, which bodes well
+ for setting up the SurfaceView for remote rendering without changing its size after the
+ content is loaded into the RecyclerView.
+
+ It's critical for any TextViews inside the included layout to have text.
+ -->
+ <include
+ layout="@layout/picker_fragment_tab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="invisible" />
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@id/affordances"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:paddingHorizontal="16dp"
+ android:visibility="gone"/>
+
+ <com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup
+ android:id="@+id/clock_size_radio_button_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="16dp" />
+ </FrameLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5da2c334..313fea89 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,6 +36,21 @@
<!-- Title of a section of the customization picker where the user can configure Clock face. [CHAR LIMIT=15] -->
<string name="clock_settings_title">Clock Settings</string>
+ <!-- Title of a tab to change the clock color. [CHAR LIMIT=15] -->
+ <string name="clock_color">Color</string>
+
+ <!-- Title of a tab to change the clock size. [CHAR LIMIT=15] -->
+ <string name="clock_size">Size</string>
+
+ <!-- Title of a radio button to apply clock size dynamic. [CHAR LIMIT=15] -->
+ <string name="clock_size_dynamic">Dynamic</string>
+
+ <!-- Description of a radio button to apply clock size dynamic. [CHAR LIMIT=NONE] -->
+ <string name="clock_size_dynamic_description">Clock size changes according to lock screen content</string>
+
+ <!-- Title of a radio button to apply clock size large. [CHAR LIMIT=15] -->
+ <string name="clock_size_large">Large</string>
+
<!-- Title of a section of the customization picker where the user can select a Grid size for
the home screen. [CHAR LIMIT=15] -->
<string name="grid_title">App grid</string>
diff --git a/src/com/android/customization/picker/clock/ClockFacePickerActivity.java b/src/com/android/customization/picker/clock/ClockFacePickerActivity.java
deleted file mode 100644
index 5e512341..00000000
--- a/src/com/android/customization/picker/clock/ClockFacePickerActivity.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.
- */
-package com.android.customization.picker.clock;
-
-import android.content.Intent;
-import android.os.Bundle;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-import com.android.customization.model.clock.BaseClockManager;
-import com.android.customization.model.clock.Clockface;
-import com.android.customization.model.clock.ContentProviderClockProvider;
-import com.android.customization.picker.clock.ClockFragment.ClockFragmentHost;
-import com.android.wallpaper.R;
-
-/**
- * Activity allowing for the clock face picker to be linked to from other setup flows.
- *
- * This should be used with startActivityForResult. The resulting intent contains an extra
- * "clock_face_name" with the id of the picked clock face.
- */
-public class ClockFacePickerActivity extends FragmentActivity implements ClockFragmentHost {
-
- private static final String EXTRA_CLOCK_FACE_NAME = "clock_face_name";
-
- private BaseClockManager mClockManager;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_clock_face_picker);
-
- // Creating a class that overrides {@link ClockManager#apply} to return the clock id to the
- // calling activity instead of putting the value into settings.
- //
- mClockManager = new BaseClockManager(
- new ContentProviderClockProvider(ClockFacePickerActivity.this)) {
-
- @Override
- protected void handleApply(Clockface option, Callback callback) {
- Intent result = new Intent();
- result.putExtra(EXTRA_CLOCK_FACE_NAME, option.getId());
- setResult(RESULT_OK, result);
- callback.onSuccess();
- finish();
- }
-
- @Override
- protected String lookUpCurrentClock() {
- return getIntent().getStringExtra(EXTRA_CLOCK_FACE_NAME);
- }
- };
- if (!mClockManager.isAvailable()) {
- finish();
- } else {
- final FragmentManager fm = getSupportFragmentManager();
- final FragmentTransaction fragmentTransaction = fm.beginTransaction();
- final ClockFragment clockFragment = ClockFragment.newInstance(
- getString(R.string.clock_title));
- fragmentTransaction.replace(R.id.fragment_container, clockFragment);
- fragmentTransaction.commitNow();
- }
- }
-
- @Override
- public BaseClockManager getClockManager() {
- return mClockManager;
- }
-}
diff --git a/src/com/android/customization/picker/clock/ClockFragment.java b/src/com/android/customization/picker/clock/ClockFragment.java
deleted file mode 100644
index bc02ae34..00000000
--- a/src/com/android/customization/picker/clock/ClockFragment.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.
- */
-package com.android.customization.picker.clock;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.widget.ContentLoadingProgressBar;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.customization.model.CustomizationManager.Callback;
-import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
-import com.android.customization.model.clock.BaseClockManager;
-import com.android.customization.model.clock.Clockface;
-import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.BasePreviewAdapter;
-import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
-import com.android.customization.widget.OptionSelectorController;
-import com.android.wallpaper.R;
-import com.android.wallpaper.asset.Asset;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.picker.AppbarFragment;
-import com.android.wallpaper.widget.PreviewPager;
-
-import java.util.List;
-
-/**
- * Fragment that contains the main UI for selecting and applying a Clockface.
- */
-public class ClockFragment extends AppbarFragment {
-
- private static final String TAG = "ClockFragment";
-
- /**
- * Interface to be implemented by an Activity hosting a {@link ClockFragment}
- */
- public interface ClockFragmentHost {
- BaseClockManager getClockManager();
- }
-
- public static ClockFragment newInstance(CharSequence title) {
- ClockFragment fragment = new ClockFragment();
- fragment.setArguments(AppbarFragment.createArguments(title));
- return fragment;
- }
-
- private RecyclerView mOptionsContainer;
- private OptionSelectorController<Clockface> mOptionsController;
- private Clockface mSelectedOption;
- private BaseClockManager mClockManager;
- private PreviewPager mPreviewPager;
- private ContentLoadingProgressBar mLoading;
- private View mContent;
- private View mError;
- private ThemesUserEventLogger mEventLogger;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mClockManager = ((ClockFragmentHost) context).getClockManager();
- mEventLogger = (ThemesUserEventLogger)
- InjectorProvider.getInjector().getUserEventLogger(context);
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(
- R.layout.fragment_clock_picker, container, /* attachToRoot */ false);
- setUpToolbar(view);
- mContent = view.findViewById(R.id.content_section);
- mPreviewPager = view.findViewById(R.id.clock_preview_pager);
- mOptionsContainer = view.findViewById(R.id.options_container);
- mLoading = view.findViewById(R.id.loading_indicator);
- mError = view.findViewById(R.id.error_section);
- setUpOptions();
- view.findViewById(R.id.apply_button).setOnClickListener(v -> {
- mClockManager.apply(mSelectedOption, new Callback() {
- @Override
- public void onSuccess() {
- mOptionsController.setAppliedOption(mSelectedOption);
- Toast.makeText(getContext(), R.string.applied_clock_msg,
- Toast.LENGTH_SHORT).show();
- }
-
- @Override
- public void onError(@Nullable Throwable throwable) {
- if (throwable != null) {
- Log.e(TAG, "Error loading clockfaces", throwable);
- }
- //TODO(santie): handle
- }
- });
-
- });
- return view;
- }
-
- private void createAdapter() {
- mPreviewPager.setAdapter(new ClockPreviewAdapter(getActivity(), mSelectedOption));
- }
-
- private void setUpOptions() {
- hideError();
- mLoading.show();
- mClockManager.fetchOptions(new OptionsFetchedListener<Clockface>() {
- @Override
- public void onOptionsLoaded(List<Clockface> options) {
- mLoading.hide();
- mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
-
- mOptionsController.addListener(selected -> {
- mSelectedOption = (Clockface) selected;
- mEventLogger.logClockSelected(mSelectedOption);
- createAdapter();
- });
- mOptionsController.initOptions(mClockManager);
- for (Clockface option : options) {
- if (option.isActive(mClockManager)) {
- mSelectedOption = option;
- }
- }
- // For development only, as there should always be a grid set.
- if (mSelectedOption == null) {
- mSelectedOption = options.get(0);
- }
- createAdapter();
- }
- @Override
- public void onError(@Nullable Throwable throwable) {
- if (throwable != null) {
- Log.e(TAG, "Error loading clockfaces", throwable);
- }
- showError();
- }
- }, false);
- }
-
- private void hideError() {
- mContent.setVisibility(View.VISIBLE);
- mError.setVisibility(View.GONE);
- }
-
- private void showError() {
- mLoading.hide();
- mContent.setVisibility(View.GONE);
- mError.setVisibility(View.VISIBLE);
- }
-
- private static class ClockfacePreviewPage extends PreviewPage {
-
- private final Asset mPreviewAsset;
-
- public ClockfacePreviewPage(String title, Activity activity, Asset previewAsset) {
- super(title, activity);
- mPreviewAsset = previewAsset;
- }
-
- @Override
- public void bindPreviewContent() {
- ImageView previewImage = card.findViewById(R.id.clock_preview_image);
- Context context = previewImage.getContext();
- Resources res = previewImage.getResources();
- mPreviewAsset.loadDrawableWithTransition(context, previewImage,
- 100 /* transitionDurationMillis */,
- null /* drawableLoadedListener */,
- res.getColor(android.R.color.transparent, null) /* placeholderColor */);
- card.setContentDescription(card.getResources().getString(
- R.string.clock_preview_content_description, title));
- }
- }
-
- /**
- * Adapter class for mPreviewPager.
- * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe,
- * we don't want to just scroll)
- */
- private static class ClockPreviewAdapter extends BasePreviewAdapter<ClockfacePreviewPage> {
- ClockPreviewAdapter(Activity activity, Clockface clockface) {
- super(activity, R.layout.clock_preview_card);
- addPage(new ClockfacePreviewPage(
- clockface.getTitle(), activity , clockface.getPreviewAsset()));
- }
- }
-}
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
index f160a3d5..8f163b72 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -16,6 +16,7 @@
*/
package com.android.customization.picker.clock.data.repository
+import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import kotlinx.coroutines.flow.Flow
@@ -25,4 +26,8 @@ import kotlinx.coroutines.flow.Flow
*/
interface ClockPickerRepository {
val selectedClock: Flow<ClockMetadataModel?>
+
+ val selectedClockSize: Flow<ClockSize>
+
+ fun setClockSize(size: ClockSize)
}
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index c307ca62..1c505172 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -17,11 +17,14 @@
package com.android.customization.picker.clock.data.repository
import android.util.Log
+import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.shared.clocks.ClockRegistry
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.callbackFlow
/** Implementation of [ClockPickerRepository], using [ClockRegistry]. */
@@ -47,6 +50,14 @@ class ClockPickerRepositoryImpl(registry: ClockRegistry) : ClockPickerRepository
awaitClose { registry.unregisterClockChangeListener(listener) }
}
+ // TODO(b/262924055): Use the shared system UI component to query the clock size
+ private val _selectedClockSize = MutableStateFlow(ClockSize.DYNAMIC)
+ override val selectedClockSize: Flow<ClockSize> = _selectedClockSize.asStateFlow()
+
+ override fun setClockSize(size: ClockSize) {
+ _selectedClockSize.value = size
+ }
+
private fun ClockMetadata.toModel(): ClockMetadataModel {
return ClockMetadataModel(clockId = clockId, name = name)
}
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
index 63b3638b..a0bf14e3 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -18,6 +18,7 @@
package com.android.customization.picker.clock.domain.interactor
import com.android.customization.picker.clock.data.repository.ClockPickerRepository
+import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import kotlinx.coroutines.flow.Flow
@@ -26,6 +27,11 @@ import kotlinx.coroutines.flow.Flow
* clocks.
*/
class ClockPickerInteractor(private val repository: ClockPickerRepository) {
-
val selectedClock: Flow<ClockMetadataModel?> = repository.selectedClock
+
+ val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
+
+ fun setClockSize(size: ClockSize) {
+ repository.setClockSize(size)
+ }
}
diff --git a/src/com/android/customization/picker/clock/shared/ClockSize.kt b/src/com/android/customization/picker/clock/shared/ClockSize.kt
new file mode 100644
index 00000000..91c5cd41
--- /dev/null
+++ b/src/com/android/customization/picker/clock/shared/ClockSize.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.shared
+
+enum class ClockSize {
+ DYNAMIC,
+ LARGE,
+}
diff --git a/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt b/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt
new file mode 100644
index 00000000..746fdb30
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsTabViewModel
+import com.android.wallpaper.R
+
+/** Adapter for the tab recycler view on the clock settings screen. */
+class ClockSettingsTabAdapter : RecyclerView.Adapter<ClockSettingsTabAdapter.ViewHolder>() {
+
+ private val items = mutableListOf<ClockSettingsTabViewModel>()
+
+ fun setItems(items: List<ClockSettingsTabViewModel>) {
+ this.items.clear()
+ this.items.addAll(items)
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(parent.context)
+ .inflate(
+ R.layout.picker_fragment_tab,
+ parent,
+ false,
+ )
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = items[position]
+ holder.itemView.isSelected = item.isSelected
+ holder.textView.text = item.name
+ holder.textView.setOnClickListener(
+ if (item.onClicked != null) {
+ View.OnClickListener { item.onClicked.invoke() }
+ } else {
+ null
+ }
+ )
+ }
+
+ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val textView: TextView = itemView.requireViewById(R.id.text)
+ }
+}
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
new file mode 100644
index 00000000..9a94a69f
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.binder
+
+import android.view.View
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.customization.picker.clock.ui.adapter.ClockSettingsTabAdapter
+import com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
+import com.android.customization.picker.common.ui.view.ItemSpacing
+import com.android.wallpaper.R
+import kotlinx.coroutines.launch
+
+/** Bind between the clock settings screen and its view model. */
+object ClockSettingsBinder {
+ fun bind(
+ view: View,
+ viewModel: ClockSettingsViewModel,
+ lifecycleOwner: LifecycleOwner,
+ ) {
+ val tabView: RecyclerView = view.requireViewById(R.id.tabs)
+ val sizeOptions =
+ view.requireViewById<ClockSizeRadioButtonGroup>(R.id.clock_size_radio_button_group)
+
+ val tabAdapter = ClockSettingsTabAdapter()
+ tabView.adapter = tabAdapter
+ tabView.layoutManager = LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
+ tabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
+
+ sizeOptions.onRadioButtonClickListener =
+ object : ClockSizeRadioButtonGroup.OnRadioButtonClickListener {
+ override fun onClick(size: ClockSize) {
+ viewModel.setClockSize(size)
+ }
+ }
+
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch { viewModel.tabs.collect { tabAdapter.setItems(it) } }
+
+ launch {
+ viewModel.selectedTabPosition.collect { tab ->
+ when (tab) {
+ ClockSettingsViewModel.Tab.COLOR -> {
+ sizeOptions.isInvisible = true
+ }
+ ClockSettingsViewModel.Tab.SIZE -> {
+ sizeOptions.isVisible = true
+ }
+ }
+ }
+ }
+
+ launch {
+ viewModel.selectedClockSize.collect { size ->
+ when (size) {
+ ClockSize.DYNAMIC -> {
+ sizeOptions.radioButtonDynamic.isChecked = true
+ sizeOptions.radioButtonLarge.isChecked = false
+ }
+ ClockSize.LARGE -> {
+ sizeOptions.radioButtonDynamic.isChecked = false
+ sizeOptions.radioButtonLarge.isChecked = true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/clock/ClockCustomFragment.java b/src/com/android/customization/picker/clock/ui/fragment/ClockCustomFragment.java
index 56860fea..ea267ab4 100644
--- a/src/com/android/customization/picker/clock/ClockCustomFragment.java
+++ b/src/com/android/customization/picker/clock/ui/fragment/ClockCustomFragment.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.customization.picker.clock;
+package com.android.customization.picker.clock.ui.fragment;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
diff --git a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
new file mode 100644
index 00000000..c8d24346
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.get
+import androidx.lifecycle.lifecycleScope
+import com.android.customization.module.ThemePickerInjector
+import com.android.customization.picker.clock.ui.binder.ClockSettingsBinder
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
+import com.android.customization.picker.quickaffordance.ui.binder.KeyguardQuickAffordancePreviewBinder
+import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
+import com.android.wallpaper.R
+import com.android.wallpaper.module.InjectorProvider
+import com.android.wallpaper.picker.AppbarFragment
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class ClockSettingsFragment : AppbarFragment() {
+ companion object {
+ const val DESTINATION_ID = "clock_settings"
+
+ @JvmStatic
+ fun newInstance(): ClockSettingsFragment {
+ return ClockSettingsFragment()
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val view =
+ inflater.inflate(
+ R.layout.fragment_clock_settings,
+ container,
+ false,
+ )
+ setUpToolbar(view)
+ val injector = InjectorProvider.getInjector() as ThemePickerInjector
+
+ // TODO(b/262924055): Modify to render the lockscreen properly
+ val viewModel: KeyguardQuickAffordancePickerViewModel =
+ ViewModelProvider(
+ requireActivity(),
+ injector.getKeyguardQuickAffordancePickerViewModelFactory(requireContext()),
+ )
+ .get()
+ KeyguardQuickAffordancePreviewBinder.bind(
+ activity = requireActivity(),
+ previewView = view.requireViewById(R.id.preview),
+ viewModel = viewModel,
+ lifecycleOwner = this,
+ offsetToStart =
+ injector.getDisplayUtils(requireActivity()).isOnWallpaperDisplay(requireActivity())
+ )
+
+ lifecycleScope.launch {
+ val clockRegistry =
+ withContext(Dispatchers.IO) {
+ injector.getClockRegistryProvider(requireContext()).get()
+ }
+ ClockSettingsBinder.bind(
+ view,
+ ClockSettingsViewModel(
+ requireContext(),
+ injector.getClockPickerInteractor(requireContext(), clockRegistry)
+ ),
+ this@ClockSettingsFragment,
+ )
+ }
+
+ return view
+ }
+
+ override fun getDefaultTitle(): CharSequence {
+ return requireContext().getString(R.string.clock_settings_title)
+ }
+}
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..fcf89049
--- /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 radioButtonLarge: 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) }
+ radioButtonLarge = requireViewById(R.id.radio_button_large)
+ val buttonLarge = requireViewById<View>(R.id.button_container_large)
+ buttonLarge.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.LARGE) }
+ }
+}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModel.kt
new file mode 100644
index 00000000..7c30ca2b
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.viewmodel
+
+/** View model for the tabs on the clock settings screen. */
+class ClockSettingsTabViewModel(
+ /** User-visible name for the tab. */
+ val name: String,
+
+ /** Whether this is the currently-selected tab in the picker. */
+ val isSelected: Boolean,
+
+ /** Notifies that the tab has been clicked by the user. */
+ val onClicked: (() -> Unit)?,
+)
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
new file mode 100644
index 00000000..8c0925fa
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.viewmodel
+
+import android.content.Context
+import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.R
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+/** View model for the clock settings screen. */
+class ClockSettingsViewModel(val context: Context, val interactor: ClockPickerInteractor) {
+
+ enum class Tab {
+ COLOR,
+ SIZE,
+ }
+
+ val selectedClockSize: Flow<ClockSize> = interactor.selectedClockSize
+
+ fun setClockSize(size: ClockSize) {
+ interactor.setClockSize(size)
+ }
+
+ private val _selectedTabPosition = MutableStateFlow(Tab.COLOR)
+ val selectedTabPosition: StateFlow<Tab> = _selectedTabPosition.asStateFlow()
+ val tabs: Flow<List<ClockSettingsTabViewModel>> =
+ selectedTabPosition.map {
+ listOf(
+ ClockSettingsTabViewModel(
+ name = context.resources.getString(R.string.clock_color),
+ isSelected = it == Tab.COLOR,
+ onClicked =
+ if (it == Tab.COLOR) {
+ null
+ } else {
+ { _selectedTabPosition.tryEmit(Tab.COLOR) }
+ }
+ ),
+ ClockSettingsTabViewModel(
+ name = context.resources.getString(R.string.clock_size),
+ isSelected = it == Tab.SIZE,
+ onClicked =
+ if (it == Tab.SIZE) {
+ null
+ } else {
+ { _selectedTabPosition.tryEmit(Tab.SIZE) }
+ }
+ ),
+ )
+ }
+}
diff --git a/src/com/android/customization/picker/common/ui/view/ItemSpacing.kt b/src/com/android/customization/picker/common/ui/view/ItemSpacing.kt
new file mode 100644
index 00000000..cbf9e974
--- /dev/null
+++ b/src/com/android/customization/picker/common/ui/view/ItemSpacing.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.common.ui.view
+
+import android.graphics.Rect
+import androidx.core.view.ViewCompat
+import androidx.recyclerview.widget.RecyclerView
+
+/** Item spacing used by the RecyclerView. */
+class ItemSpacing(
+ private val itemSpacingDp: Int,
+) : RecyclerView.ItemDecoration() {
+ override fun getItemOffsets(outRect: Rect, itemPosition: Int, parent: RecyclerView) {
+ val addSpacingToStart = itemPosition > 0
+ val addSpacingToEnd = itemPosition < (parent.adapter?.itemCount ?: 0) - 1
+ val isRtl = parent.layoutManager?.layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL
+ val density = parent.context.resources.displayMetrics.density
+ val halfItemSpacingPx = itemSpacingDp.toPx(density) / 2
+ if (!isRtl) {
+ outRect.left = if (addSpacingToStart) halfItemSpacingPx else 0
+ outRect.right = if (addSpacingToEnd) halfItemSpacingPx else 0
+ } else {
+ outRect.left = if (addSpacingToEnd) halfItemSpacingPx else 0
+ outRect.right = if (addSpacingToStart) halfItemSpacingPx else 0
+ }
+ }
+
+ private fun Int.toPx(density: Float): Int {
+ return (this * density).toInt()
+ }
+
+ companion object {
+ const val TAB_ITEM_SPACING_DP = 12
+ const val AFFORDANCE_ITEM_SPACING_DP = 8
+ }
+}
diff --git a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
index 30372fe6..efa090bb 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
@@ -19,15 +19,14 @@ package com.android.customization.picker.quickaffordance.ui.binder
import android.app.Dialog
import android.content.Context
-import android.graphics.Rect
import android.view.View
-import androidx.core.view.ViewCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.common.ui.view.ItemSpacing
import com.android.customization.picker.quickaffordance.ui.adapter.AffordancesAdapter
import com.android.customization.picker.quickaffordance.ui.adapter.SlotTabAdapter
import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
@@ -54,12 +53,12 @@ object KeyguardQuickAffordancePickerBinder {
slotTabView.adapter = slotTabAdapter
slotTabView.layoutManager =
LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
- slotTabView.addItemDecoration(ItemSpacing(SLOT_TAB_ITEM_SPACING_DP))
+ slotTabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
val affordancesAdapter = AffordancesAdapter()
affordancesView.adapter = affordancesAdapter
affordancesView.layoutManager =
LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
- affordancesView.addItemDecoration(ItemSpacing(AFFORDANCE_ITEM_SPACING_DP))
+ affordancesView.addItemDecoration(ItemSpacing(ItemSpacing.AFFORDANCE_ITEM_SPACING_DP))
var dialog: Dialog? = null
@@ -117,30 +116,4 @@ object KeyguardQuickAffordancePickerBinder {
onDismissed = onDismissed,
)
}
-
- private class ItemSpacing(
- private val itemSpacingDp: Int,
- ) : RecyclerView.ItemDecoration() {
- override fun getItemOffsets(outRect: Rect, itemPosition: Int, parent: RecyclerView) {
- val addSpacingToStart = itemPosition > 0
- val addSpacingToEnd = itemPosition < (parent.adapter?.itemCount ?: 0) - 1
- val isRtl = parent.layoutManager?.layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL
- val density = parent.context.resources.displayMetrics.density
- val halfItemSpacingPx = itemSpacingDp.toPx(density) / 2
- if (!isRtl) {
- outRect.left = if (addSpacingToStart) halfItemSpacingPx else 0
- outRect.right = if (addSpacingToEnd) halfItemSpacingPx else 0
- } else {
- outRect.left = if (addSpacingToEnd) halfItemSpacingPx else 0
- outRect.right = if (addSpacingToStart) halfItemSpacingPx else 0
- }
- }
-
- private fun Int.toPx(density: Float): Int {
- return (this * density).toInt()
- }
- }
-
- private const val SLOT_TAB_ITEM_SPACING_DP = 12
- private const val AFFORDANCE_ITEM_SPACING_DP = 8
}
diff --git a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index ea97c9a9..0ff0cab9 100644
--- a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -15,11 +15,20 @@
*/
package com.android.customization.picker.clock.data.repository
+import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
class FakeClockPickerRepository : ClockPickerRepository {
override val selectedClock: Flow<ClockMetadataModel?> = MutableStateFlow(null)
+
+ private val _selectedClockSize = MutableStateFlow(ClockSize.LARGE)
+ override val selectedClockSize: Flow<ClockSize> = _selectedClockSize.asStateFlow()
+
+ override fun setClockSize(size: ClockSize) {
+ _selectedClockSize.value = size
+ }
}
diff --git a/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
new file mode 100644
index 00000000..a6ad6de2
--- /dev/null
+++ b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
@@ -0,0 +1,48 @@
+package com.android.customization.picker.clock.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ClockPickerInteractorTest {
+
+ private lateinit var underTest: ClockPickerInteractor
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ Dispatchers.setMain(testDispatcher)
+ underTest = ClockPickerInteractor(FakeClockPickerRepository())
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun setClockSize() = runTest {
+ val observedClockSize = collectLastValue(underTest.selectedClockSize)
+ underTest.setClockSize(ClockSize.DYNAMIC)
+ Truth.assertThat(observedClockSize()).isEqualTo(ClockSize.DYNAMIC)
+
+ underTest.setClockSize(ClockSize.LARGE)
+ Truth.assertThat(observedClockSize()).isEqualTo(ClockSize.LARGE)
+ }
+}
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
new file mode 100644
index 00000000..63bdd7b8
--- /dev/null
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
@@ -0,0 +1,68 @@
+package com.android.customization.picker.clock.ui.viewmodel
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
+import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ClockSettingsViewModelTest {
+
+ private lateinit var underTest: ClockSettingsViewModel
+
+ private lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ Dispatchers.setMain(testDispatcher)
+ context = InstrumentationRegistry.getInstrumentation().targetContext
+ underTest =
+ ClockSettingsViewModel(context, ClockPickerInteractor(FakeClockPickerRepository()))
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun setClockSize() = runTest {
+ val observedClockSize = collectLastValue(underTest.selectedClockSize)
+ underTest.setClockSize(ClockSize.DYNAMIC)
+ assertThat(observedClockSize()).isEqualTo(ClockSize.DYNAMIC)
+
+ underTest.setClockSize(ClockSize.LARGE)
+ assertThat(observedClockSize()).isEqualTo(ClockSize.LARGE)
+ }
+
+ @Test
+ fun `Click on a picker tab`() = runTest {
+ val tabs = collectLastValue(underTest.tabs)
+ assertThat(tabs()?.get(0)?.name).isEqualTo("Color")
+ assertThat(tabs()?.get(0)?.isSelected).isTrue()
+ assertThat(tabs()?.get(1)?.name).isEqualTo("Size")
+ assertThat(tabs()?.get(1)?.isSelected).isFalse()
+
+ tabs()?.get(1)?.onClicked?.invoke()
+ assertThat(tabs()?.get(0)?.isSelected).isFalse()
+ assertThat(tabs()?.get(1)?.isSelected).isTrue()
+ }
+}