Home | History | Annotate | Download | only in ui
      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 
     18 package com.android.mail.ui;
     19 
     20 import com.android.mail.R;
     21 import com.google.common.collect.ImmutableList;
     22 import com.google.common.collect.Sets;
     23 
     24 import android.app.AlertDialog;
     25 import android.app.Dialog;
     26 import android.app.DialogFragment;
     27 import android.content.Context;
     28 import android.content.DialogInterface;
     29 import android.content.DialogInterface.OnClickListener;
     30 import android.os.Bundle;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.AdapterView;
     35 import android.widget.AdapterView.OnItemClickListener;
     36 import android.widget.BaseAdapter;
     37 import android.widget.CheckedTextView;
     38 import android.widget.ListView;
     39 
     40 
     41 import java.lang.ref.WeakReference;
     42 import java.util.ArrayList;
     43 import java.util.Collection;
     44 import java.util.List;
     45 import 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  */
     51 public 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