Time zone, Region, UTC picker
- 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
)
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user