1/* 2 * Copyright (C) 2013 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.mail.ui; 19 20import com.android.mail.R; 21import com.google.common.collect.ImmutableList; 22import com.google.common.collect.Sets; 23 24import android.app.AlertDialog; 25import android.app.Dialog; 26import android.app.DialogFragment; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.DialogInterface.OnClickListener; 30import android.os.Bundle; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.ViewGroup; 34import android.widget.AdapterView; 35import android.widget.AdapterView.OnItemClickListener; 36import android.widget.BaseAdapter; 37import android.widget.CheckedTextView; 38import android.widget.ListView; 39 40 41import java.lang.ref.WeakReference; 42import java.util.ArrayList; 43import java.util.Collection; 44import java.util.List; 45import java.util.Set; 46 47/** 48 * A {@link DialogFragment} that shows multiple values, and lets you select 0 to 49 * {@link #MAX_SELECTED_VALUES} of them. 50 */ 51public abstract class LimitedMultiSelectDialogFragment extends DialogFragment { 52 public interface LimitedMultiSelectDialogListener { 53 void onSelectionChanged(Set<String> selectedValues); 54 } 55 56 private static final String ARG_ENTRIES = "entries"; 57 private static final String ARG_ENTRY_VALUES = "entryValues"; 58 private static final String ARG_SELECTED_VALUES = "selectedValues"; 59 60 private WeakReference<LimitedMultiSelectDialogListener> mListener = null; 61 62 // Public no-args constructor needed for fragment re-instantiation 63 public LimitedMultiSelectDialogFragment() {} 64 /** 65 * Populates the arguments on a {@link LimitedMultiSelectDialogFragment}. 66 */ 67 protected static void populateArguments(final LimitedMultiSelectDialogFragment fragment, 68 final ArrayList<String> entries, final ArrayList<String> entryValues, 69 final ArrayList<String> selectedValues) { 70 final Bundle args = new Bundle(3); 71 args.putStringArrayList(ARG_ENTRIES, entries); 72 args.putStringArrayList(ARG_ENTRY_VALUES, entryValues); 73 args.putStringArrayList(ARG_SELECTED_VALUES, selectedValues); 74 fragment.setArguments(args); 75 } 76 77 @Override 78 public Dialog onCreateDialog(final Bundle savedInstanceState) { 79 final List<String> selectedValuesList = 80 getArguments().getStringArrayList(ARG_SELECTED_VALUES); 81 final Set<String> selectedValues = Sets.newHashSet(selectedValuesList); 82 83 final List<String> entryValues = getArguments().getStringArrayList(ARG_ENTRY_VALUES); 84 85 final LimitedMultiSelectAdapter adapter = new LimitedMultiSelectAdapter( 86 getActivity(), getArguments().getStringArrayList(ARG_ENTRIES), entryValues, 87 getMaxSelectedValues()); 88 adapter.setSelected(selectedValues); 89 90 final ListView listView = new ListView(getActivity()); 91 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 92 listView.setAdapter(adapter); 93 listView.setOnItemClickListener(new OnItemClickListener() { 94 @Override 95 public void onItemClick( 96 final AdapterView<?> parent, final View view, final int position, 97 final long id) { 98 final String entryValue = (String) parent.getItemAtPosition(position); 99 100 if (selectedValues.contains(entryValue)) { 101 // Remove / uncheck 102 selectedValues.remove(entryValue); 103 adapter.removeSelected(entryValue); 104 } else { 105 // Add / check 106 selectedValues.add(entryValue); 107 adapter.addSelected(entryValue); 108 } 109 110 getArguments().putStringArrayList(ARG_SELECTED_VALUES, 111 new ArrayList<String>(selectedValues)); 112 113 adapter.notifyDataSetChanged(); 114 } 115 }); 116 117 // Set initial check states 118 for (final String value : selectedValues) { 119 for (int j = 0; j < entryValues.size(); j++) { 120 if (entryValues.get(j).equals(value)) { 121 listView.setItemChecked(j, true); 122 } 123 } 124 } 125 126 return new AlertDialog.Builder(getActivity()).setTitle(getDialogTitle()) 127 .setView(listView) 128 .setPositiveButton(R.string.ok, new OnClickListener() { 129 @Override 130 public void onClick(final DialogInterface dialog, final int which) { 131 if (mListener != null) { 132 final LimitedMultiSelectDialogListener listener = mListener.get(); 133 if (listener != null) { 134 listener.onSelectionChanged(selectedValues); 135 } 136 } 137 } 138 }) 139 .setNegativeButton(R.string.cancel, new OnClickListener() { 140 @Override 141 public void onClick(final DialogInterface dialog, final int which) { 142 dismiss(); 143 } 144 }) 145 .create(); 146 } 147 148 public void setListener(final LimitedMultiSelectDialogListener listener) { 149 mListener = new WeakReference<LimitedMultiSelectDialogListener>(listener); 150 } 151 152 private static class LimitedMultiSelectAdapter extends BaseAdapter { 153 private final Context mContext; 154 155 private final List<String> mEntries; 156 private final List<String> mEntryValues; 157 158 private final Set<String> mSelectedValues; 159 160 private final int mMaxSelectedValues; 161 162 public LimitedMultiSelectAdapter(final Context context, final List<String> entries, 163 final List<String> entryValues, final int maxSelectedValues) { 164 mContext = context; 165 166 mEntries = ImmutableList.copyOf(entries); 167 mEntryValues = ImmutableList.copyOf(entryValues); 168 169 mSelectedValues = Sets.newHashSetWithExpectedSize(maxSelectedValues); 170 171 mMaxSelectedValues = maxSelectedValues; 172 173 if (mEntries.size() != mEntryValues.size()) { 174 throw new IllegalArgumentException("Each entry must have a corresponding value"); 175 } 176 } 177 178 @Override 179 public int getCount() { 180 return mEntries.size(); 181 } 182 183 @Override 184 public String getItem(final int position) { 185 return mEntryValues.get(position); 186 } 187 188 @Override 189 public long getItemId(final int position) { 190 return getItem(position).hashCode(); 191 } 192 193 @Override 194 public int getItemViewType(final int position) { 195 return 0; 196 } 197 198 @Override 199 public View getView(final int position, final View convertView, final ViewGroup parent) { 200 final CheckedTextView checkedTextView; 201 202 if (convertView == null) { 203 checkedTextView = (CheckedTextView) LayoutInflater.from(mContext) 204 .inflate(R.layout.select_dialog_multichoice_holo, null); 205 } else { 206 checkedTextView = (CheckedTextView) convertView; 207 } 208 209 checkedTextView.setText(mEntries.get(position)); 210 checkedTextView.setEnabled(isEnabled(position)); 211 212 return checkedTextView; 213 } 214 215 @Override 216 public int getViewTypeCount() { 217 return 1; 218 } 219 220 @Override 221 public boolean hasStableIds() { 222 return true; 223 } 224 225 @Override 226 public boolean isEmpty() { 227 return mEntries.isEmpty(); 228 } 229 230 @Override 231 public boolean areAllItemsEnabled() { 232 // If we have less than the maximum number selected, everything is 233 // enabled 234 if (mMaxSelectedValues > mSelectedValues.size()) { 235 return true; 236 } 237 238 return false; 239 } 240 241 @Override 242 public boolean isEnabled(final int position) { 243 // If we have less than the maximum selected, everything is enabled 244 if (mMaxSelectedValues > mSelectedValues.size()) { 245 return true; 246 } 247 248 // If we have the maximum selected, only the selected rows are 249 // enabled 250 if (mSelectedValues.contains(getItem(position))) { 251 return true; 252 } 253 254 return false; 255 } 256 257 public void setSelected(final Collection<String> selectedValues) { 258 mSelectedValues.clear(); 259 mSelectedValues.addAll(selectedValues); 260 notifyDataSetChanged(); 261 } 262 263 public void addSelected(final String selectedValue) { 264 mSelectedValues.add(selectedValue); 265 notifyDataSetChanged(); 266 } 267 268 public void removeSelected(final String selectedValue) { 269 mSelectedValues.remove(selectedValue); 270 notifyDataSetChanged(); 271 } 272 } 273 274 /** 275 * Gets a unique String to be used as a tag for this Fragment. 276 */ 277 protected abstract String getFragmentTag(); 278 279 /** 280 * Gets the maximum number of values that may be selected. 281 */ 282 protected abstract int getMaxSelectedValues(); 283 284 /** 285 * Gets the title of the dialog. 286 */ 287 protected abstract String getDialogTitle(); 288} 289