1/*
2 * Copyright (C) 2015 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
17package android.support.v17.preference;
18
19import android.os.Bundle;
20import android.support.annotation.NonNull;
21import android.support.annotation.Nullable;
22import android.support.v14.preference.MultiSelectListPreference;
23import android.support.v17.leanback.widget.VerticalGridView;
24import android.support.v7.preference.DialogPreference;
25import android.support.v7.preference.ListPreference;
26import android.support.v7.widget.RecyclerView;
27import android.text.TextUtils;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.Checkable;
32import android.widget.TextView;
33
34import java.util.HashSet;
35import java.util.Set;
36
37public class LeanbackListPreferenceDialogFragment extends LeanbackPreferenceDialogFragment {
38
39    public static LeanbackListPreferenceDialogFragment newInstanceSingle(String key) {
40        final Bundle args = new Bundle(5);
41        args.putString(ARG_KEY, key);
42
43        final LeanbackListPreferenceDialogFragment
44                fragment = new LeanbackListPreferenceDialogFragment();
45        fragment.setArguments(args);
46
47        return fragment;
48    }
49
50    public static LeanbackListPreferenceDialogFragment newInstanceMulti(String key) {
51        final Bundle args = new Bundle(5);
52        args.putString(ARG_KEY, key);
53
54        final LeanbackListPreferenceDialogFragment
55                fragment = new LeanbackListPreferenceDialogFragment();
56        fragment.setArguments(args);
57
58        return fragment;
59    }
60
61    @Override
62    public void onCreate(Bundle savedInstanceState) {
63        super.onCreate(savedInstanceState);
64
65        final DialogPreference preference = getPreference();
66        if (!(preference instanceof ListPreference) &&
67                !(preference instanceof MultiSelectListPreference)) {
68            throw new IllegalArgumentException("Preference must be a ListPreference or " +
69                    "MultiSelectListPreference");
70        }
71    }
72
73    @Override
74    public @Nullable View onCreateView(LayoutInflater inflater, ViewGroup container,
75            Bundle savedInstanceState) {
76        final View view = inflater.inflate(R.layout.leanback_list_preference_fragment, container,
77                false);
78        final VerticalGridView verticalGridView =
79                (VerticalGridView) view.findViewById(android.R.id.list);
80
81        verticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
82        verticalGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
83        verticalGridView.setAdapter(onCreateAdapter());
84        verticalGridView.requestFocus();
85
86        final DialogPreference preference = getPreference();
87        final CharSequence title = preference.getDialogTitle();
88        if (!TextUtils.isEmpty(title)) {
89            final TextView titleView = (TextView) view.findViewById(R.id.decor_title);
90            titleView.setText(title);
91        }
92
93        final CharSequence message = preference.getDialogMessage();
94        if (!TextUtils.isEmpty(message)) {
95            final TextView messageView = (TextView) view.findViewById(android.R.id.message);
96            messageView.setVisibility(View.VISIBLE);
97            messageView.setText(message);
98        }
99
100        return view;
101    }
102
103    public RecyclerView.Adapter onCreateAdapter() {
104        final DialogPreference preference = getPreference();
105        if (preference instanceof MultiSelectListPreference) {
106            final MultiSelectListPreference pref = (MultiSelectListPreference) preference;
107            final CharSequence[] entries = pref.getEntries();
108            final CharSequence[] entryValues = pref.getEntryValues();
109            final Set<String> initialSelections = pref.getValues();
110            return new AdapterMulti(entries, entryValues, initialSelections);
111        } else if (preference instanceof ListPreference) {
112            final ListPreference pref = (ListPreference) preference;
113            final CharSequence[] entries = pref.getEntries();
114            final CharSequence[] entryValues = pref.getEntryValues();
115            final String initialSelection = pref.getValue();
116            return new AdapterSingle(entries, entryValues, initialSelection);
117        } else {
118            throw new IllegalStateException("Unknown preference type");
119        }
120    }
121
122    public class AdapterSingle extends RecyclerView.Adapter<ViewHolder>
123            implements ViewHolder.OnItemClickListener {
124
125        private final CharSequence[] mEntries;
126        private final CharSequence[] mEntryValues;
127        private CharSequence mSelectedValue;
128
129        public AdapterSingle(CharSequence[] entries, CharSequence[] entryValues,
130                CharSequence selectedValue) {
131            mEntries = entries;
132            mEntryValues = entryValues;
133            mSelectedValue = selectedValue;
134        }
135
136        @Override
137        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
138            final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
139            final View view = inflater.inflate(R.layout.leanback_list_preference_item_single,
140                    parent, false);
141            return new ViewHolder(view, this);
142        }
143
144        @Override
145        public void onBindViewHolder(ViewHolder holder, int position) {
146            holder.getWidgetView().setChecked(mEntryValues[position].equals(mSelectedValue));
147            holder.getTitleView().setText(mEntries[position]);
148        }
149
150        @Override
151        public int getItemCount() {
152            return mEntries.length;
153        }
154
155        @Override
156        public void onItemClick(ViewHolder viewHolder) {
157            final int index = viewHolder.getAdapterPosition();
158            if (index == RecyclerView.NO_POSITION) {
159                return;
160            }
161            final CharSequence entry = mEntryValues[index];
162            final ListPreference preference = (ListPreference) getPreference();
163            if (index >= 0) {
164                String value = mEntryValues[index].toString();
165                if (preference.callChangeListener(value)) {
166                    preference.setValue(value);
167                    mSelectedValue = entry;
168                }
169            }
170
171            getFragmentManager().popBackStack();
172            notifyDataSetChanged();
173        }
174    }
175
176    public class AdapterMulti extends RecyclerView.Adapter<ViewHolder>
177            implements ViewHolder.OnItemClickListener {
178
179        private final CharSequence[] mEntries;
180        private final CharSequence[] mEntryValues;
181        private final Set<String> mSelections;
182
183        public AdapterMulti(CharSequence[] entries, CharSequence[] entryValues,
184                Set<String> initialSelections) {
185            mEntries = entries;
186            mEntryValues = entryValues;
187            mSelections = new HashSet<>(initialSelections);
188        }
189
190        @Override
191        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
192            final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
193            final View view = inflater.inflate(R.layout.leanback_list_preference_item_multi, parent,
194                    false);
195            return new ViewHolder(view, this);
196        }
197
198        @Override
199        public void onBindViewHolder(ViewHolder holder, int position) {
200            holder.getWidgetView().setChecked(
201                    mSelections.contains(mEntryValues[position].toString()));
202            holder.getTitleView().setText(mEntries[position]);
203        }
204
205        @Override
206        public int getItemCount() {
207            return mEntries.length;
208        }
209
210        @Override
211        public void onItemClick(ViewHolder viewHolder) {
212            final int index = viewHolder.getAdapterPosition();
213            if (index == RecyclerView.NO_POSITION) {
214                return;
215            }
216            final String entry = mEntryValues[index].toString();
217            if (mSelections.contains(entry)) {
218                mSelections.remove(entry);
219            } else {
220                mSelections.add(entry);
221            }
222            final MultiSelectListPreference multiSelectListPreference
223                    = (MultiSelectListPreference) getPreference();
224            // Pass copies of the set to callChangeListener and setValues to avoid mutations
225            if (multiSelectListPreference.callChangeListener(new HashSet<>(mSelections))) {
226                multiSelectListPreference.setValues(new HashSet<>(mSelections));
227            } else {
228                // Change refused, back it out
229                if (mSelections.contains(entry)) {
230                    mSelections.remove(entry);
231                } else {
232                    mSelections.add(entry);
233                }
234            }
235
236            notifyDataSetChanged();
237        }
238    }
239
240    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
241
242        public interface OnItemClickListener {
243            void onItemClick(ViewHolder viewHolder);
244        }
245
246        private final Checkable mWidgetView;
247        private final TextView mTitleView;
248        private final ViewGroup mContainer;
249        private final OnItemClickListener mListener;
250
251        public ViewHolder(@NonNull View view, @NonNull OnItemClickListener listener) {
252            super(view);
253            mWidgetView = (Checkable) view.findViewById(R.id.button);
254            mContainer = (ViewGroup) view.findViewById(R.id.container);
255            mTitleView = (TextView) view.findViewById(android.R.id.title);
256            mContainer.setOnClickListener(this);
257            mListener = listener;
258        }
259
260        public Checkable getWidgetView() {
261            return mWidgetView;
262        }
263
264        public TextView getTitleView() {
265            return mTitleView;
266        }
267
268        public ViewGroup getContainer() {
269            return mContainer;
270        }
271
272        @Override
273        public void onClick(View v) {
274            mListener.onItemClick(this);
275        }
276    }
277}
278