1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.datetime.timezone;
18 
19 import android.content.res.Configuration;
20 import android.os.Bundle;
21 import android.view.LayoutInflater;
22 import android.view.Menu;
23 import android.view.MenuInflater;
24 import android.view.MenuItem;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.LinearLayout;
28 import android.widget.SearchView;
29 import android.widget.TextView;
30 
31 import androidx.annotation.NonNull;
32 import androidx.coordinatorlayout.widget.CoordinatorLayout;
33 import androidx.core.view.ViewCompat;
34 import androidx.recyclerview.widget.LinearLayoutManager;
35 import androidx.recyclerview.widget.RecyclerView;
36 
37 import com.android.settings.R;
38 import com.android.settings.core.InstrumentedFragment;
39 import com.android.settings.datetime.timezone.model.TimeZoneData;
40 import com.android.settings.datetime.timezone.model.TimeZoneDataLoader;
41 
42 import com.google.android.material.appbar.AppBarLayout;
43 
44 import java.util.Locale;
45 
46 /**
47  * It's abstract class. Subclass should use it with {@class BaseTimeZoneAdapter} and
48  * {@class AdapterItem} to provide a list view with text search capability.
49  * The search matches the prefix of words in the search text.
50  */
51 public abstract class BaseTimeZonePicker extends InstrumentedFragment
52         implements SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
53 
54     public static final String EXTRA_RESULT_REGION_ID =
55             "com.android.settings.datetime.timezone.result_region_id";
56     public static final String EXTRA_RESULT_TIME_ZONE_ID =
57             "com.android.settings.datetime.timezone.result_time_zone_id";
58 
59     protected AppBarLayout mAppBarLayout;
60 
61     private final int mTitleResId;
62     private final int mSearchHintResId;
63     private final boolean mSearchEnabled;
64     private final boolean mDefaultExpandSearch;
65 
66     protected Locale mLocale;
67     private BaseTimeZoneAdapter mAdapter;
68     private RecyclerView mRecyclerView;
69     private TimeZoneData mTimeZoneData;
70 
71     private SearchView mSearchView;
72 
73     /**
74      * Constructor called by subclass.
75      * @param defaultExpandSearch whether expand the search view when first launching the fragment
76      */
BaseTimeZonePicker(int titleResId, int searchHintResId, boolean searchEnabled, boolean defaultExpandSearch)77     protected BaseTimeZonePicker(int titleResId, int searchHintResId,
78             boolean searchEnabled, boolean defaultExpandSearch) {
79         mTitleResId = titleResId;
80         mSearchHintResId = searchHintResId;
81         mSearchEnabled = searchEnabled;
82         mDefaultExpandSearch = defaultExpandSearch;
83     }
84 
85     @Override
onCreate(Bundle savedInstanceState)86     public void onCreate(Bundle savedInstanceState) {
87         super.onCreate(savedInstanceState);
88         setHasOptionsMenu(true);
89         getActivity().setTitle(mTitleResId);
90     }
91 
92     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)93     public View onCreateView(LayoutInflater inflater, ViewGroup container,
94             Bundle savedInstanceState) {
95         final View view = inflater.inflate(R.layout.recycler_view, container, false);
96         mRecyclerView = view.findViewById(R.id.recycler_view);
97         mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),
98                 LinearLayoutManager.VERTICAL, /* reverseLayout */ false));
99         mRecyclerView.setAdapter(mAdapter);
100         mAppBarLayout = getActivity().findViewById(R.id.app_bar);
101         autoSetCollapsingToolbarLayoutScrolling();
102 
103         // Initialize TimeZoneDataLoader only when mRecyclerView is ready to avoid race
104         // during onDateLoaderReady callback.
105         getLoaderManager().initLoader(0, null, new TimeZoneDataLoader.LoaderCreator(
106                 getContext(), this::onTimeZoneDataReady));
107         return view;
108     }
109 
onTimeZoneDataReady(TimeZoneData timeZoneData)110     public void onTimeZoneDataReady(TimeZoneData timeZoneData) {
111         if (mTimeZoneData == null && timeZoneData != null) {
112             mTimeZoneData = timeZoneData;
113             mAdapter = createAdapter(mTimeZoneData);
114             if (mRecyclerView != null) {
115                 mRecyclerView.setAdapter(mAdapter);
116             }
117         }
118     }
119 
getLocale()120     protected Locale getLocale() {
121         return getContext().getResources().getConfiguration().getLocales().get(0);
122     }
123 
124     /**
125      * Called when TimeZoneData is ready.
126      */
createAdapter(TimeZoneData timeZoneData)127     protected abstract BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData);
128 
129     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)130     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
131         if (mSearchEnabled) {
132             inflater.inflate(R.menu.time_zone_base_search_menu, menu);
133 
134             final MenuItem searchMenuItem = menu.findItem(R.id.time_zone_search_menu);
135             searchMenuItem.setOnActionExpandListener(this);
136             mSearchView = (SearchView) searchMenuItem.getActionView();
137 
138             mSearchView.setQueryHint(getText(mSearchHintResId));
139             mSearchView.setOnQueryTextListener(this);
140             mSearchView.setMaxWidth(Integer.MAX_VALUE);
141 
142             if (mDefaultExpandSearch) {
143                 searchMenuItem.expandActionView();
144                 mSearchView.setIconified(false);
145                 mSearchView.setActivated(true);
146                 mSearchView.setQuery("", true /* submit */);
147             }
148 
149             // Set zero margin and padding to align with the text horizontally in the preference
150             final TextView searchViewView = (TextView) mSearchView.findViewById(
151                     com.android.internal.R.id.search_src_text);
152             searchViewView.setPadding(0, searchViewView.getPaddingTop(), 0,
153                     searchViewView.getPaddingBottom());
154             final View editFrame = mSearchView.findViewById(
155                     com.android.internal.R.id.search_edit_frame);
156             final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) editFrame
157                     .getLayoutParams();
158             params.setMarginStart(0);
159             params.setMarginEnd(0);
160             editFrame.setLayoutParams(params);
161         }
162     }
163 
164     @Override
onMenuItemActionExpand(MenuItem item)165     public boolean onMenuItemActionExpand(MenuItem item) {
166         // To prevent a large space on tool bar.
167         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
168         // To prevent user can expand the collapsing tool bar view.
169         ViewCompat.setNestedScrollingEnabled(mRecyclerView, false);
170         return true;
171     }
172 
173     @Override
onMenuItemActionCollapse(MenuItem item)174     public boolean onMenuItemActionCollapse(MenuItem item) {
175         // We keep the collapsed status after user cancel the search function.
176         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
177         ViewCompat.setNestedScrollingEnabled(mRecyclerView, true);
178         return true;
179     }
180 
181     @Override
onQueryTextSubmit(String query)182     public boolean onQueryTextSubmit(String query) {
183         return false;
184     }
185 
186     @Override
onQueryTextChange(String newText)187     public boolean onQueryTextChange(String newText) {
188         if (mAdapter != null) {
189             mAdapter.getFilter().filter(newText);
190         }
191         return false;
192     }
193 
194     public interface OnListItemClickListener<T extends BaseTimeZoneAdapter.AdapterItem> {
onListItemClick(T item)195         void onListItemClick(T item);
196     }
197 
autoSetCollapsingToolbarLayoutScrolling()198     private void autoSetCollapsingToolbarLayoutScrolling() {
199         CoordinatorLayout.LayoutParams params =
200                 (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
201         AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
202         behavior.setDragCallback(
203                 new AppBarLayout.Behavior.DragCallback() {
204                     @Override
205                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
206                         return appBarLayout.getResources().getConfiguration().orientation
207                                 == Configuration.ORIENTATION_LANDSCAPE;
208                     }
209                 });
210         params.setBehavior(behavior);
211     }
212 }
213