mobile/android/base/widget/DoorHanger.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko.widget;
michael@0 7
michael@0 8 import org.mozilla.gecko.R;
michael@0 9 import org.mozilla.gecko.Tabs;
michael@0 10 import org.mozilla.gecko.prompts.PromptInput;
michael@0 11
michael@0 12 import org.json.JSONArray;
michael@0 13 import org.json.JSONException;
michael@0 14 import org.json.JSONObject;
michael@0 15
michael@0 16 import android.content.Context;
michael@0 17 import android.content.res.Resources;
michael@0 18 import android.graphics.Rect;
michael@0 19 import android.os.Build;
michael@0 20 import android.text.SpannableString;
michael@0 21 import android.text.TextUtils;
michael@0 22 import android.text.method.LinkMovementMethod;
michael@0 23 import android.text.style.ForegroundColorSpan;
michael@0 24 import android.text.style.URLSpan;
michael@0 25 import android.util.Log;
michael@0 26 import android.view.LayoutInflater;
michael@0 27 import android.view.View;
michael@0 28 import android.view.ViewGroup;
michael@0 29 import android.widget.Button;
michael@0 30 import android.widget.CheckBox;
michael@0 31 import android.widget.ImageView;
michael@0 32 import android.widget.LinearLayout;
michael@0 33 import android.widget.Spinner;
michael@0 34 import android.widget.SpinnerAdapter;
michael@0 35 import android.widget.TextView;
michael@0 36
michael@0 37 import java.util.ArrayList;
michael@0 38 import java.util.List;
michael@0 39
michael@0 40 public class DoorHanger extends LinearLayout {
michael@0 41 private static final String LOGTAG = "GeckoDoorHanger";
michael@0 42
michael@0 43 private static int sInputPadding = -1;
michael@0 44 private static int sSpinnerTextColor = -1;
michael@0 45 private static int sSpinnerTextSize = -1;
michael@0 46
michael@0 47 private static LayoutParams sButtonParams;
michael@0 48 static {
michael@0 49 sButtonParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT, 1.0f);
michael@0 50 }
michael@0 51
michael@0 52 private final TextView mTextView;
michael@0 53 private final ImageView mIcon;
michael@0 54 private final LinearLayout mChoicesLayout;
michael@0 55
michael@0 56 // Divider between doorhangers.
michael@0 57 private final View mDivider;
michael@0 58
michael@0 59 // The tab associated with this notification.
michael@0 60 private final int mTabId;
michael@0 61
michael@0 62 // Value used to identify the notification.
michael@0 63 private final String mValue;
michael@0 64
michael@0 65 private Resources mResources;
michael@0 66
michael@0 67 private List<PromptInput> mInputs;
michael@0 68 private CheckBox mCheckBox;
michael@0 69
michael@0 70 private int mPersistence = 0;
michael@0 71 private boolean mPersistWhileVisible = false;
michael@0 72 private long mTimeout = 0;
michael@0 73
michael@0 74 // Color used for dividers above and between buttons.
michael@0 75 private int mDividerColor;
michael@0 76
michael@0 77 public static enum Theme {
michael@0 78 LIGHT,
michael@0 79 DARK
michael@0 80 }
michael@0 81
michael@0 82 public interface OnButtonClickListener {
michael@0 83 public void onButtonClick(DoorHanger dh, String tag);
michael@0 84 }
michael@0 85
michael@0 86 public DoorHanger(Context context, Theme theme) {
michael@0 87 this(context, 0, null, theme);
michael@0 88 }
michael@0 89
michael@0 90 public DoorHanger(Context context, int tabId, String value) {
michael@0 91 this(context, tabId, value, Theme.LIGHT);
michael@0 92 }
michael@0 93
michael@0 94 private DoorHanger(Context context, int tabId, String value, Theme theme) {
michael@0 95 super(context);
michael@0 96
michael@0 97 mTabId = tabId;
michael@0 98 mValue = value;
michael@0 99 mResources = getResources();
michael@0 100
michael@0 101 if (sInputPadding == -1) {
michael@0 102 sInputPadding = mResources.getDimensionPixelSize(R.dimen.doorhanger_padding);
michael@0 103 }
michael@0 104 if (sSpinnerTextColor == -1) {
michael@0 105 sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only);
michael@0 106 }
michael@0 107 if (sSpinnerTextSize == -1) {
michael@0 108 sSpinnerTextSize = mResources.getDimensionPixelSize(R.dimen.doorhanger_spinner_textsize);
michael@0 109 }
michael@0 110
michael@0 111 setOrientation(VERTICAL);
michael@0 112
michael@0 113 LayoutInflater.from(context).inflate(R.layout.doorhanger, this);
michael@0 114 mTextView = (TextView) findViewById(R.id.doorhanger_title);
michael@0 115 mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
michael@0 116 mChoicesLayout = (LinearLayout) findViewById(R.id.doorhanger_choices);
michael@0 117 mDivider = findViewById(R.id.divider_doorhanger);
michael@0 118
michael@0 119 setTheme(theme);
michael@0 120 }
michael@0 121
michael@0 122 private void setTheme(Theme theme) {
michael@0 123 if (theme == Theme.LIGHT) {
michael@0 124 // The default styles declared in doorhanger.xml are light-themed, so we just
michael@0 125 // need to set the divider color that we'll use in addButton.
michael@0 126 mDividerColor = mResources.getColor(R.color.doorhanger_divider_light);
michael@0 127
michael@0 128 } else if (theme == Theme.DARK) {
michael@0 129 mDividerColor = mResources.getColor(R.color.doorhanger_divider_dark);
michael@0 130
michael@0 131 // Set a dark background, and use a smaller text size for dark-themed DoorHangers.
michael@0 132 setBackgroundColor(mResources.getColor(R.color.doorhanger_background_dark));
michael@0 133 mTextView.setTextSize(mResources.getDimension(R.dimen.doorhanger_textsize_small));
michael@0 134 }
michael@0 135 }
michael@0 136
michael@0 137 public int getTabId() {
michael@0 138 return mTabId;
michael@0 139 }
michael@0 140
michael@0 141 public String getValue() {
michael@0 142 return mValue;
michael@0 143 }
michael@0 144
michael@0 145 public List<PromptInput> getInputs() {
michael@0 146 return mInputs;
michael@0 147 }
michael@0 148
michael@0 149 public CheckBox getCheckBox() {
michael@0 150 return mCheckBox;
michael@0 151 }
michael@0 152
michael@0 153 public void showDivider() {
michael@0 154 mDivider.setVisibility(View.VISIBLE);
michael@0 155 }
michael@0 156
michael@0 157 public void hideDivider() {
michael@0 158 mDivider.setVisibility(View.GONE);
michael@0 159 }
michael@0 160
michael@0 161 public void setMessage(String message) {
michael@0 162 mTextView.setText(message);
michael@0 163 }
michael@0 164
michael@0 165 public void setIcon(int resId) {
michael@0 166 mIcon.setImageResource(resId);
michael@0 167 mIcon.setVisibility(View.VISIBLE);
michael@0 168 }
michael@0 169
michael@0 170 public void addLink(String label, String url, String delimiter) {
michael@0 171 String title = mTextView.getText().toString();
michael@0 172 SpannableString titleWithLink = new SpannableString(title + delimiter + label);
michael@0 173 URLSpan linkSpan = new URLSpan(url) {
michael@0 174 @Override
michael@0 175 public void onClick(View view) {
michael@0 176 Tabs.getInstance().loadUrlInTab(getURL());
michael@0 177 }
michael@0 178 };
michael@0 179
michael@0 180 // Prevent text outside the link from flashing when clicked.
michael@0 181 ForegroundColorSpan colorSpan = new ForegroundColorSpan(mTextView.getCurrentTextColor());
michael@0 182 titleWithLink.setSpan(colorSpan, 0, title.length(), 0);
michael@0 183
michael@0 184 titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0);
michael@0 185 mTextView.setText(titleWithLink);
michael@0 186 mTextView.setMovementMethod(LinkMovementMethod.getInstance());
michael@0 187 }
michael@0 188
michael@0 189 public void addButton(final String text, final String tag, final OnButtonClickListener listener) {
michael@0 190 final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
michael@0 191 button.setText(text);
michael@0 192 button.setTag(tag);
michael@0 193
michael@0 194 button.setOnClickListener(new Button.OnClickListener() {
michael@0 195 @Override
michael@0 196 public void onClick(View v) {
michael@0 197 listener.onButtonClick(DoorHanger.this, tag);
michael@0 198 }
michael@0 199 });
michael@0 200
michael@0 201 if (mChoicesLayout.getChildCount() == 0) {
michael@0 202 // If this is the first button we're adding, make the choices layout visible.
michael@0 203 mChoicesLayout.setVisibility(View.VISIBLE);
michael@0 204 // Make the divider above the buttons visible.
michael@0 205 View divider = findViewById(R.id.divider_choices);
michael@0 206 divider.setVisibility(View.VISIBLE);
michael@0 207 divider.setBackgroundColor(mDividerColor);
michael@0 208 } else {
michael@0 209 // Add a vertical divider between additional buttons.
michael@0 210 Divider divider = new Divider(getContext(), null);
michael@0 211 divider.setOrientation(Divider.Orientation.VERTICAL);
michael@0 212 divider.setBackgroundColor(mDividerColor);
michael@0 213 mChoicesLayout.addView(divider);
michael@0 214 }
michael@0 215
michael@0 216 mChoicesLayout.addView(button, sButtonParams);
michael@0 217 }
michael@0 218
michael@0 219 public void setOptions(final JSONObject options) {
michael@0 220 final int persistence = options.optInt("persistence");
michael@0 221 if (persistence > 0) {
michael@0 222 mPersistence = persistence;
michael@0 223 }
michael@0 224
michael@0 225 mPersistWhileVisible = options.optBoolean("persistWhileVisible");
michael@0 226
michael@0 227 final long timeout = options.optLong("timeout");
michael@0 228 if (timeout > 0) {
michael@0 229 mTimeout = timeout;
michael@0 230 }
michael@0 231
michael@0 232 final JSONObject link = options.optJSONObject("link");
michael@0 233 if (link != null) {
michael@0 234 try {
michael@0 235 final String linkLabel = link.getString("label");
michael@0 236 final String linkUrl = link.getString("url");
michael@0 237 addLink(linkLabel, linkUrl, " ");
michael@0 238 } catch (JSONException e) { }
michael@0 239 }
michael@0 240
michael@0 241 final JSONArray inputs = options.optJSONArray("inputs");
michael@0 242 if (inputs != null) {
michael@0 243 mInputs = new ArrayList<PromptInput>();
michael@0 244
michael@0 245 final ViewGroup group = (ViewGroup) findViewById(R.id.doorhanger_inputs);
michael@0 246 group.setVisibility(VISIBLE);
michael@0 247
michael@0 248 for (int i = 0; i < inputs.length(); i++) {
michael@0 249 try {
michael@0 250 PromptInput input = PromptInput.getInput(inputs.getJSONObject(i));
michael@0 251 mInputs.add(input);
michael@0 252
michael@0 253 View v = input.getView(getContext());
michael@0 254 styleInput(input, v);
michael@0 255 group.addView(v);
michael@0 256 } catch(JSONException ex) { }
michael@0 257 }
michael@0 258 }
michael@0 259
michael@0 260 final String checkBoxText = options.optString("checkbox");
michael@0 261 if (!TextUtils.isEmpty(checkBoxText)) {
michael@0 262 mCheckBox = (CheckBox) findViewById(R.id.doorhanger_checkbox);
michael@0 263 mCheckBox.setText(checkBoxText);
michael@0 264 mCheckBox.setVisibility(VISIBLE);
michael@0 265 }
michael@0 266 }
michael@0 267
michael@0 268 private void styleInput(PromptInput input, View view) {
michael@0 269 if (input instanceof PromptInput.MenulistInput) {
michael@0 270 styleSpinner(input, view);
michael@0 271 } else {
michael@0 272 // add some top and bottom padding to separate inputs
michael@0 273 view.setPadding(0, sInputPadding,
michael@0 274 0, sInputPadding);
michael@0 275 }
michael@0 276 }
michael@0 277
michael@0 278 private void styleSpinner(PromptInput input, View view) {
michael@0 279 PromptInput.MenulistInput spinInput = (PromptInput.MenulistInput) input;
michael@0 280
michael@0 281 /* Spinners have some intrinsic padding. To force the spinner's text to line up with
michael@0 282 * the doorhanger text, we have to take that padding into account.
michael@0 283 *
michael@0 284 * |-----A-------| <-- Normal doorhanger message
michael@0 285 * |-B-|---C+D---| <-- (optional) Spinner Label
michael@0 286 * |-B-|-C-|--D--| <-- Spinner
michael@0 287 *
michael@0 288 * A - Desired padding (sInputPadding)
michael@0 289 * B - Final padding applied to input element (sInputPadding - rect.left - textPadding).
michael@0 290 * C - Spinner background drawable padding (rect.left).
michael@0 291 * D - Spinner inner TextView padding (textPadding).
michael@0 292 */
michael@0 293
michael@0 294 // First get the padding of the selected view inside the spinner. Since the spinner
michael@0 295 // hasn't been shown yet, we get this view directly from the adapter.
michael@0 296 Spinner spinner = spinInput.spinner;
michael@0 297 SpinnerAdapter adapter = spinner.getAdapter();
michael@0 298 View dropView = adapter.getView(0, null, spinner);
michael@0 299 int textPadding = 0;
michael@0 300 if (dropView != null) {
michael@0 301 textPadding = dropView.getPaddingLeft();
michael@0 302 }
michael@0 303
michael@0 304 // Then get the intrinsic padding built into the background image of the spinner.
michael@0 305 Rect rect = new Rect();
michael@0 306 spinner.getBackground().getPadding(rect);
michael@0 307
michael@0 308 // Set the difference in padding to the spinner view to align it with doorhanger text.
michael@0 309 view.setPadding(sInputPadding - rect.left - textPadding, 0, rect.right, sInputPadding);
michael@0 310
michael@0 311 if (spinInput.textView != null) {
michael@0 312 spinInput.textView.setTextColor(sSpinnerTextColor);
michael@0 313 spinInput.textView.setTextSize(sSpinnerTextSize);
michael@0 314
michael@0 315 // If this spinner has a label, offset it to also be aligned with the doorhanger text.
michael@0 316 spinInput.textView.setPadding(rect.left + textPadding, 0, 0, 0);
michael@0 317 }
michael@0 318 }
michael@0 319
michael@0 320
michael@0 321 /*
michael@0 322 * Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
michael@0 323 *
michael@0 324 * @param isShowing Whether or not this doorhanger is currently visible to the user.
michael@0 325 * (e.g. the DoorHanger view might be VISIBLE, but its parent could be hidden)
michael@0 326 */
michael@0 327 public boolean shouldRemove(boolean isShowing) {
michael@0 328 if (mPersistWhileVisible && isShowing) {
michael@0 329 // We still want to decrement mPersistence, even if the popup is showing
michael@0 330 if (mPersistence != 0)
michael@0 331 mPersistence--;
michael@0 332 return false;
michael@0 333 }
michael@0 334
michael@0 335 // If persistence is set to -1, the doorhanger will never be
michael@0 336 // automatically removed.
michael@0 337 if (mPersistence != 0) {
michael@0 338 mPersistence--;
michael@0 339 return false;
michael@0 340 }
michael@0 341
michael@0 342 if (System.currentTimeMillis() <= mTimeout) {
michael@0 343 return false;
michael@0 344 }
michael@0 345
michael@0 346 return true;
michael@0 347 }
michael@0 348 }

mercurial