diff options
author | George Lin <giolin@google.com> | 2023-01-26 06:00:28 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-01-26 06:00:28 +0000 |
commit | 1ff19de353df4f4dd4d6c18364fc0128f69bd9a8 (patch) | |
tree | b1cbcd3de2ed7cab4b55f72d0c6f2dc6c5571f3f | |
parent | 5dad51230d9c66d283dab021a6a566920e5945d4 (diff) | |
parent | 906d65e2dd699d95eb13fd3413ec66e8a3f6fe5d (diff) | |
download | ThemePicker-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>
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() + } +} |