Commit ca80f582 authored by Jenna Himawan's avatar Jenna Himawan Committed by Commit Bot

Refactoring language list to use drag-reordering classes

Change-Id: I281bb273a2052743beae656fbb357e46613f8e52
Bug: 993066
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1716959
Commit-Queue: Jenna Himawan <jhimawan@google.com>
Reviewed-by: default avataranthonyvd <anthonyvd@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#686225}
parent 1cf00362
......@@ -928,6 +928,15 @@ public class PrefServiceBridge {
nativeMoveAcceptLanguage(languageCode, offset);
}
/**
* Given an array of language codes, sets the order of the user's accepted languages to match.
*
* @param codes The new order for the user's accepted languages.
*/
public void setLanguageOrder(String[] codes) {
nativeSetLanguageOrder(codes);
}
/**
* @param languageCode A valid language code to check.
* @return Whether the given language is blocked by the user.
......@@ -1171,6 +1180,7 @@ public class PrefServiceBridge {
private native void nativeGetUserAcceptLanguages(List<String> list);
private native void nativeUpdateUserAcceptLanguages(String language, boolean add);
private native void nativeMoveAcceptLanguage(String language, int offset);
private native void nativeSetLanguageOrder(String[] codes);
private native boolean nativeIsBlockedLanguage(String language);
private native void nativeSetLanguageBlockedState(String language, boolean blocked);
private native String nativeGetDownloadDefaultDirectory();
......
......@@ -12,6 +12,7 @@ import android.support.v4.app.Fragment;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.view.LayoutInflater;
......@@ -51,9 +52,10 @@ public class AddLanguageFragment extends Fragment {
}
@Override
public void onBindViewHolder(LanguageRowViewHolder holder, int position) {
public void onBindViewHolder(ViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
holder.setItemClickListener(getItemByPosition(position), mItemClickListener);
((LanguageRowViewHolder) holder)
.setItemClickListener(getItemByPosition(position), mItemClickListener);
}
/**
......@@ -62,7 +64,7 @@ public class AddLanguageFragment extends Fragment {
*/
private void search(String query) {
if (TextUtils.isEmpty(query)) {
reload(mFullLanguageList);
setDisplayedLanguages(mFullLanguageList);
return;
}
......@@ -76,7 +78,7 @@ public class AddLanguageFragment extends Fragment {
results.add(item);
}
}
reload(results);
setDisplayedLanguages(results);
}
}
......@@ -124,7 +126,7 @@ public class AddLanguageFragment extends Fragment {
mAdapter = new LanguageSearchListAdapter(activity);
mRecyclerView.setAdapter(mAdapter);
mAdapter.reload(mFullLanguageList);
mAdapter.setDisplayedLanguages(mFullLanguageList);
mRecyclerView.getViewTreeObserver().addOnScrollChangedListener(
PreferenceUtils.getShowShadowOnScrollListener(
mRecyclerView, view.findViewById(R.id.shadow)));
......@@ -141,7 +143,7 @@ public class AddLanguageFragment extends Fragment {
mSearchView.setOnCloseListener(() -> {
mSearch = "";
mAdapter.reload(mFullLanguageList);
mAdapter.setDisplayedLanguages(mFullLanguageList);
return false;
});
......
......@@ -5,40 +5,33 @@
package org.chromium.chrome.browser.preferences.languages;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.widget.ImageView;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.widget.ListMenuButton;
import org.chromium.chrome.browser.widget.dragreorder.DragReorderableListAdapter;
import org.chromium.chrome.browser.widget.dragreorder.DragStateDelegate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* BaseAdapter for {@link RecyclerView}. It manages languages to list there.
*/
public class LanguageListBaseAdapter
extends RecyclerView.Adapter<LanguageListBaseAdapter.LanguageRowViewHolder> {
private static final int ANIMATION_DELAY_MS = 100;
public class LanguageListBaseAdapter extends DragReorderableListAdapter<LanguageItem> {
/**
* Listener used to respond to click event on a language item.
*/
......@@ -59,18 +52,18 @@ public class LanguageListBaseAdapter
LanguageRowViewHolder(View view) {
super(view);
mTitle = (TextView) view.findViewById(R.id.title);
mDescription = (TextView) view.findViewById(R.id.description);
mTitle = view.findViewById(R.id.title);
mDescription = view.findViewById(R.id.description);
mStartIcon = view.findViewById(R.id.icon_view);
mMoreButton = (ListMenuButton) view.findViewById(R.id.more);
mMoreButton = view.findViewById(R.id.more);
}
/**
* Update the current {@link LanguageRowViewHolder} with basic language info.
* @param item A {@link LanguageItem} with the language details.
*/
private void updateLanguageInfo(LanguageItem item) {
protected void updateLanguageInfo(LanguageItem item) {
mTitle.setText(item.getDisplayName());
// Avoid duplicate display names.
......@@ -83,7 +76,7 @@ public class LanguageListBaseAdapter
mMoreButton.setContentDescriptionContext(item.getDisplayName());
// The default visibility for the views below is GONE.
// The more button will become visible if setMenuButtonDelegate is called.
mStartIcon.setVisibility(View.GONE);
mMoreButton.setVisibility(View.GONE);
}
......@@ -119,66 +112,61 @@ public class LanguageListBaseAdapter
}
}
private final int mDraggedBackgroundColor;
private final float mDraggedElevation;
private boolean mDragEnabled;
private ItemTouchHelper mItemTouchHelper;
private List<LanguageItem> mLanguageList;
protected Context mContext;
LanguageListBaseAdapter(Context context) {
mContext = context;
mLanguageList = new ArrayList<>();
Resources resource = context.getResources();
// Set the alpha to 90% when dragging which is 230/255
mDraggedBackgroundColor = ColorUtils.setAlphaComponent(
ApiCompatibilityUtils.getColor(resource, R.color.default_bg_color_elev_1),
resource.getInteger(R.integer.list_item_dragged_alpha));
mDraggedElevation = resource.getDimension(R.dimen.list_item_dragged_elevation);
}
@Override
public LanguageRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View row = LayoutInflater.from(parent.getContext())
.inflate(R.layout.accept_languages_item, parent, false);
return new LanguageRowViewHolder(row);
}
/**
* Keeps track of whether drag is enabled / active for language preference lists.
*/
private class LanguageDragStateDelegate implements DragStateDelegate {
private AccessibilityManager mA11yManager;
private AccessibilityStateChangeListener mA11yListener;
private boolean mA11yEnabled;
public LanguageDragStateDelegate() {
mA11yManager =
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mA11yEnabled = mA11yManager.isEnabled();
mA11yListener = enabled -> {
mA11yEnabled = enabled;
notifyDataSetChanged();
};
mA11yManager.addAccessibilityStateChangeListener(mA11yListener);
}
@Override
public void onBindViewHolder(LanguageRowViewHolder holder, int position) {
holder.updateLanguageInfo(mLanguageList.get(position));
}
// DragStateDelegate implementation
@Override
public boolean getDragEnabled() {
return !mA11yEnabled;
}
@Override
public int getItemCount() {
return mLanguageList.size();
}
@Override
public boolean getDragActive() {
return getDragEnabled();
}
LanguageItem getItemByPosition(int position) {
return mLanguageList.get(position);
@Override
public void setA11yStateForTesting(boolean a11yEnabled) {
mA11yManager.removeAccessibilityStateChangeListener(mA11yListener);
mA11yListener = null;
mA11yManager = null;
mA11yEnabled = a11yEnabled;
}
}
void reload(List<LanguageItem> languageList) {
mLanguageList.clear();
mLanguageList.addAll(languageList);
notifyDataSetChanged();
LanguageListBaseAdapter(Context context) {
super(context);
setDragStateDelegate(new LanguageDragStateDelegate());
}
/**
* Show a drag indicator at the start of the row if applicable.
*
* @param holder The LanguageRowViewHolder of the row.
* @param indicatorResId The identifier of the drawable resource for the indicator.
*/
void showDragIndicatorInRow(LanguageRowViewHolder holder, @DrawableRes int indicatorResId) {
void showDragIndicatorInRow(LanguageRowViewHolder holder) {
// Quit if it's not applicable.
if (getItemCount() <= 1 || !mDragEnabled) return;
if (getItemCount() <= 1 || !mDragStateDelegate.getDragEnabled()) return;
assert mItemTouchHelper != null;
holder.setStartIcon(indicatorResId);
holder.setStartIcon(R.drawable.ic_drag_handle_grey600_24dp);
holder.mStartIcon.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mItemTouchHelper.startDrag(holder);
......@@ -187,112 +175,46 @@ public class LanguageListBaseAdapter
});
}
/**
* Enables drag & drop interaction on the given RecyclerView.
* @param recyclerView The RecyclerView you want to drag from.
*/
void enableDrag(RecyclerView recyclerView) {
mDragEnabled = true;
if (mItemTouchHelper == null) {
ItemTouchHelper.Callback touchHelperCallBack = new ItemTouchHelper.Callback() {
// The dragged language info during a single drag operation.
// The first is its start postion when it's dragged, the second is its language
// code.
@Nullable
private Pair<Integer, String> mDraggedLanguage;
@Override
public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
return makeMovementFlags(
ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0 /* swipe flags */);
}
@Override
public boolean onMove(
RecyclerView recyclerView, ViewHolder current, ViewHolder target) {
int from = current.getAdapterPosition();
int to = target.getAdapterPosition();
if (from == to) return false;
Collections.swap(mLanguageList, from, to);
notifyItemMoved(from, to);
return true;
}
@Override
public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
// mDraggedLanguage should be cleaned up before.
assert mDraggedLanguage == null;
int start = viewHolder.getAdapterPosition();
mDraggedLanguage = Pair.create(start, getItemByPosition(start).getCode());
updateVisualState(true, viewHolder.itemView);
}
}
@Override
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
// Commit the postion change for the dragged language when it's dropped.
if (mDraggedLanguage != null) {
LanguagesManager.getInstance().moveLanguagePosition(mDraggedLanguage.second,
viewHolder.getAdapterPosition() - mDraggedLanguage.first, false);
mDraggedLanguage = null;
}
updateVisualState(false, viewHolder.itemView);
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
// no-op
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View row = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.accept_languages_item, viewGroup, false);
return new LanguageRowViewHolder(row);
}
private void updateVisualState(boolean dragged, View view) {
ViewCompat.animate(view)
.translationZ(dragged ? mDraggedElevation : 0)
.withEndAction(()
-> view.setBackgroundColor(dragged
? mDraggedBackgroundColor
: Color.TRANSPARENT))
.setDuration(ANIMATION_DELAY_MS)
.start();
}
};
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
((LanguageRowViewHolder) viewHolder).updateLanguageInfo(mElements.get(i));
}
mItemTouchHelper = new ItemTouchHelper(touchHelperCallBack);
@Override
protected void setOrder(List<LanguageItem> order) {
String[] codes = new String[order.size()];
for (int i = 0; i < order.size(); i++) {
codes[i] = order.get(i).getCode();
}
mItemTouchHelper.attachToRecyclerView(recyclerView);
LanguagesManager.getInstance().setOrder(codes, false);
notifyDataSetChanged();
}
/**
* Disables drag & drop interaction.
* @param recyclerView The RecyclerView you want to drag from.
* Sets the displayed languages (not the order of the user's preferred languages;
* see setOrder above).
*
* @param languages The language items to show.
*/
void disableDrag() {
mDragEnabled = false;
if (mItemTouchHelper != null) mItemTouchHelper.attachToRecyclerView(null);
void setDisplayedLanguages(List<LanguageItem> languages) {
mElements = new ArrayList<>(languages);
notifyDataSetChanged();
}
/**
* Returns whether the drag & drop is enabled.
*/
boolean isDragEnabled() {
return mDragEnabled;
@Override
protected boolean isActivelyDraggable(ViewHolder viewHolder) {
return isPassivelyDraggable(viewHolder);
}
@Override
protected boolean isPassivelyDraggable(ViewHolder viewHolder) {
return viewHolder instanceof LanguageRowViewHolder;
}
}
......@@ -10,13 +10,12 @@ import android.support.v7.preference.PreferenceViewHolder;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.chrome.browser.widget.ListMenuButton;
import org.chromium.chrome.browser.widget.ListMenuButton.Item;
import org.chromium.chrome.browser.widget.TintedDrawable;
......@@ -35,14 +34,13 @@ public class LanguageListPreference extends Preference {
}
@Override
public void onBindViewHolder(
LanguageListBaseAdapter.LanguageRowViewHolder holder, int position) {
public void onBindViewHolder(ViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
showDragIndicatorInRow(holder, R.drawable.ic_drag_handle_grey600_24dp);
final LanguageItem info = getItemByPosition(position);
holder.setMenuButtonDelegate(new ListMenuButton.Delegate() {
showDragIndicatorInRow((LanguageRowViewHolder) holder);
((LanguageRowViewHolder) holder).setMenuButtonDelegate(new ListMenuButton.Delegate() {
@Override
public Item[] getItems() {
ArrayList<Item> menuItems = new ArrayList<>();
......@@ -66,7 +64,7 @@ public class LanguageListPreference extends Preference {
// Add some appropriate options for moving the language when the list is not
// draggable. E.g. in the accessibility mode.
if (!isDragEnabled()) {
if (!mDragStateDelegate.getDragEnabled()) {
// Add "Move to top" and "Move up" menu when it's not the first one.
if (position > 0) {
menuItems.add(new Item(mContext, R.string.menu_item_move_to_top, true));
......@@ -114,14 +112,18 @@ public class LanguageListPreference extends Preference {
@Override
public void onDataUpdated() {
reload(LanguagesManager.getInstance().getUserAcceptLanguageItems());
if (mDragStateDelegate.getDragActive()) {
enableDrag();
} else {
disableDrag();
}
setDisplayedLanguages(LanguagesManager.getInstance().getUserAcceptLanguageItems());
}
}
private TextView mAddLanguageButton;
private RecyclerView mRecyclerView;
private LanguageListAdapter mAdapter;
private AddLanguageFragment.Launcher mLauncher;
public LanguageListPreference(Context context, AttributeSet attrs) {
......@@ -152,27 +154,13 @@ public class LanguageListPreference extends Preference {
mRecyclerView.addItemDecoration(
new DividerItemDecoration(getContext(), layoutMangager.getOrientation()));
// Due to a known native bug (crbug/640763), the list order written into Preference Service
// might be different from the order shown after it's adjusted by dragging.
if (!AccessibilityUtil.isAccessibilityEnabled()) mAdapter.enableDrag(mRecyclerView);
AccessibilityManager manager =
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
manager.addAccessibilityStateChangeListener(enabled -> {
if (enabled) {
mAdapter.disableDrag();
} else {
mAdapter.enableDrag(mRecyclerView);
}
mAdapter.notifyDataSetChanged();
});
// We do not want the RecyclerView to be announced by screen readers every time
// the view is bound.
if (mRecyclerView.getAdapter() != mAdapter) {
mRecyclerView.setAdapter(mAdapter);
LanguagesManager.getInstance().setAcceptLanguageObserver(mAdapter);
// Initialize accept language list.
mAdapter.reload(LanguagesManager.getInstance().getUserAcceptLanguageItems());
mAdapter.onDataUpdated();
}
}
......
......@@ -159,6 +159,18 @@ class LanguagesManager {
if (reload) notifyAcceptLanguageObserver();
}
/**
* Sets the preference order of the user's accepted languages to the provided order.
*
* @param codes The new order for the user's languages.
* @param reload True iff the language list should be reloaded.
*/
public void setOrder(String[] codes, boolean reload) {
PrefServiceBridge.getInstance().setLanguageOrder(codes);
recordAction(LanguageSettingsActionType.LANGUAGE_LIST_REORDERED);
if (reload) notifyAcceptLanguageObserver();
}
/**
* Get the static instance of ChromePreferenceManager if it exists else create it.
* @return the LanguagesManager singleton.
......
......@@ -1229,6 +1229,21 @@ static void JNI_PrefServiceBridge_GetUserAcceptLanguages(
env, list, ToJavaArrayOfStrings(env, languages));
}
static void JNI_PrefServiceBridge_SetLanguageOrder(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobjectArray>& j_order) {
std::unique_ptr<translate::TranslatePrefs> translate_prefs =
ChromeTranslateClient::CreateTranslatePrefs(GetPrefService());
std::vector<std::string> order;
const int num_langs = (*env).GetArrayLength(j_order);
for (int i = 0; i < num_langs; i++) {
jstring string = (jstring)(*env).GetObjectArrayElement(j_order, i);
order.push_back((*env).GetStringUTFChars(string, nullptr));
}
translate_prefs->SetLanguageOrder(order);
}
static void JNI_PrefServiceBridge_UpdateUserAcceptLanguages(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
......
......@@ -368,6 +368,11 @@ void TranslatePrefs::RearrangeLanguage(
}
}
void TranslatePrefs::SetLanguageOrder(
const std::vector<std::string>& new_order) {
UpdateLanguageList(new_order);
}
// static
void TranslatePrefs::GetLanguageInfoList(
const std::string& app_locale,
......
......@@ -206,6 +206,11 @@ class TranslatePrefs {
const int offset,
const std::vector<std::string>& enabled_languages);
// Sets the language order to the provided order.
// This function is called from the language preference manager in Chrome for
// Android.
void SetLanguageOrder(const std::vector<std::string>& new_order);
// Returns the list of TranslateLanguageInfo for all languages that are
// available in the given locale.
// The list returned in |languages| is sorted alphabetically based on the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment