1 /* 2 * Copyright 2013 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 18 package com.example.android.batchstepsensor.cardstream; 19 20 import android.os.Bundle; 21 import android.support.v4.app.Fragment; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.ViewGroup; 25 26 import java.util.Collection; 27 import java.util.HashMap; 28 import java.util.HashSet; 29 import java.util.LinkedHashMap; 30 31 import com.example.android.batchstepsensor.R; 32 33 /** 34 * A Fragment that handles a stream of cards. 35 * Cards can be shown or hidden. When a card is shown it can also be marked as not-dismissible, see 36 * {@link CardStreamLinearLayout#addCard(android.view.View, boolean)}. 37 */ 38 public class CardStreamFragment extends Fragment { 39 40 private static final int INITIAL_SIZE = 15; 41 private CardStreamLinearLayout mLayout = null; 42 private LinkedHashMap<String, Card> mVisibleCards = new LinkedHashMap<String, Card>(INITIAL_SIZE); 43 private HashMap<String, Card> mHiddenCards = new HashMap<String, Card>(INITIAL_SIZE); 44 private HashSet<String> mDismissibleCards = new HashSet<String>(INITIAL_SIZE); 45 46 // Set the listener to handle dismissed cards by moving them to the hidden cards map. 47 private CardStreamLinearLayout.OnDissmissListener mCardDismissListener = 48 new CardStreamLinearLayout.OnDissmissListener() { 49 @Override 50 public void onDismiss(String tag) { 51 dismissCard(tag); 52 } 53 }; 54 55 56 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)57 public View onCreateView(LayoutInflater inflater, ViewGroup container, 58 Bundle savedInstanceState) { 59 60 View view = inflater.inflate(R.layout.cardstream, container, false); 61 mLayout = (CardStreamLinearLayout) view.findViewById(R.id.card_stream); 62 mLayout.setOnDismissListener(mCardDismissListener); 63 64 return view; 65 } 66 67 /** 68 * Add a visible, dismissible card to the card stream. 69 * 70 * @param card 71 */ addCard(Card card)72 public void addCard(Card card) { 73 final String tag = card.getTag(); 74 75 if (!mVisibleCards.containsKey(tag) && !mHiddenCards.containsKey(tag)) { 76 final View view = card.getView(); 77 view.setTag(tag); 78 mHiddenCards.put(tag, card); 79 } 80 } 81 82 /** 83 * Add and show a card. 84 * 85 * @param card 86 * @param show 87 */ addCard(Card card, boolean show)88 public void addCard(Card card, boolean show) { 89 addCard(card); 90 if (show) { 91 showCard(card.getTag()); 92 } 93 } 94 95 /** 96 * Remove a card and return true if it has been successfully removed. 97 * 98 * @param tag 99 * @return 100 */ removeCard(String tag)101 public boolean removeCard(String tag) { 102 // Attempt to remove a visible card first 103 Card card = mVisibleCards.get(tag); 104 if (card != null) { 105 // Card is visible, also remove from layout 106 mVisibleCards.remove(tag); 107 mLayout.removeView(card.getView()); 108 return true; 109 } else { 110 // Card is hidden, no need to remove from layout 111 card = mHiddenCards.remove(tag); 112 return card != null; 113 } 114 } 115 116 /** 117 * Show a dismissible card, returns false if the card could not be shown. 118 * 119 * @param tag 120 * @return 121 */ showCard(String tag)122 public boolean showCard(String tag) { 123 return showCard(tag, true); 124 } 125 126 /** 127 * Show a card, returns false if the card could not be shown. 128 * 129 * @param tag 130 * @param dismissible 131 * @return 132 */ showCard(String tag, boolean dismissible)133 public boolean showCard(String tag, boolean dismissible) { 134 final Card card = mHiddenCards.get(tag); 135 // ensure the card is hidden and not already visible 136 if (card != null && !mVisibleCards.containsValue(tag)) { 137 mHiddenCards.remove(tag); 138 mVisibleCards.put(tag, card); 139 mLayout.addCard(card.getView(), dismissible); 140 if (dismissible) { 141 mDismissibleCards.add(tag); 142 } 143 return true; 144 } 145 return false; 146 } 147 148 /** 149 * Hides the card, returns false if the card could not be hidden. 150 * 151 * @param tag 152 * @return 153 */ hideCard(String tag)154 public boolean hideCard(String tag) { 155 final Card card = mVisibleCards.get(tag); 156 if (card != null) { 157 mVisibleCards.remove(tag); 158 mDismissibleCards.remove(tag); 159 mHiddenCards.put(tag, card); 160 161 mLayout.removeView(card.getView()); 162 return true; 163 } 164 return mHiddenCards.containsValue(tag); 165 } 166 167 dismissCard(String tag)168 private void dismissCard(String tag) { 169 final Card card = mVisibleCards.get(tag); 170 if (card != null) { 171 mDismissibleCards.remove(tag); 172 mVisibleCards.remove(tag); 173 mHiddenCards.put(tag, card); 174 } 175 } 176 177 isCardVisible(String tag)178 public boolean isCardVisible(String tag) { 179 return mVisibleCards.containsValue(tag); 180 } 181 182 /** 183 * Returns true if the card is shown and is dismissible. 184 * 185 * @param tag 186 * @return 187 */ isCardDismissible(String tag)188 public boolean isCardDismissible(String tag) { 189 return mDismissibleCards.contains(tag); 190 } 191 192 /** 193 * Returns the Card for this tag. 194 * 195 * @param tag 196 * @return 197 */ getCard(String tag)198 public Card getCard(String tag) { 199 final Card card = mVisibleCards.get(tag); 200 if (card != null) { 201 return card; 202 } else { 203 return mHiddenCards.get(tag); 204 } 205 } 206 207 /** 208 * Moves the view port to show the card with this tag. 209 * 210 * @param tag 211 * @see CardStreamLinearLayout#setFirstVisibleCard(String) 212 */ setFirstVisibleCard(String tag)213 public void setFirstVisibleCard(String tag) { 214 final Card card = mVisibleCards.get(tag); 215 if (card != null) { 216 mLayout.setFirstVisibleCard(tag); 217 } 218 } 219 getVisibleCardCount()220 public int getVisibleCardCount() { 221 return mVisibleCards.size(); 222 } 223 getVisibleCards()224 public Collection<Card> getVisibleCards() { 225 return mVisibleCards.values(); 226 } 227 restoreState(CardStreamState state, OnCardClickListener callback)228 public void restoreState(CardStreamState state, OnCardClickListener callback) { 229 // restore hidden cards 230 for (Card c : state.hiddenCards) { 231 Card card = new Card.Builder(callback,c).build(getActivity()); 232 mHiddenCards.put(card.getTag(), card); 233 } 234 235 // temporarily set up list of dismissible 236 final HashSet<String> dismissibleCards = state.dismissibleCards; 237 238 //restore shown cards 239 for (Card c : state.visibleCards) { 240 Card card = new Card.Builder(callback,c).build(getActivity()); 241 addCard(card); 242 final String tag = card.getTag(); 243 showCard(tag, dismissibleCards.contains(tag)); 244 } 245 246 // move to first visible card 247 final String firstShown = state.shownTag; 248 if (firstShown != null) { 249 mLayout.setFirstVisibleCard(firstShown); 250 } 251 252 mLayout.triggerShowInitialAnimation(); 253 } 254 dumpState()255 public CardStreamState dumpState() { 256 final Card[] visible = cloneCards(mVisibleCards.values()); 257 final Card[] hidden = cloneCards(mHiddenCards.values()); 258 final HashSet<String> dismissible = new HashSet<String>(mDismissibleCards); 259 final String firstVisible = mLayout.getFirstVisibleCardTag(); 260 261 return new CardStreamState(visible, hidden, dismissible, firstVisible); 262 } 263 cloneCards(Collection<Card> cards)264 private Card[] cloneCards(Collection<Card> cards) { 265 Card[] cardArray = new Card[cards.size()]; 266 int i = 0; 267 for (Card c : cards) { 268 cardArray[i++] = c.createShallowClone(); 269 } 270 271 return cardArray; 272 } 273 274 } 275