OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.enhancedbookmarks; |
| 6 |
| 7 import android.animation.Animator; |
| 8 import android.animation.AnimatorListenerAdapter; |
| 9 import android.animation.ObjectAnimator; |
| 10 import android.annotation.TargetApi; |
| 11 import android.content.Context; |
| 12 import android.graphics.Bitmap; |
| 13 import android.os.Build; |
| 14 import android.os.Bundle; |
| 15 import android.text.Editable; |
| 16 import android.text.TextWatcher; |
| 17 import android.transition.Fade; |
| 18 import android.transition.Transition; |
| 19 import android.view.View; |
| 20 import android.view.ViewGroup; |
| 21 import android.view.inputmethod.InputMethodManager; |
| 22 import android.widget.Button; |
| 23 import android.widget.EditText; |
| 24 import android.widget.ImageButton; |
| 25 import android.widget.ImageView; |
| 26 import android.widget.LinearLayout; |
| 27 import android.widget.RelativeLayout; |
| 28 import android.widget.TextView; |
| 29 |
| 30 import com.google.android.apps.chrome.R; |
| 31 |
| 32 import org.chromium.chrome.browser.BookmarksBridge.BookmarkItem; |
| 33 import org.chromium.chrome.browser.BookmarksBridge.BookmarkModelObserver; |
| 34 import org.chromium.chrome.browser.UrlUtilities; |
| 35 import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksBridge.Fi
ltersObserver; |
| 36 import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksBridge.Sa
lientImageCallback; |
| 37 import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksModel; |
| 38 import org.chromium.chrome.browser.enhancedbookmarks.EnhancedBookmarkDetailScrol
lView.OnScrollListener; |
| 39 import org.chromium.chrome.browser.widget.FadingShadow; |
| 40 import org.chromium.chrome.browser.widget.FadingShadowView; |
| 41 import org.chromium.chrome.browser.widget.FlowLayout; |
| 42 import org.chromium.components.bookmarks.BookmarkId; |
| 43 import org.chromium.components.bookmarks.BookmarkType; |
| 44 import org.chromium.ui.base.DeviceFormFactor; |
| 45 |
| 46 /** |
| 47 * Dialog to show details of the selected bookmark. It has two modes: read-only
mode and editing |
| 48 * mode. Clicking on a textview will make the dialog animate to editing mode. On
a handset this |
| 49 * dialog will be full-screen; on tablet it will be a fixed-size dialog popping
up in the middle of |
| 50 * the screen. |
| 51 */ |
| 52 public class EnhancedBookmarkDetailActivity extends EnhancedBookmarkActivityBase
implements |
| 53 View.OnClickListener, OnScrollListener, FiltersObserver { |
| 54 public static final String INTENT_BOOKMARK_ID = "EnhancedBookmarkDetailActiv
ity.BookmarkId"; |
| 55 private static final int ANIMATION_DURATION_MS = 300; |
| 56 |
| 57 private EnhancedBookmarksModel mEnhancedBookmarksModel; |
| 58 private BookmarkId mBookmarkId; |
| 59 |
| 60 private EnhancedBookmarkDetailScrollView mScrollView; |
| 61 private LinearLayout mContentLayout; |
| 62 private ImageView mImageView; |
| 63 private EditText mTitleEditText; |
| 64 private EditText mUrlEditText; |
| 65 private View mFolderBox; |
| 66 private TextView mFolderTextView; |
| 67 private TextView mAutoFoldersLabel; |
| 68 private FlowLayout mAutoFoldersFlowLayout; |
| 69 private EditText mDescriptionEditText; |
| 70 private View mMaskView; |
| 71 private RelativeLayout mActionBarLayout; |
| 72 private ImageButton mCloseButton; |
| 73 private ImageButton mDeleteButton; |
| 74 private ImageButton mSaveButton; |
| 75 private FadingShadowView mImageShadowMask; |
| 76 private FadingShadowView mShadow; |
| 77 private View mBottomSpacer; |
| 78 private EditText[] mEditTexts; |
| 79 |
| 80 private Animator mCurrentAnimator; |
| 81 private int mDominantColor; |
| 82 private boolean mIsEditingMode; |
| 83 |
| 84 private BookmarkModelObserver mBookmarkModelObserver = new BookmarkModelObse
rver() { |
| 85 @Override |
| 86 public void bookmarkNodeRemoved(BookmarkItem parent, int oldIndex, Bookm
arkItem node, |
| 87 boolean isDoingExtensiveChanges) { |
| 88 if (mBookmarkId.equals(node.getId())) { |
| 89 dismiss(); |
| 90 } |
| 91 } |
| 92 |
| 93 @Override |
| 94 public void bookmarkNodeMoved(BookmarkItem oldParent, int oldIndex, Book
markItem newParent, |
| 95 int newIndex) { |
| 96 BookmarkId movedBookmark = mEnhancedBookmarksModel.getChildAt(newPar
ent.getId(), |
| 97 newIndex); |
| 98 if (movedBookmark.equals(mBookmarkId)) { |
| 99 setParentFolderName(newParent.getId()); |
| 100 } |
| 101 } |
| 102 |
| 103 @Override |
| 104 public void bookmarkNodeChanged(BookmarkItem node) { |
| 105 if (mBookmarkId.equals(node.getId())) updateViews(node); |
| 106 } |
| 107 |
| 108 @Override |
| 109 public void bookmarkModelChanged() { |
| 110 } |
| 111 }; |
| 112 |
| 113 @Override |
| 114 public void onCreate(Bundle savedInstanceState) { |
| 115 super.onCreate(savedInstanceState); |
| 116 EnhancedBookmarkUtils.setTaskDescriptionInDocumentMode(this, |
| 117 getString(R.string.accessibility_enhanced_bookmark_detail_view))
; |
| 118 mEnhancedBookmarksModel = new EnhancedBookmarksModel(); |
| 119 mBookmarkId = BookmarkId.getBookmarkIdFromString( |
| 120 getIntent().getStringExtra(INTENT_BOOKMARK_ID)); |
| 121 mEnhancedBookmarksModel.addModelObserver(mBookmarkModelObserver); |
| 122 mEnhancedBookmarksModel.addFiltersObserver(this); |
| 123 setContentView(R.layout.eb_detail); |
| 124 mScrollView = (EnhancedBookmarkDetailScrollView) findViewById( |
| 125 R.id.eb_detail_scroll_view); |
| 126 mContentLayout = (LinearLayout) findViewById(R.id.eb_detail_content); |
| 127 mImageView = (ImageView) findViewById(R.id.eb_detail_image_view); |
| 128 mTitleEditText = (EditText) findViewById(R.id.eb_detail_title); |
| 129 mUrlEditText = (EditText) findViewById(R.id.eb_detail_url); |
| 130 mFolderBox = findViewById(R.id.eb_detail_folder_box); |
| 131 mFolderTextView = (TextView) findViewById(R.id.eb_detail_folder_textview
); |
| 132 mAutoFoldersLabel = (TextView) findViewById(R.id.eb_detail_auto_folder_l
abel); |
| 133 mAutoFoldersFlowLayout = (FlowLayout) findViewById(R.id.eb_detail_flow_l
ayout); |
| 134 mDescriptionEditText = (EditText) findViewById(R.id.eb_detail_descriptio
n); |
| 135 mMaskView = findViewById(R.id.eb_detail_image_mask); |
| 136 mActionBarLayout = (RelativeLayout) findViewById(R.id.eb_detail_action_b
ar); |
| 137 mSaveButton = (ImageButton) findViewById(R.id.eb_detail_actionbar_save_b
utton); |
| 138 mDeleteButton = (ImageButton) findViewById(R.id.eb_detail_actionbar_dele
te_button); |
| 139 mCloseButton = (ImageButton) findViewById(R.id.eb_detail_actionbar_close
_button); |
| 140 mImageShadowMask = (FadingShadowView) findViewById(R.id.eb_detail_top_sh
adow); |
| 141 mShadow = (FadingShadowView) findViewById(R.id.eb_detail_shadow); |
| 142 mBottomSpacer = mScrollView.findViewById(R.id.bottom_spacer); |
| 143 mEditTexts = new EditText[]{mTitleEditText, mUrlEditText, mDescriptionEd
itText}; |
| 144 |
| 145 // Listen to click event of EditTexts while setting them not focusable i
n order to |
| 146 // postpone showing soft keyboard until animations are finished. |
| 147 for (EditText editText : mEditTexts) editText.setOnClickListener(this); |
| 148 clearErrorWhenNonEmpty(mEditTexts); |
| 149 int shadowColor = getResources().getColor( |
| 150 R.color.enhanced_bookmark_detail_dialog_shadow_color); |
| 151 mShadow.init(shadowColor, FadingShadow.POSITION_TOP); |
| 152 mImageShadowMask.init(shadowColor, FadingShadow.POSITION_TOP); |
| 153 mImageShadowMask.setStrength(1.0f); |
| 154 |
| 155 mSaveButton.setOnClickListener(this); |
| 156 mDeleteButton.setOnClickListener(this); |
| 157 mCloseButton.setOnClickListener(this); |
| 158 mFolderBox.setOnClickListener(this); |
| 159 |
| 160 mScrollView.setOnScrollListener(this); |
| 161 |
| 162 updateViews(); |
| 163 updateAutoFolders(); |
| 164 setUpSharedElementAnimation(); |
| 165 } |
| 166 |
| 167 @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| 168 private void setUpSharedElementAnimation() { |
| 169 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; |
| 170 Transition t = new Fade(); |
| 171 // Exlude status bar and navigation bar, to work around an Android bug t
hat white background |
| 172 // activity will cause these two bars blink/flash during content transit
ion. |
| 173 t.excludeTarget(android.R.id.statusBarBackground, true); |
| 174 t.excludeTarget(android.R.id.navigationBarBackground, true); |
| 175 getWindow().setEnterTransition(t); |
| 176 getWindow().setExitTransition(t); |
| 177 } |
| 178 |
| 179 /** |
| 180 * Custom {@link android.app.Activity#finish()} that checks device version.
If in Lollipop or |
| 181 * future releases, it enables finish with backward shared element animation
. |
| 182 */ |
| 183 @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| 184 private void dismiss() { |
| 185 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) finish(); |
| 186 else finishAfterTransition(); |
| 187 } |
| 188 |
| 189 @Override |
| 190 public void onBackPressed() { |
| 191 onClick(mCloseButton); |
| 192 } |
| 193 |
| 194 private void updateViews() { |
| 195 BookmarkItem bookmarkItem = mEnhancedBookmarksModel.getBookmarkById(mBoo
kmarkId); |
| 196 updateViews(bookmarkItem); |
| 197 } |
| 198 |
| 199 /** |
| 200 * Updates each EditText to display the data in bookmarkItem. This function
will be called every |
| 201 * time user cancels editing. |
| 202 */ |
| 203 private void updateViews(BookmarkItem bookmarkItem) { |
| 204 mTitleEditText.setText(bookmarkItem.getTitle()); |
| 205 mUrlEditText.setText(bookmarkItem.getUrl()); |
| 206 setParentFolderName(bookmarkItem.getParentId()); |
| 207 |
| 208 if (bookmarkItem.getId().getType() == BookmarkType.PARTNER) { |
| 209 mUrlEditText.setEnabled(false); |
| 210 mDescriptionEditText.setVisibility(View.GONE); |
| 211 } else { |
| 212 mUrlEditText.setEnabled(true); |
| 213 mDescriptionEditText.setText( |
| 214 mEnhancedBookmarksModel.getBookmarkDescription(mBookmarkId))
; |
| 215 mDescriptionEditText.setVisibility(View.VISIBLE); |
| 216 } |
| 217 |
| 218 mDominantColor = EnhancedBookmarkUtils.generateBackgroundColor(bookmarkI
tem); |
| 219 mImageView.setBackgroundColor(mDominantColor); |
| 220 mMaskView.setBackgroundColor(mDominantColor); |
| 221 mEnhancedBookmarksModel.salientImageForUrl(bookmarkItem.getUrl(), |
| 222 new SalientImageCallback() { |
| 223 @Override |
| 224 public void onSalientImageReady(Bitmap bitmap, String imageU
rl) { |
| 225 if (bitmap == null) return; |
| 226 mImageView.setImageBitmap(bitmap); |
| 227 mDominantColor = EnhancedBookmarkUtils.getDominantColorF
orBitmap(bitmap); |
| 228 mImageView.setBackgroundColor(mDominantColor); |
| 229 mMaskView.setBackgroundColor(mDominantColor); |
| 230 } |
| 231 }); |
| 232 mMaskView.setAlpha(0.0f); |
| 233 } |
| 234 |
| 235 private void updateAutoFolders() { |
| 236 mAutoFoldersFlowLayout.removeAllViews(); |
| 237 String[] filters = mEnhancedBookmarksModel.getFiltersForBookmark(mBookma
rkId); |
| 238 for (String filter : filters) { |
| 239 Button autoFolder = new AutoFolderButton(this); |
| 240 autoFolder.setText(filter); |
| 241 mAutoFoldersFlowLayout.addView(autoFolder); |
| 242 } |
| 243 if (mAutoFoldersFlowLayout.getChildCount() > 0) { |
| 244 mAutoFoldersLabel.setVisibility(View.VISIBLE); |
| 245 mAutoFoldersFlowLayout.setVisibility(View.VISIBLE); |
| 246 } else { |
| 247 mAutoFoldersLabel.setVisibility(View.GONE); |
| 248 mAutoFoldersFlowLayout.setVisibility(View.GONE); |
| 249 } |
| 250 } |
| 251 |
| 252 private void setParentFolderName(BookmarkId parentId) { |
| 253 mFolderTextView.setText(mEnhancedBookmarksModel.getBookmarkTitle(parentI
d)); |
| 254 mFolderTextView.setEnabled(parentId.getType() != BookmarkType.PARTNER); |
| 255 } |
| 256 |
| 257 @Override |
| 258 public void onClick(View v) { |
| 259 // During animation, do not respond to any clicking events. |
| 260 if (mCurrentAnimator != null) return; |
| 261 if (v == mCloseButton) { |
| 262 if (mIsEditingMode) leaveEditingMode(false); |
| 263 else dismiss(); |
| 264 } else if (v == mSaveButton) { |
| 265 if (mIsEditingMode) leaveEditingMode(true); |
| 266 else dismiss(); |
| 267 } else if (v instanceof EditText) { |
| 268 if (!mIsEditingMode) enterEditingMode((EditText) v); |
| 269 } else if (v == mFolderBox) { |
| 270 EnhancedBookmarkFolderSelectActivity.startFolderSelectActivity(this,
mBookmarkId); |
| 271 } else if (v == mDeleteButton) { |
| 272 mEnhancedBookmarksModel.deleteBookmarks(mBookmarkId); |
| 273 dismiss(); |
| 274 } |
| 275 } |
| 276 |
| 277 /** |
| 278 * Entering editing mode will trigger scrolling-up animation, as an effort t
o smoothly place the |
| 279 * focused EditText to be the best editable position to user. After animatin
g, corresponding |
| 280 * EditText will be given a simulated touch event that shows up soft keyboar
d and allow user to |
| 281 * edit. |
| 282 * <p> |
| 283 * To ensure the view can always be scrolled up to enter editing mode, befor
e animation: 1). If |
| 284 * content is shorter than screen height, then we fill the content view by e
mptyAreaHeight so as |
| 285 * to align the content and scroll view. 2). Calculate the scrolling amount.
3). If the |
| 286 * scrolling amount is larger than the current maximum scrollable amount, in
crease height of |
| 287 * mBottomSpacer to make the content long enough. 4).trigger scroll-up anima
tion. |
| 288 */ |
| 289 private void enterEditingMode(final EditText editText) { |
| 290 if (mIsEditingMode) return; |
| 291 mIsEditingMode = true; |
| 292 |
| 293 if (DeviceFormFactor.isTablet(this)) { |
| 294 // On tablet this the size of the dialog is controlled by framework.
To avoid any |
| 295 // jumpy behavior, we skip the crazy scrolling effect below. |
| 296 animateScrolling(mScrollView.getScrollY(), editText); |
| 297 } else { |
| 298 int scrollAmount = mScrollView.getHeightCompensation() + editText.ge
tTop() |
| 299 - mTitleEditText.getTop(); |
| 300 |
| 301 // If there is not enough space to scroll, create padding at bottom. |
| 302 if (scrollAmount > mScrollView.getMaximumScrollY()) { |
| 303 // First try to align content view and scroll view. |
| 304 int emptyAreaHeight = mScrollView.getHeight() - mContentLayout.g
etHeight(); |
| 305 if (emptyAreaHeight < 0) emptyAreaHeight = 0; |
| 306 // Then increase height of bottom spacer to create margin for sc
roll. |
| 307 setViewHeight(mBottomSpacer, |
| 308 scrollAmount - mScrollView.getMaximumScrollY() + emptyAr
eaHeight); |
| 309 } |
| 310 |
| 311 animateScrolling(scrollAmount, editText); |
| 312 } |
| 313 } |
| 314 |
| 315 private void animateScrolling(int scrollAmount, final EditText editText) { |
| 316 ObjectAnimator animator = ObjectAnimator.ofInt(mScrollView, "scrollY", s
crollAmount); |
| 317 animator.setDuration(scrollAmount == mScrollView.getScrollY() ? 0 : ANIM
ATION_DURATION_MS); |
| 318 animator.addListener(new AnimatorListenerAdapter() { |
| 319 @Override |
| 320 public void onAnimationEnd(Animator animation) { |
| 321 for (EditText text : mEditTexts) text.setFocusableInTouchMode(tr
ue); |
| 322 editText.requestFocus(); |
| 323 InputMethodManager imm = (InputMethodManager) getSystemService( |
| 324 Context.INPUT_METHOD_SERVICE); |
| 325 imm.showSoftInput(editText, 0); |
| 326 mCurrentAnimator = null; |
| 327 } |
| 328 }); |
| 329 mCurrentAnimator = animator; |
| 330 animator.start(); |
| 331 } |
| 332 |
| 333 /** |
| 334 * Leaves editing mode and finishes the activity. If shouldSave is set false
, all changes will |
| 335 * be reverted. |
| 336 */ |
| 337 private void leaveEditingMode(boolean shouldSave) { |
| 338 assert mIsEditingMode; |
| 339 |
| 340 String newTitle = mTitleEditText.getText().toString().trim(); |
| 341 String newUrl = mUrlEditText.getText().toString().trim(); |
| 342 if (shouldSave) { |
| 343 boolean urlOrTitleInvalid = false; |
| 344 // Fix user input urls, if necessary. |
| 345 newUrl = UrlUtilities.fixupUrl(newUrl); |
| 346 if (newUrl == null) newUrl = ""; |
| 347 mUrlEditText.setText(newUrl); |
| 348 if (newUrl.isEmpty()) { |
| 349 mUrlEditText.setError(getString(R.string.bookmark_missing_url)); |
| 350 urlOrTitleInvalid = true; |
| 351 } |
| 352 if (newTitle.isEmpty()) { |
| 353 mTitleEditText.setError(getString(R.string.bookmark_missing_titl
e)); |
| 354 urlOrTitleInvalid = true; |
| 355 } |
| 356 if (urlOrTitleInvalid) return; |
| 357 } |
| 358 |
| 359 mIsEditingMode = false; |
| 360 |
| 361 if (shouldSave) { |
| 362 BookmarkItem bookmarkItem = mEnhancedBookmarksModel.getBookmarkById(
mBookmarkId); |
| 363 String newDescription = mDescriptionEditText.getText().toString().tr
im(); |
| 364 if (!bookmarkItem.getTitle().equals(newTitle)) { |
| 365 mEnhancedBookmarksModel.setBookmarkTitle(mBookmarkId, newTitle); |
| 366 } |
| 367 if (!bookmarkItem.getUrl().equals(newUrl) |
| 368 && bookmarkItem.getId().getType() != BookmarkType.PARTNER) { |
| 369 mEnhancedBookmarksModel.setBookmarkUrl(mBookmarkId, newUrl); |
| 370 } |
| 371 if (bookmarkItem.getId().getType() != BookmarkType.PARTNER) { |
| 372 mEnhancedBookmarksModel.setBookmarkDescription(mBookmarkId, newD
escription); |
| 373 } |
| 374 } else { |
| 375 // If user discards change, restore textviews to original values. |
| 376 updateViews(); |
| 377 } |
| 378 |
| 379 mContentLayout.requestFocus(); |
| 380 InputMethodManager imm = (InputMethodManager) getSystemService( |
| 381 Context.INPUT_METHOD_SERVICE); |
| 382 imm.hideSoftInputFromWindow(mTitleEditText.getWindowToken(), 0); |
| 383 // Cancel error state of message |
| 384 mUrlEditText.setError(null); |
| 385 mTitleEditText.setError(null); |
| 386 // Reset all EditTexts to be not focosable to postpone showing keyboard |
| 387 for (EditText editText : mEditTexts) editText.setFocusable(false); |
| 388 mCurrentAnimator = null; |
| 389 if (mBottomSpacer.getHeight() != 0) setViewHeight(mBottomSpacer, 0); |
| 390 dismiss(); |
| 391 } |
| 392 |
| 393 @Override |
| 394 public void onScrollChanged(int y, int oldY) { |
| 395 int offset = mScrollView.getHeightCompensation(); |
| 396 if (y < offset) { |
| 397 mMaskView.setAlpha(y / (float) offset); |
| 398 mActionBarLayout.setBackgroundResource(0); |
| 399 mShadow.setStrength(0.0f); |
| 400 } else { |
| 401 mMaskView.setAlpha(1.0f); |
| 402 mActionBarLayout.setBackgroundColor(mDominantColor); |
| 403 mShadow.setStrength(1.0f); |
| 404 } |
| 405 |
| 406 } |
| 407 |
| 408 /** |
| 409 * Adds a listener to EditTexts that clears the TextView's error once the us
er types something. |
| 410 */ |
| 411 private void clearErrorWhenNonEmpty(TextView... textViews) { |
| 412 for (final TextView textView : textViews) { |
| 413 textView.addTextChangedListener(new TextWatcher() { |
| 414 @Override |
| 415 public void beforeTextChanged(CharSequence s, int start, int cou
nt, int after) { |
| 416 } |
| 417 @Override |
| 418 public void onTextChanged(CharSequence s, int start, int before,
int count) { |
| 419 } |
| 420 @Override |
| 421 public void afterTextChanged(Editable s) { |
| 422 if (s.length() != 0 && textView.getError() != null) textView
.setError(null); |
| 423 } |
| 424 }); |
| 425 } |
| 426 } |
| 427 |
| 428 @Override |
| 429 public void onDestroy() { |
| 430 mEnhancedBookmarksModel.removeModelObserver(mBookmarkModelObserver); |
| 431 mEnhancedBookmarksModel.destroy(); |
| 432 mEnhancedBookmarksModel = null; |
| 433 super.onDestroy(); |
| 434 } |
| 435 |
| 436 @Override |
| 437 public void onFiltersChanged() { |
| 438 updateAutoFolders(); |
| 439 } |
| 440 |
| 441 private static void setViewHeight(View view, int height) { |
| 442 ViewGroup.LayoutParams lp = view.getLayoutParams(); |
| 443 lp.height = height; |
| 444 view.setLayoutParams(lp); |
| 445 } |
| 446 |
| 447 /** |
| 448 * Rounded button for auto folders in bookmark detail page. |
| 449 */ |
| 450 private static class AutoFolderButton extends Button { |
| 451 |
| 452 private static final int TEXT_SIZE_SP = 14; |
| 453 |
| 454 public AutoFolderButton(Context context) { |
| 455 super(context); |
| 456 setTextSize(TEXT_SIZE_SP); |
| 457 // Force auto folder buttons smaller than android default size. |
| 458 setMinHeight(0); |
| 459 setMinimumHeight(0); |
| 460 setMinWidth(0); |
| 461 setMinimumWidth(0); |
| 462 setBackgroundResource(R.drawable.btn_enhanced_bookmark_auto_folder); |
| 463 } |
| 464 } |
| 465 } |
OLD | NEW |