- Extract most common view related codes into BaseTimeZoneAdapter
and BaseTimeZonePicker. Subclass handles the text formatting and
order.
- Search view is added compared to previous version of time
zone picker
- SpannableUtil is added to preserve spannable when formatting
String resource.
- Fix the bug using GMT+<arabic> as time zone id. b/73132985
- Fix Talkback treating flags on screens as a separate element
Bug: 72146259
Bug: 73132985
Bug: 73952488
Test: mm RunSettingsRoboTests
Change-Id: I42c6ac369199c09d11e7f5cc4707358fa4780fed
(cherry picked from commit fbd30acef0)
207 lines
7.4 KiB
Java
207 lines
7.4 KiB
Java
/*
|
|
* Copyright (C) 2018 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.settings.datetime.timezone;
|
|
|
|
import android.icu.text.BreakIterator;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.WorkerThread;
|
|
import android.support.v7.widget.RecyclerView;
|
|
import android.text.TextUtils;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.Filter;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.datetime.timezone.BaseTimeZonePicker.OnListItemClickListener;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
/**
|
|
* Used with {@class BaseTimeZonePicker}. It renders text in each item into list view. A list of
|
|
* {@class AdapterItem} must be provided when an instance is created.
|
|
*/
|
|
public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
|
extends RecyclerView.Adapter<BaseTimeZoneAdapter.ItemViewHolder> {
|
|
|
|
private final List<T> mOriginalItems;
|
|
private final OnListItemClickListener mOnListItemClickListener;
|
|
private final Locale mLocale;
|
|
private final boolean mShowItemSummary;
|
|
|
|
private List<T> mItems;
|
|
private ArrayFilter mFilter;
|
|
|
|
public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener
|
|
onListItemClickListener, Locale locale, boolean showItemSummary) {
|
|
mOriginalItems = items;
|
|
mItems = items;
|
|
mOnListItemClickListener = onListItemClickListener;
|
|
mLocale = locale;
|
|
mShowItemSummary = showItemSummary;
|
|
setHasStableIds(true);
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
final View view = LayoutInflater.from(parent.getContext())
|
|
.inflate(R.layout.time_zone_search_item, parent, false);
|
|
return new ItemViewHolder(view, mOnListItemClickListener);
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
|
|
final AdapterItem item = mItems.get(position);
|
|
holder.mSummaryFrame.setVisibility(
|
|
mShowItemSummary ? View.VISIBLE : View.GONE);
|
|
holder.mTitleView.setText(item.getTitle());
|
|
holder.mIconTextView.setText(item.getIconText());
|
|
holder.mSummaryView.setText(item.getSummary());
|
|
holder.mTimeView.setText(item.getCurrentTime());
|
|
holder.setPosition(position);
|
|
}
|
|
|
|
@Override
|
|
public long getItemId(int position) {
|
|
return getItem(position).getItemId();
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount() {
|
|
return mItems.size();
|
|
}
|
|
|
|
public @NonNull
|
|
Filter getFilter() {
|
|
if (mFilter == null) {
|
|
mFilter = new ArrayFilter();
|
|
}
|
|
return mFilter;
|
|
}
|
|
|
|
public T getItem(int position) {
|
|
return mItems.get(position);
|
|
}
|
|
|
|
public interface AdapterItem {
|
|
CharSequence getTitle();
|
|
CharSequence getSummary();
|
|
String getIconText();
|
|
String getCurrentTime();
|
|
long getItemId();
|
|
String[] getSearchKeys();
|
|
}
|
|
|
|
public static class ItemViewHolder extends RecyclerView.ViewHolder
|
|
implements View.OnClickListener{
|
|
|
|
final OnListItemClickListener mOnListItemClickListener;
|
|
final View mSummaryFrame;
|
|
final TextView mTitleView;
|
|
final TextView mIconTextView;
|
|
final TextView mSummaryView;
|
|
final TextView mTimeView;
|
|
private int mPosition;
|
|
|
|
public ItemViewHolder(View itemView, OnListItemClickListener onListItemClickListener) {
|
|
super(itemView);
|
|
itemView.setOnClickListener(this);
|
|
mSummaryFrame = itemView.findViewById(R.id.summary_frame);
|
|
mTitleView = itemView.findViewById(android.R.id.title);
|
|
mIconTextView = itemView.findViewById(R.id.icon_text);
|
|
mSummaryView = itemView.findViewById(android.R.id.summary);
|
|
mTimeView = itemView.findViewById(R.id.current_time);
|
|
mOnListItemClickListener = onListItemClickListener;
|
|
}
|
|
|
|
public void setPosition(int position) {
|
|
mPosition = position;
|
|
}
|
|
|
|
@Override
|
|
public void onClick(View v) {
|
|
mOnListItemClickListener.onListItemClick(mPosition);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <p>An array filter constrains the content of the array adapter with
|
|
* a prefix. Each item that does not start with the supplied prefix
|
|
* is removed from the list.</p>
|
|
*
|
|
* The filtering operation is not optimized, due to small data size (~260 regions),
|
|
* require additional pre-processing. Potentially, a trie structure can be used to match
|
|
* prefixes of the search keys.
|
|
*/
|
|
private class ArrayFilter extends Filter {
|
|
|
|
private BreakIterator mBreakIterator = BreakIterator.getWordInstance(mLocale);
|
|
|
|
@WorkerThread
|
|
@Override
|
|
protected FilterResults performFiltering(CharSequence prefix) {
|
|
final List<T> newItems;
|
|
if (TextUtils.isEmpty(prefix)) {
|
|
newItems = mOriginalItems;
|
|
} else {
|
|
final String prefixString = prefix.toString().toLowerCase(mLocale);
|
|
newItems = new ArrayList<>();
|
|
|
|
for (T item : mOriginalItems) {
|
|
outer:
|
|
for (String searchKey : item.getSearchKeys()) {
|
|
searchKey = searchKey.toLowerCase(mLocale);
|
|
// First match against the whole, non-splitted value
|
|
if (searchKey.startsWith(prefixString)) {
|
|
newItems.add(item);
|
|
break outer;
|
|
} else {
|
|
mBreakIterator.setText(searchKey);
|
|
for (int wordStart = 0, wordLimit = mBreakIterator.next();
|
|
wordLimit != BreakIterator.DONE;
|
|
wordStart = wordLimit,
|
|
wordLimit = mBreakIterator.next()) {
|
|
if (mBreakIterator.getRuleStatus() != BreakIterator.WORD_NONE
|
|
&& searchKey.startsWith(prefixString, wordStart)) {
|
|
newItems.add(item);
|
|
break outer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final FilterResults results = new FilterResults();
|
|
results.values = newItems;
|
|
results.count = newItems.size();
|
|
|
|
return results;
|
|
}
|
|
|
|
@Override
|
|
protected void publishResults(CharSequence constraint, FilterResults results) {
|
|
mItems = (List<T>) results.values;
|
|
notifyDataSetChanged();
|
|
}
|
|
}
|
|
}
|