Merge "[4/n] Add Apply Button to grid preview page" into udc-qpr-dev am: 8e191fd9a1 am: 210d17c227
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/ThemePicker/+/24435476
Change-Id: I43715e218a5e7c05136cecb8589be0ea050ebb86
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/res/layout/fragment_grid.xml b/res/layout/fragment_grid.xml
index 4f0aaef..8c97d45 100644
--- a/res/layout/fragment_grid.xml
+++ b/res/layout/fragment_grid.xml
@@ -88,6 +88,24 @@
</FrameLayout>
+ <Button
+ android:id="@+id/apply_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:visibility="gone"
+ android:enabled="false"
+ style="@style/ActionPrimaryButton"
+ android:text="@string/apply_btn" />
+
+ <TextView
+ android:id="@+id/apply_button_note"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:textSize="@dimen/grid_apply_button_note_text_size"
+ android:visibility="gone"
+ android:text="@string/apply_grid_btn_note" />
</LinearLayout>
</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 47171e5..aa6c477 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -61,6 +61,7 @@
<!-- Dimensions for the grid options -->
<dimen name="grid_options_container_bottom_margin">@dimen/bottom_actions_height</dimen>
<dimen name="grid_options_container_horizontal_margin">24dp</dimen>
+ <dimen name="grid_apply_button_note_text_size">11sp</dimen>
<dimen name="card_title_text_size">16sp</dimen>
<dimen name="card_header_icon_size">32dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eb68dce..9b55965 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -178,6 +178,15 @@
<!--Title for a grid option, describing the number of columns and rows, eg: 4x4 [CHAR LIMIT=10] -->
<string name="grid_title_pattern"><xliff:g name="num_cols" example="1">%1$d</xliff:g>x<xliff:g name="num_rows" example="1">%2$d</xliff:g></string>
+ <!-- Label of what will happen when user tap on apply button to change grid. [CHAR LIMIT=50] -->
+ <string name="apply_grid_btn_note">Changing grid size will reload workspace and may take a few seconds.</string>
+
+ <!-- Toast of reloading workspace with new grid. -->
+ <string name="toast_of_changing_grid">Reloading workspace with %1$s grid</string>
+
+ <!-- Toast of failure to reload workspace with new grid. -->
+ <string name="toast_of_failure_to_change_grid">Failed to reload workspace with %1$s grid</string>
+
<!-- Message shown when a theme has been applied successfully in the system [CHAR LIMIT=NONE] -->
<string name="applied_theme_msg">Style set successfully</string>
diff --git a/src/com/android/customization/model/grid/GridOption.java b/src/com/android/customization/model/grid/GridOption.java
index 17ba115..a5307c9 100644
--- a/src/com/android/customization/model/grid/GridOption.java
+++ b/src/com/android/customization/model/grid/GridOption.java
@@ -48,9 +48,9 @@
}
};
- private final String mTitle;
private final String mIconShapePath;
private final GridTileDrawable mTileDrawable;
+ public final String title;
public final String name;
public final int rows;
public final int cols;
@@ -60,7 +60,7 @@
public GridOption(String title, String name, boolean isCurrent, int rows, int cols,
Uri previewImageUri, int previewPagesCount, String iconShapePath) {
- mTitle = title;
+ this.title = title;
mIsCurrent = isCurrent;
mIconShapePath = iconShapePath;
mTileDrawable = new GridTileDrawable(rows, cols, mIconShapePath);
@@ -76,7 +76,7 @@
}
protected GridOption(Parcel in) {
- mTitle = in.readString();
+ title = in.readString();
mIsCurrent = in.readByte() != 0;
mIconShapePath = in.readString();
name = in.readString();
@@ -89,7 +89,7 @@
@Override
public String getTitle() {
- return mTitle;
+ return title;
}
@Override
@@ -143,7 +143,7 @@
@Override
public void writeToParcel(Parcel parcel, int i) {
- parcel.writeString(mTitle);
+ parcel.writeString(title);
parcel.writeByte((byte) (mIsCurrent ? 1 : 0));
parcel.writeString(mIconShapePath);
parcel.writeString(name);
@@ -158,7 +158,7 @@
return String.format(
"GridOption{mTitle='%s', mIsCurrent=%s, mTileDrawable=%s, name='%s', rows=%d, "
+ "cols=%d, previewImageUri=%s, previewPagesCount=%d}\n",
- mTitle, mIsCurrent, mTileDrawable, name, rows, cols, previewImageUri,
+ title, mIsCurrent, mTileDrawable, name, rows, cols, previewImageUri,
previewPagesCount);
}
}
diff --git a/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
index 8f1860e..e71cca9 100644
--- a/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
+++ b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
@@ -33,6 +33,7 @@
import com.android.customization.model.ResourceConstants;
import com.android.wallpaper.R;
+import com.android.wallpaper.config.BaseFlags;
import com.android.wallpaper.util.PreviewUtils;
import java.util.ArrayList;
@@ -57,12 +58,14 @@
private final Context mContext;
private final PreviewUtils mPreviewUtils;
+ private final boolean mIsGridApplyButtonEnabled;
private List<GridOption> mOptions;
private OptionChangeLiveData mLiveData;
public LauncherGridOptionsProvider(Context context, String authorityMetadataKey) {
mPreviewUtils = new PreviewUtils(context, authorityMetadataKey);
mContext = context;
+ mIsGridApplyButtonEnabled = BaseFlags.get().isGridApplyButtonEnabled(context);
}
boolean areGridsAvailable() {
@@ -124,6 +127,7 @@
int applyGrid(String name) {
ContentValues values = new ContentValues();
values.put("name", name);
+ values.put("enable_apply_button", mIsGridApplyButtonEnabled);
return mContext.getContentResolver().update(mPreviewUtils.getUri(DEFAULT_GRID), values,
null, null);
}
@@ -157,6 +161,14 @@
mContentObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
+ // If grid apply button is enabled, user has previewed the grid before applying
+ // the grid change. Thus there is no need to preview again (which will cause a
+ // blank preview as launcher's is loader thread is busy reloading workspace)
+ // after applying grid change. Thus we should ignore ContentObserver#onChange
+ // from launcher
+ if (BaseFlags.get().isGridApplyButtonEnabled(context.getApplicationContext())) {
+ return;
+ }
postValue(new Object());
}
};
diff --git a/src/com/android/customization/model/grid/data/repository/GridRepository.kt b/src/com/android/customization/model/grid/data/repository/GridRepository.kt
index e65c18e..4379dad 100644
--- a/src/com/android/customization/model/grid/data/repository/GridRepository.kt
+++ b/src/com/android/customization/model/grid/data/repository/GridRepository.kt
@@ -19,6 +19,7 @@
import androidx.lifecycle.asFlow
import com.android.customization.model.CustomizationManager
+import com.android.customization.model.CustomizationManager.Callback
import com.android.customization.model.grid.GridOption
import com.android.customization.model.grid.GridOptionsManager
import com.android.customization.model.grid.shared.model.GridOptionItemModel
@@ -39,6 +40,9 @@
fun getOptionChanges(): Flow<Unit>
suspend fun getOptions(): GridOptionItemsModel
fun getSelectedOption(): GridOption?
+ fun applySelectedOption(callback: Callback)
+ fun clearSelectedOption()
+ fun isSelectedOptionApplied(): Boolean
}
class GridRepositoryImpl(
@@ -57,6 +61,8 @@
private val selectedOption = MutableStateFlow<GridOption?>(null)
+ private var appliedOption: GridOption? = null
+
override fun getSelectedOption() = selectedOption.value
override suspend fun getOptions(): GridOptionItemsModel {
@@ -71,6 +77,9 @@
if (!isGridApplyButtonEnabled || selectedOption.value == null) {
selectedOption.value = optionsOrEmpty.find { it.isActive(manager) }
}
+ if (isGridApplyButtonEnabled && appliedOption == null) {
+ appliedOption = selectedOption.value
+ }
continuation.resume(
GridOptionItemsModel.Loaded(
optionsOrEmpty.map { option -> toModel(option) }
@@ -137,6 +146,35 @@
}
}
+ override fun applySelectedOption(callback: Callback) {
+ val option = getSelectedOption()
+ manager.apply(
+ option,
+ if (isGridApplyButtonEnabled) {
+ object : Callback {
+ override fun onSuccess() {
+ callback.onSuccess()
+ appliedOption = option
+ }
+
+ override fun onError(throwable: Throwable?) {
+ callback.onError(throwable)
+ }
+ }
+ } else callback
+ )
+ }
+
+ override fun clearSelectedOption() {
+ if (!isGridApplyButtonEnabled) {
+ return
+ }
+ selectedOption.value?.setIsCurrent(false)
+ selectedOption.value = null
+ }
+
+ override fun isSelectedOptionApplied() = selectedOption.value?.name == appliedOption?.name
+
private fun GridOption?.key(): String? {
return if (this != null) "${cols}x${rows}" else null
}
diff --git a/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt b/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt
index 6cb00ff..7abd605 100644
--- a/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt
+++ b/src/com/android/customization/model/grid/domain/interactor/GridInteractor.kt
@@ -17,6 +17,8 @@
package com.android.customization.model.grid.domain.interactor
+import com.android.customization.model.CustomizationManager
+import com.android.customization.model.grid.GridOption
import com.android.customization.model.grid.data.repository.GridRepository
import com.android.customization.model.grid.shared.model.GridOptionItemModel
import com.android.customization.model.grid.shared.model.GridOptionItemsModel
@@ -73,6 +75,16 @@
}
}
+ fun getSelectOptionNonSuspend(): GridOption? = repository.getSelectedOption()
+
+ fun clearSelectedOption() = repository.clearSelectedOption()
+
+ fun isSelectedOptionApplied() = repository.isSelectedOptionApplied()
+
+ fun applySelectedOption(callback: CustomizationManager.Callback) {
+ repository.applySelectedOption(callback)
+ }
+
private suspend fun reload(): GridOptionItemsModel {
val model = repository.getOptions()
return if (model is GridOptionItemsModel.Loaded) {
@@ -95,6 +107,4 @@
model
}
}
-
- fun getSelectedOptionName(): String? = repository.getSelectedOption()?.name
}
diff --git a/src/com/android/customization/model/grid/ui/binder/GridScreenBinder.kt b/src/com/android/customization/model/grid/ui/binder/GridScreenBinder.kt
index 1c3561d..56fe425 100644
--- a/src/com/android/customization/model/grid/ui/binder/GridScreenBinder.kt
+++ b/src/com/android/customization/model/grid/ui/binder/GridScreenBinder.kt
@@ -18,6 +18,7 @@
package com.android.customization.model.grid.ui.binder
import android.view.View
+import android.widget.Button
import android.widget.ImageView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -41,6 +42,8 @@
lifecycleOwner: LifecycleOwner,
backgroundDispatcher: CoroutineDispatcher,
onOptionsChanged: () -> Unit,
+ isGridApplyButtonEnabled: Boolean,
+ onOptionApplied: () -> Unit,
) {
val optionView: RecyclerView = view.requireViewById(R.id.options)
optionView.layoutManager =
@@ -67,6 +70,13 @@
)
optionView.adapter = adapter
+ if (isGridApplyButtonEnabled) {
+ val applyButton: Button = view.requireViewById(R.id.apply_button)
+ applyButton.visibility = View.VISIBLE
+ view.requireViewById<View>(R.id.apply_button_note).visibility = View.VISIBLE
+ applyButton.setOnClickListener { onOptionApplied() }
+ }
+
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/src/com/android/customization/model/grid/ui/fragment/GridFragment2.kt b/src/com/android/customization/model/grid/ui/fragment/GridFragment2.kt
index 14d1344..9e99efe 100644
--- a/src/com/android/customization/model/grid/ui/fragment/GridFragment2.kt
+++ b/src/com/android/customization/model/grid/ui/fragment/GridFragment2.kt
@@ -18,19 +18,24 @@
package com.android.customization.model.grid.ui.fragment
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Button
+import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.transition.Transition
import androidx.transition.doOnStart
+import com.android.customization.model.CustomizationManager.Callback
import com.android.customization.model.grid.domain.interactor.GridInteractor
import com.android.customization.model.grid.ui.binder.GridScreenBinder
import com.android.customization.model.grid.ui.viewmodel.GridScreenViewModel
import com.android.customization.module.ThemePickerInjector
import com.android.wallpaper.R
+import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.module.CurrentWallpaperInfoFactory
import com.android.wallpaper.module.CustomizationSections
import com.android.wallpaper.module.InjectorProvider
@@ -43,9 +48,13 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
+private val TAG = GridFragment2::class.java.simpleName
+
@OptIn(ExperimentalCoroutinesApi::class)
class GridFragment2 : AppbarFragment() {
+ private lateinit var gridInteractor: GridInteractor
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -59,6 +68,8 @@
)
setUpToolbar(view)
+ val isGridApplyButtonEnabled = BaseFlags.get().isGridApplyButtonEnabled(requireContext())
+
val injector = InjectorProvider.getInjector() as ThemePickerInjector
val wallpaperInfoFactory = injector.getCurrentWallpaperInfoFactory(requireContext())
@@ -71,6 +82,7 @@
)
val viewModelFactory = injector.getGridScreenViewModelFactory(requireContext())
+ gridInteractor = injector.getGridInteractor(requireContext())
GridScreenBinder.bind(
view = view,
viewModel =
@@ -87,8 +99,42 @@
view,
wallpaperInfoFactory,
injector.getWallpaperInteractor(requireContext()),
- injector.getGridInteractor(requireContext())
+ gridInteractor,
)
+ if (isGridApplyButtonEnabled) {
+ val applyButton: Button = view.requireViewById(R.id.apply_button)
+ applyButton.isEnabled = !gridInteractor.isSelectedOptionApplied()
+ }
+ },
+ isGridApplyButtonEnabled = isGridApplyButtonEnabled,
+ onOptionApplied = {
+ gridInteractor.applySelectedOption(
+ object : Callback {
+ override fun onSuccess() {
+ Toast.makeText(
+ context,
+ getString(
+ R.string.toast_of_changing_grid,
+ gridInteractor.getSelectOptionNonSuspend()?.title
+ ),
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ val applyButton: Button = view.requireViewById(R.id.apply_button)
+ applyButton.isEnabled = false
+ }
+
+ override fun onError(throwable: Throwable?) {
+ val errorMsg =
+ getString(
+ R.string.toast_of_failure_to_change_grid,
+ gridInteractor.getSelectOptionNonSuspend()?.title
+ )
+ Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show()
+ Log.e(TAG, errorMsg, throwable)
+ }
+ }
+ )
}
)
@@ -133,7 +179,7 @@
),
initialExtrasProvider = {
val bundle = Bundle()
- bundle.putString("name", gridInteractor.getSelectedOptionName())
+ bundle.putString("name", gridInteractor.getSelectOptionNonSuspend()?.name)
bundle
},
wallpaperInfoProvider = {
@@ -154,4 +200,11 @@
onWallpaperPreviewDirty = { activity?.recreate() },
)
}
+
+ override fun onBackPressed(): Boolean {
+ if (BaseFlags.get().isGridApplyButtonEnabled(requireContext())) {
+ gridInteractor.clearSelectedOption()
+ }
+ return super.onBackPressed()
+ }
}
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index a4b1d27..1486884 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -566,9 +566,7 @@
.also { gridScreenViewModelFactory = it }
}
- fun getGridInteractor(
- context: Context,
- ): GridInteractor {
+ fun getGridInteractor(context: Context): GridInteractor {
val appContext = context.applicationContext
return gridInteractor
?: GridInteractor(
diff --git a/tests/robotests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt b/tests/robotests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt
index 0c80e28..317ad3a 100644
--- a/tests/robotests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt
+++ b/tests/robotests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt
@@ -17,6 +17,7 @@
package com.android.customization.model.grid.data.repository
+import com.android.customization.model.CustomizationManager
import com.android.customization.model.grid.GridOption
import com.android.customization.model.grid.shared.model.GridOptionItemModel
import com.android.customization.model.grid.shared.model.GridOptionItemsModel
@@ -54,6 +55,12 @@
override fun getSelectedOption(): GridOption? = null
+ override fun applySelectedOption(callback: CustomizationManager.Callback) {}
+
+ override fun clearSelectedOption() {}
+
+ override fun isSelectedOptionApplied() = false
+
fun setOptions(
count: Int,
selectedIndex: Int = 0,