Merge "Add a header view to show the country in RegionZonePicker" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
05ab467e80
22
res/layout/time_zone_items_list.xml
Normal file
22
res/layout/time_zone_items_list.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<android.support.v7.widget.RecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"/>
|
@@ -27,7 +27,7 @@
|
||||
android:summary="@string/summary_placeholder" />
|
||||
<com.android.settingslib.RestrictedPreference
|
||||
android:key="region_zone"
|
||||
android:title="@string/date_time_select_zone"
|
||||
android:title="@string/date_time_set_timezone_title"
|
||||
android:summary="@string/summary_placeholder" />
|
||||
<com.android.settingslib.widget.FooterPreference/>
|
||||
</PreferenceCategory>
|
||||
|
@@ -18,6 +18,7 @@ package com.android.settings.datetime.timezone;
|
||||
|
||||
import android.icu.text.BreakIterator;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -40,48 +41,98 @@ import java.util.Locale;
|
||||
* {@class AdapterItem} must be provided when an instance is created.
|
||||
*/
|
||||
public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
||||
extends RecyclerView.Adapter<BaseTimeZoneAdapter.ItemViewHolder> {
|
||||
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
@VisibleForTesting
|
||||
static final int TYPE_HEADER = 0;
|
||||
@VisibleForTesting
|
||||
static final int TYPE_ITEM = 1;
|
||||
|
||||
private final List<T> mOriginalItems;
|
||||
private final OnListItemClickListener<T> mOnListItemClickListener;
|
||||
private final Locale mLocale;
|
||||
private final boolean mShowItemSummary;
|
||||
private final boolean mShowHeader;
|
||||
private final CharSequence mHeaderText;
|
||||
|
||||
private List<T> mItems;
|
||||
private ArrayFilter mFilter;
|
||||
|
||||
public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener<T>
|
||||
onListItemClickListener, Locale locale, boolean showItemSummary) {
|
||||
/**
|
||||
* @param headerText the text shown in the header, or null to show no header.
|
||||
*/
|
||||
public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener<T> onListItemClickListener,
|
||||
Locale locale, boolean showItemSummary, @Nullable CharSequence headerText) {
|
||||
mOriginalItems = items;
|
||||
mItems = items;
|
||||
mOnListItemClickListener = onListItemClickListener;
|
||||
mLocale = locale;
|
||||
mShowItemSummary = showItemSummary;
|
||||
mShowHeader = headerText != null;
|
||||
mHeaderText = headerText;
|
||||
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);
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
switch(viewType) {
|
||||
case TYPE_HEADER: {
|
||||
final View view = inflater.inflate(R.layout.preference_category_material_settings,
|
||||
parent, false);
|
||||
return new HeaderViewHolder(view);
|
||||
}
|
||||
case TYPE_ITEM: {
|
||||
final View view = inflater.inflate(R.layout.time_zone_search_item, parent, false);
|
||||
return new ItemViewHolder(view, mOnListItemClickListener);
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected viewType: " + viewType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
|
||||
holder.setAdapterItem(mItems.get(position));
|
||||
holder.mSummaryFrame.setVisibility(mShowItemSummary ? View.VISIBLE : View.GONE);
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
if (holder instanceof HeaderViewHolder) {
|
||||
((HeaderViewHolder) holder).setText(mHeaderText);
|
||||
} else if (holder instanceof ItemViewHolder) {
|
||||
ItemViewHolder<T> itemViewHolder = (ItemViewHolder<T>) holder;
|
||||
itemViewHolder.setAdapterItem(getDataItem(position));
|
||||
itemViewHolder.mSummaryFrame.setVisibility(mShowItemSummary ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).getItemId();
|
||||
// Data item can't have negative id
|
||||
return isPositionHeader(position) ? -1 : getDataItem(position).getItemId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mItems.size();
|
||||
return mItems.size() + getHeaderCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return isPositionHeader(position) ? TYPE_HEADER : TYPE_ITEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Avoid being overridden by making the method final, since constructor shouldn't invoke
|
||||
* overridable method.
|
||||
*/
|
||||
@Override
|
||||
public final void setHasStableIds(boolean hasStableIds) {
|
||||
super.setHasStableIds(hasStableIds);
|
||||
}
|
||||
|
||||
private int getHeaderCount() {
|
||||
return mShowHeader ? 1 : 0;
|
||||
}
|
||||
|
||||
private boolean isPositionHeader(int position) {
|
||||
return mShowHeader && position == 0;
|
||||
}
|
||||
|
||||
public @NonNull ArrayFilter getFilter() {
|
||||
@@ -91,8 +142,12 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
||||
return mFilter;
|
||||
}
|
||||
|
||||
public T getItem(int position) {
|
||||
return mItems.get(position);
|
||||
/**
|
||||
* @throws IndexOutOfBoundsException if the view type at the position is a header
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public T getDataItem(int position) {
|
||||
return mItems.get(position - getHeaderCount());
|
||||
}
|
||||
|
||||
public interface AdapterItem {
|
||||
@@ -100,10 +155,28 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
||||
CharSequence getSummary();
|
||||
String getIconText();
|
||||
String getCurrentTime();
|
||||
|
||||
/**
|
||||
* @return unique non-negative number
|
||||
*/
|
||||
long getItemId();
|
||||
String[] getSearchKeys();
|
||||
}
|
||||
|
||||
private static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView mTextView;
|
||||
|
||||
public HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
mTextView = itemView.findViewById(android.R.id.title);
|
||||
}
|
||||
|
||||
public void setText(CharSequence text) {
|
||||
mTextView.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static class ItemViewHolder<T extends BaseTimeZoneAdapter.AdapterItem>
|
||||
extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
|
||||
|
@@ -23,6 +23,7 @@ import android.content.res.Resources;
|
||||
import android.icu.text.DateFormat;
|
||||
import android.icu.text.SimpleDateFormat;
|
||||
import android.icu.util.Calendar;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.datetime.timezone.model.TimeZoneData;
|
||||
@@ -47,10 +48,17 @@ public abstract class BaseTimeZoneInfoPicker extends BaseTimeZonePicker {
|
||||
@Override
|
||||
protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
|
||||
mAdapter = new ZoneAdapter(getContext(), getAllTimeZoneInfos(timeZoneData),
|
||||
this::onListItemClick, getLocale());
|
||||
this::onListItemClick, getLocale(), getHeaderText());
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the text shown in the header, or null to show no header.
|
||||
*/
|
||||
protected @Nullable CharSequence getHeaderText() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void onListItemClick(TimeZoneInfoItem item) {
|
||||
final TimeZoneInfo timeZoneInfo = item.mTimeZoneInfo;
|
||||
getActivity().setResult(Activity.RESULT_OK, prepareResultData(timeZoneInfo));
|
||||
@@ -66,9 +74,11 @@ public abstract class BaseTimeZoneInfoPicker extends BaseTimeZonePicker {
|
||||
protected static class ZoneAdapter extends BaseTimeZoneAdapter<TimeZoneInfoItem> {
|
||||
|
||||
public ZoneAdapter(Context context, List<TimeZoneInfo> timeZones,
|
||||
OnListItemClickListener<TimeZoneInfoItem> onListItemClickListener, Locale locale) {
|
||||
OnListItemClickListener<TimeZoneInfoItem> onListItemClickListener, Locale locale,
|
||||
CharSequence headerText) {
|
||||
super(createTimeZoneInfoItems(context, timeZones, locale),
|
||||
onListItemClickListener, locale, true /* showItemSummary */);
|
||||
onListItemClickListener, locale, true /* showItemSummary */,
|
||||
headerText /* headerText */);
|
||||
}
|
||||
|
||||
private static List<TimeZoneInfoItem> createTimeZoneInfoItems(Context context,
|
||||
|
@@ -44,7 +44,7 @@ import java.util.Locale;
|
||||
* The search matches the prefix of words in the search text.
|
||||
*/
|
||||
public abstract class BaseTimeZonePicker extends InstrumentedFragment
|
||||
implements SearchView.OnQueryTextListener{
|
||||
implements SearchView.OnQueryTextListener {
|
||||
|
||||
public static final String EXTRA_RESULT_REGION_ID =
|
||||
"com.android.settings.datetime.timezone.result_region_id";
|
||||
@@ -84,7 +84,7 @@ public abstract class BaseTimeZonePicker extends InstrumentedFragment
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.recycler_view, container, false);
|
||||
final View view = inflater.inflate(R.layout.time_zone_items_list, container, false);
|
||||
mRecyclerView = view.findViewById(R.id.recycler_view);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),
|
||||
LinearLayoutManager.VERTICAL, /* reverseLayout */ false));
|
||||
|
@@ -48,7 +48,7 @@ public class RegionSearchPicker extends BaseTimeZonePicker {
|
||||
private TimeZoneData mTimeZoneData;
|
||||
|
||||
public RegionSearchPicker() {
|
||||
super(R.string.date_time_select_region, R.string.search_settings, true, true);
|
||||
super(R.string.date_time_select_region, R.string.date_time_search_region, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,7 +60,8 @@ public class RegionSearchPicker extends BaseTimeZonePicker {
|
||||
protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
|
||||
mTimeZoneData = timeZoneData;
|
||||
mAdapter = new BaseTimeZoneAdapter<>(createAdapterItem(timeZoneData.getRegionIds()),
|
||||
this::onListItemClick, getLocale(), false);
|
||||
this::onListItemClick, getLocale(), false /* showItemSummary */,
|
||||
null /* headerText */);
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,10 @@ package com.android.settings.datetime.timezone;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.icu.text.Collator;
|
||||
import android.icu.text.LocaleDisplayNames;
|
||||
import android.icu.util.TimeZone;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -43,8 +46,10 @@ public class RegionZonePicker extends BaseTimeZoneInfoPicker {
|
||||
public static final String EXTRA_REGION_ID =
|
||||
"com.android.settings.datetime.timezone.region_id";
|
||||
|
||||
private @Nullable String mRegionName;
|
||||
|
||||
public RegionZonePicker() {
|
||||
super(R.string.date_time_select_zone, R.string.search_settings, true, false);
|
||||
super(R.string.date_time_set_timezone_title, R.string.search_settings, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,6 +57,21 @@ public class RegionZonePicker extends BaseTimeZoneInfoPicker {
|
||||
return MetricsProto.MetricsEvent.SETTINGS_ZONE_PICKER_TIME_ZONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final LocaleDisplayNames localeDisplayNames = LocaleDisplayNames.getInstance(getLocale());
|
||||
final String regionId =
|
||||
getArguments() == null ? null : getArguments().getString(EXTRA_REGION_ID);
|
||||
mRegionName = regionId == null ? null : localeDisplayNames.regionDisplayName(regionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable CharSequence getHeaderText() {
|
||||
return mRegionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the extra region id into the result.
|
||||
*/
|
||||
@@ -67,6 +87,7 @@ public class RegionZonePicker extends BaseTimeZoneInfoPicker {
|
||||
if (getArguments() == null) {
|
||||
Log.e(TAG, "getArguments() == null");
|
||||
getActivity().finish();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String regionId = getArguments().getString(EXTRA_REGION_ID);
|
||||
|
||||
@@ -75,6 +96,7 @@ public class RegionZonePicker extends BaseTimeZoneInfoPicker {
|
||||
if (filteredCountryTimeZones == null) {
|
||||
Log.e(TAG, "region id is not valid: " + regionId);
|
||||
getActivity().finish();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// It could be a timely operations if there are many time zones. A region in time zone data
|
||||
|
@@ -62,7 +62,7 @@ public class BaseTimeZoneAdapterTest {
|
||||
observer.await();
|
||||
assertThat(adapter.getItemCount()).isEqualTo(items.length);
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
assertThat(adapter.getItem(i)).isEqualTo(items[i]);
|
||||
assertThat(adapter.getDataItem(i)).isEqualTo(items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ public class BaseTimeZoneAdapterTest {
|
||||
private static class TestTimeZoneAdapter extends BaseTimeZoneAdapter<TestItem> {
|
||||
|
||||
public TestTimeZoneAdapter(List<TestItem> items) {
|
||||
super(items, position -> {}, Locale.US, false);
|
||||
super(items, position -> {}, Locale.US, false /* showItemSummary */,
|
||||
null /* headerText */);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -61,13 +61,13 @@ public class BaseTimeZoneInfoPickerTest {
|
||||
BaseTimeZoneAdapter adapter = picker.createAdapter(mock(TimeZoneData.class));
|
||||
Truth.assertThat(adapter.getItemCount()).isEqualTo(2);
|
||||
|
||||
BaseTimeZoneAdapter.AdapterItem item1 = adapter.getItem(0);
|
||||
BaseTimeZoneAdapter.AdapterItem item1 = adapter.getDataItem(0);
|
||||
Truth.assertThat(item1.getTitle().toString()).isEqualTo("Los Angeles");
|
||||
Truth.assertThat(item1.getSummary().toString()).isEqualTo("Pacific Time (GMT-08:00)");
|
||||
Truth.assertThat(item1.getCurrentTime())
|
||||
.hasLength(ShadowDataFormat.sTimeFormatString.length());
|
||||
|
||||
BaseTimeZoneAdapter.AdapterItem item2 = adapter.getItem(1);
|
||||
BaseTimeZoneAdapter.AdapterItem item2 = adapter.getDataItem(1);
|
||||
Truth.assertThat(item2.getTitle().toString()).isEqualTo("New York");
|
||||
Truth.assertThat(item2.getSummary().toString()).isEqualTo("Eastern Time (GMT-05:00)");
|
||||
Truth.assertThat(item2.getCurrentTime())
|
||||
|
@@ -65,10 +65,10 @@ public class FixedOffsetPickerTest {
|
||||
TestFixedOffsetPicker picker = new TestFixedOffsetPicker();
|
||||
BaseTimeZoneAdapter adapter = picker.createAdapter(new TimeZoneData(mFinder));
|
||||
assertThat(adapter.getItemCount()).isEqualTo(12 + 1 + 14); // 27 GMT offsets from -12 to +14
|
||||
AdapterItem utc = adapter.getItem(0);
|
||||
AdapterItem utc = adapter.getDataItem(0);
|
||||
assertThat(utc.getTitle().toString()).isEqualTo("Coordinated Universal Time");
|
||||
assertThat(utc.getSummary().toString()).isEqualTo("GMT+00:00");
|
||||
AdapterItem gmtMinus12 = adapter.getItem(1);
|
||||
AdapterItem gmtMinus12 = adapter.getDataItem(1);
|
||||
assertThat(gmtMinus12.getTitle().toString()).isEqualTo("GMT-12:00");
|
||||
assertThat(gmtMinus12.getSummary().toString()).isEmpty();
|
||||
}
|
||||
|
@@ -66,7 +66,7 @@ public class RegionSearchPickerTest {
|
||||
RegionSearchPicker picker = new RegionSearchPicker();
|
||||
BaseTimeZoneAdapter adapter = picker.createAdapter(new TimeZoneData(finder));
|
||||
assertEquals(1, adapter.getItemCount());
|
||||
AdapterItem item = adapter.getItem(0);
|
||||
AdapterItem item = adapter.getDataItem(0);
|
||||
assertEquals("United States", item.getTitle().toString());
|
||||
assertThat(Arrays.asList(item.getSearchKeys())).contains("United States");
|
||||
}
|
||||
@@ -86,8 +86,8 @@ public class RegionSearchPickerTest {
|
||||
RegionSearchPicker picker = new RegionSearchPicker();
|
||||
BaseTimeZoneAdapter<RegionItem> adapter = picker.createAdapter(new TimeZoneData(finder));
|
||||
// Prepare and bind a new ItemViewHolder with United States
|
||||
ItemViewHolder viewHolder = adapter.onCreateViewHolder(
|
||||
new LinearLayout(RuntimeEnvironment.application), 0);
|
||||
ItemViewHolder viewHolder = (ItemViewHolder) adapter.onCreateViewHolder(
|
||||
new LinearLayout(RuntimeEnvironment.application), BaseTimeZoneAdapter.TYPE_ITEM);
|
||||
adapter.onBindViewHolder(viewHolder, 0);
|
||||
assertEquals(1, adapter.getItemCount());
|
||||
|
||||
|
Reference in New Issue
Block a user