Index: chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkDetailActivity.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkDetailActivity.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkDetailActivity.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c08b30a306668d6b5556f14e124b4a73a0e8aef5 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkDetailActivity.java |
@@ -0,0 +1,465 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.enhancedbookmarks; |
+ |
+import android.animation.Animator; |
+import android.animation.AnimatorListenerAdapter; |
+import android.animation.ObjectAnimator; |
+import android.annotation.TargetApi; |
+import android.content.Context; |
+import android.graphics.Bitmap; |
+import android.os.Build; |
+import android.os.Bundle; |
+import android.text.Editable; |
+import android.text.TextWatcher; |
+import android.transition.Fade; |
+import android.transition.Transition; |
+import android.view.View; |
+import android.view.ViewGroup; |
+import android.view.inputmethod.InputMethodManager; |
+import android.widget.Button; |
+import android.widget.EditText; |
+import android.widget.ImageButton; |
+import android.widget.ImageView; |
+import android.widget.LinearLayout; |
+import android.widget.RelativeLayout; |
+import android.widget.TextView; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import org.chromium.chrome.browser.BookmarksBridge.BookmarkItem; |
+import org.chromium.chrome.browser.BookmarksBridge.BookmarkModelObserver; |
+import org.chromium.chrome.browser.UrlUtilities; |
+import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksBridge.FiltersObserver; |
+import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksBridge.SalientImageCallback; |
+import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksModel; |
+import org.chromium.chrome.browser.enhancedbookmarks.EnhancedBookmarkDetailScrollView.OnScrollListener; |
+import org.chromium.chrome.browser.widget.FadingShadow; |
+import org.chromium.chrome.browser.widget.FadingShadowView; |
+import org.chromium.chrome.browser.widget.FlowLayout; |
+import org.chromium.components.bookmarks.BookmarkId; |
+import org.chromium.components.bookmarks.BookmarkType; |
+import org.chromium.ui.base.DeviceFormFactor; |
+ |
+/** |
+ * Dialog to show details of the selected bookmark. It has two modes: read-only mode and editing |
+ * mode. Clicking on a textview will make the dialog animate to editing mode. On a handset this |
+ * dialog will be full-screen; on tablet it will be a fixed-size dialog popping up in the middle of |
+ * the screen. |
+ */ |
+public class EnhancedBookmarkDetailActivity extends EnhancedBookmarkActivityBase implements |
+ View.OnClickListener, OnScrollListener, FiltersObserver { |
+ public static final String INTENT_BOOKMARK_ID = "EnhancedBookmarkDetailActivity.BookmarkId"; |
+ private static final int ANIMATION_DURATION_MS = 300; |
+ |
+ private EnhancedBookmarksModel mEnhancedBookmarksModel; |
+ private BookmarkId mBookmarkId; |
+ |
+ private EnhancedBookmarkDetailScrollView mScrollView; |
+ private LinearLayout mContentLayout; |
+ private ImageView mImageView; |
+ private EditText mTitleEditText; |
+ private EditText mUrlEditText; |
+ private View mFolderBox; |
+ private TextView mFolderTextView; |
+ private TextView mAutoFoldersLabel; |
+ private FlowLayout mAutoFoldersFlowLayout; |
+ private EditText mDescriptionEditText; |
+ private View mMaskView; |
+ private RelativeLayout mActionBarLayout; |
+ private ImageButton mCloseButton; |
+ private ImageButton mDeleteButton; |
+ private ImageButton mSaveButton; |
+ private FadingShadowView mImageShadowMask; |
+ private FadingShadowView mShadow; |
+ private View mBottomSpacer; |
+ private EditText[] mEditTexts; |
+ |
+ private Animator mCurrentAnimator; |
+ private int mDominantColor; |
+ private boolean mIsEditingMode; |
+ |
+ private BookmarkModelObserver mBookmarkModelObserver = new BookmarkModelObserver() { |
+ @Override |
+ public void bookmarkNodeRemoved(BookmarkItem parent, int oldIndex, BookmarkItem node, |
+ boolean isDoingExtensiveChanges) { |
+ if (mBookmarkId.equals(node.getId())) { |
+ dismiss(); |
+ } |
+ } |
+ |
+ @Override |
+ public void bookmarkNodeMoved(BookmarkItem oldParent, int oldIndex, BookmarkItem newParent, |
+ int newIndex) { |
+ BookmarkId movedBookmark = mEnhancedBookmarksModel.getChildAt(newParent.getId(), |
+ newIndex); |
+ if (movedBookmark.equals(mBookmarkId)) { |
+ setParentFolderName(newParent.getId()); |
+ } |
+ } |
+ |
+ @Override |
+ public void bookmarkNodeChanged(BookmarkItem node) { |
+ if (mBookmarkId.equals(node.getId())) updateViews(node); |
+ } |
+ |
+ @Override |
+ public void bookmarkModelChanged() { |
+ } |
+ }; |
+ |
+ @Override |
+ public void onCreate(Bundle savedInstanceState) { |
+ super.onCreate(savedInstanceState); |
+ EnhancedBookmarkUtils.setTaskDescriptionInDocumentMode(this, |
+ getString(R.string.accessibility_enhanced_bookmark_detail_view)); |
+ mEnhancedBookmarksModel = new EnhancedBookmarksModel(); |
+ mBookmarkId = BookmarkId.getBookmarkIdFromString( |
+ getIntent().getStringExtra(INTENT_BOOKMARK_ID)); |
+ mEnhancedBookmarksModel.addModelObserver(mBookmarkModelObserver); |
+ mEnhancedBookmarksModel.addFiltersObserver(this); |
+ setContentView(R.layout.eb_detail); |
+ mScrollView = (EnhancedBookmarkDetailScrollView) findViewById( |
+ R.id.eb_detail_scroll_view); |
+ mContentLayout = (LinearLayout) findViewById(R.id.eb_detail_content); |
+ mImageView = (ImageView) findViewById(R.id.eb_detail_image_view); |
+ mTitleEditText = (EditText) findViewById(R.id.eb_detail_title); |
+ mUrlEditText = (EditText) findViewById(R.id.eb_detail_url); |
+ mFolderBox = findViewById(R.id.eb_detail_folder_box); |
+ mFolderTextView = (TextView) findViewById(R.id.eb_detail_folder_textview); |
+ mAutoFoldersLabel = (TextView) findViewById(R.id.eb_detail_auto_folder_label); |
+ mAutoFoldersFlowLayout = (FlowLayout) findViewById(R.id.eb_detail_flow_layout); |
+ mDescriptionEditText = (EditText) findViewById(R.id.eb_detail_description); |
+ mMaskView = findViewById(R.id.eb_detail_image_mask); |
+ mActionBarLayout = (RelativeLayout) findViewById(R.id.eb_detail_action_bar); |
+ mSaveButton = (ImageButton) findViewById(R.id.eb_detail_actionbar_save_button); |
+ mDeleteButton = (ImageButton) findViewById(R.id.eb_detail_actionbar_delete_button); |
+ mCloseButton = (ImageButton) findViewById(R.id.eb_detail_actionbar_close_button); |
+ mImageShadowMask = (FadingShadowView) findViewById(R.id.eb_detail_top_shadow); |
+ mShadow = (FadingShadowView) findViewById(R.id.eb_detail_shadow); |
+ mBottomSpacer = mScrollView.findViewById(R.id.bottom_spacer); |
+ mEditTexts = new EditText[]{mTitleEditText, mUrlEditText, mDescriptionEditText}; |
+ |
+ // Listen to click event of EditTexts while setting them not focusable in order to |
+ // postpone showing soft keyboard until animations are finished. |
+ for (EditText editText : mEditTexts) editText.setOnClickListener(this); |
+ clearErrorWhenNonEmpty(mEditTexts); |
+ int shadowColor = getResources().getColor( |
+ R.color.enhanced_bookmark_detail_dialog_shadow_color); |
+ mShadow.init(shadowColor, FadingShadow.POSITION_TOP); |
+ mImageShadowMask.init(shadowColor, FadingShadow.POSITION_TOP); |
+ mImageShadowMask.setStrength(1.0f); |
+ |
+ mSaveButton.setOnClickListener(this); |
+ mDeleteButton.setOnClickListener(this); |
+ mCloseButton.setOnClickListener(this); |
+ mFolderBox.setOnClickListener(this); |
+ |
+ mScrollView.setOnScrollListener(this); |
+ |
+ updateViews(); |
+ updateAutoFolders(); |
+ setUpSharedElementAnimation(); |
+ } |
+ |
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
+ private void setUpSharedElementAnimation() { |
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; |
+ Transition t = new Fade(); |
+ // Exlude status bar and navigation bar, to work around an Android bug that white background |
+ // activity will cause these two bars blink/flash during content transition. |
+ t.excludeTarget(android.R.id.statusBarBackground, true); |
+ t.excludeTarget(android.R.id.navigationBarBackground, true); |
+ getWindow().setEnterTransition(t); |
+ getWindow().setExitTransition(t); |
+ } |
+ |
+ /** |
+ * Custom {@link android.app.Activity#finish()} that checks device version. If in Lollipop or |
+ * future releases, it enables finish with backward shared element animation. |
+ */ |
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
+ private void dismiss() { |
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) finish(); |
+ else finishAfterTransition(); |
+ } |
+ |
+ @Override |
+ public void onBackPressed() { |
+ onClick(mCloseButton); |
+ } |
+ |
+ private void updateViews() { |
+ BookmarkItem bookmarkItem = mEnhancedBookmarksModel.getBookmarkById(mBookmarkId); |
+ updateViews(bookmarkItem); |
+ } |
+ |
+ /** |
+ * Updates each EditText to display the data in bookmarkItem. This function will be called every |
+ * time user cancels editing. |
+ */ |
+ private void updateViews(BookmarkItem bookmarkItem) { |
+ mTitleEditText.setText(bookmarkItem.getTitle()); |
+ mUrlEditText.setText(bookmarkItem.getUrl()); |
+ setParentFolderName(bookmarkItem.getParentId()); |
+ |
+ if (bookmarkItem.getId().getType() == BookmarkType.PARTNER) { |
+ mUrlEditText.setEnabled(false); |
+ mDescriptionEditText.setVisibility(View.GONE); |
+ } else { |
+ mUrlEditText.setEnabled(true); |
+ mDescriptionEditText.setText( |
+ mEnhancedBookmarksModel.getBookmarkDescription(mBookmarkId)); |
+ mDescriptionEditText.setVisibility(View.VISIBLE); |
+ } |
+ |
+ mDominantColor = EnhancedBookmarkUtils.generateBackgroundColor(bookmarkItem); |
+ mImageView.setBackgroundColor(mDominantColor); |
+ mMaskView.setBackgroundColor(mDominantColor); |
+ mEnhancedBookmarksModel.salientImageForUrl(bookmarkItem.getUrl(), |
+ new SalientImageCallback() { |
+ @Override |
+ public void onSalientImageReady(Bitmap bitmap, String imageUrl) { |
+ if (bitmap == null) return; |
+ mImageView.setImageBitmap(bitmap); |
+ mDominantColor = EnhancedBookmarkUtils.getDominantColorForBitmap(bitmap); |
+ mImageView.setBackgroundColor(mDominantColor); |
+ mMaskView.setBackgroundColor(mDominantColor); |
+ } |
+ }); |
+ mMaskView.setAlpha(0.0f); |
+ } |
+ |
+ private void updateAutoFolders() { |
+ mAutoFoldersFlowLayout.removeAllViews(); |
+ String[] filters = mEnhancedBookmarksModel.getFiltersForBookmark(mBookmarkId); |
+ for (String filter : filters) { |
+ Button autoFolder = new AutoFolderButton(this); |
+ autoFolder.setText(filter); |
+ mAutoFoldersFlowLayout.addView(autoFolder); |
+ } |
+ if (mAutoFoldersFlowLayout.getChildCount() > 0) { |
+ mAutoFoldersLabel.setVisibility(View.VISIBLE); |
+ mAutoFoldersFlowLayout.setVisibility(View.VISIBLE); |
+ } else { |
+ mAutoFoldersLabel.setVisibility(View.GONE); |
+ mAutoFoldersFlowLayout.setVisibility(View.GONE); |
+ } |
+ } |
+ |
+ private void setParentFolderName(BookmarkId parentId) { |
+ mFolderTextView.setText(mEnhancedBookmarksModel.getBookmarkTitle(parentId)); |
+ mFolderTextView.setEnabled(parentId.getType() != BookmarkType.PARTNER); |
+ } |
+ |
+ @Override |
+ public void onClick(View v) { |
+ // During animation, do not respond to any clicking events. |
+ if (mCurrentAnimator != null) return; |
+ if (v == mCloseButton) { |
+ if (mIsEditingMode) leaveEditingMode(false); |
+ else dismiss(); |
+ } else if (v == mSaveButton) { |
+ if (mIsEditingMode) leaveEditingMode(true); |
+ else dismiss(); |
+ } else if (v instanceof EditText) { |
+ if (!mIsEditingMode) enterEditingMode((EditText) v); |
+ } else if (v == mFolderBox) { |
+ EnhancedBookmarkFolderSelectActivity.startFolderSelectActivity(this, mBookmarkId); |
+ } else if (v == mDeleteButton) { |
+ mEnhancedBookmarksModel.deleteBookmarks(mBookmarkId); |
+ dismiss(); |
+ } |
+ } |
+ |
+ /** |
+ * Entering editing mode will trigger scrolling-up animation, as an effort to smoothly place the |
+ * focused EditText to be the best editable position to user. After animating, corresponding |
+ * EditText will be given a simulated touch event that shows up soft keyboard and allow user to |
+ * edit. |
+ * <p> |
+ * To ensure the view can always be scrolled up to enter editing mode, before animation: 1). If |
+ * content is shorter than screen height, then we fill the content view by emptyAreaHeight so as |
+ * to align the content and scroll view. 2). Calculate the scrolling amount. 3). If the |
+ * scrolling amount is larger than the current maximum scrollable amount, increase height of |
+ * mBottomSpacer to make the content long enough. 4).trigger scroll-up animation. |
+ */ |
+ private void enterEditingMode(final EditText editText) { |
+ if (mIsEditingMode) return; |
+ mIsEditingMode = true; |
+ |
+ if (DeviceFormFactor.isTablet(this)) { |
+ // On tablet this the size of the dialog is controlled by framework. To avoid any |
+ // jumpy behavior, we skip the crazy scrolling effect below. |
+ animateScrolling(mScrollView.getScrollY(), editText); |
+ } else { |
+ int scrollAmount = mScrollView.getHeightCompensation() + editText.getTop() |
+ - mTitleEditText.getTop(); |
+ |
+ // If there is not enough space to scroll, create padding at bottom. |
+ if (scrollAmount > mScrollView.getMaximumScrollY()) { |
+ // First try to align content view and scroll view. |
+ int emptyAreaHeight = mScrollView.getHeight() - mContentLayout.getHeight(); |
+ if (emptyAreaHeight < 0) emptyAreaHeight = 0; |
+ // Then increase height of bottom spacer to create margin for scroll. |
+ setViewHeight(mBottomSpacer, |
+ scrollAmount - mScrollView.getMaximumScrollY() + emptyAreaHeight); |
+ } |
+ |
+ animateScrolling(scrollAmount, editText); |
+ } |
+ } |
+ |
+ private void animateScrolling(int scrollAmount, final EditText editText) { |
+ ObjectAnimator animator = ObjectAnimator.ofInt(mScrollView, "scrollY", scrollAmount); |
+ animator.setDuration(scrollAmount == mScrollView.getScrollY() ? 0 : ANIMATION_DURATION_MS); |
+ animator.addListener(new AnimatorListenerAdapter() { |
+ @Override |
+ public void onAnimationEnd(Animator animation) { |
+ for (EditText text : mEditTexts) text.setFocusableInTouchMode(true); |
+ editText.requestFocus(); |
+ InputMethodManager imm = (InputMethodManager) getSystemService( |
+ Context.INPUT_METHOD_SERVICE); |
+ imm.showSoftInput(editText, 0); |
+ mCurrentAnimator = null; |
+ } |
+ }); |
+ mCurrentAnimator = animator; |
+ animator.start(); |
+ } |
+ |
+ /** |
+ * Leaves editing mode and finishes the activity. If shouldSave is set false, all changes will |
+ * be reverted. |
+ */ |
+ private void leaveEditingMode(boolean shouldSave) { |
+ assert mIsEditingMode; |
+ |
+ String newTitle = mTitleEditText.getText().toString().trim(); |
+ String newUrl = mUrlEditText.getText().toString().trim(); |
+ if (shouldSave) { |
+ boolean urlOrTitleInvalid = false; |
+ // Fix user input urls, if necessary. |
+ newUrl = UrlUtilities.fixupUrl(newUrl); |
+ if (newUrl == null) newUrl = ""; |
+ mUrlEditText.setText(newUrl); |
+ if (newUrl.isEmpty()) { |
+ mUrlEditText.setError(getString(R.string.bookmark_missing_url)); |
+ urlOrTitleInvalid = true; |
+ } |
+ if (newTitle.isEmpty()) { |
+ mTitleEditText.setError(getString(R.string.bookmark_missing_title)); |
+ urlOrTitleInvalid = true; |
+ } |
+ if (urlOrTitleInvalid) return; |
+ } |
+ |
+ mIsEditingMode = false; |
+ |
+ if (shouldSave) { |
+ BookmarkItem bookmarkItem = mEnhancedBookmarksModel.getBookmarkById(mBookmarkId); |
+ String newDescription = mDescriptionEditText.getText().toString().trim(); |
+ if (!bookmarkItem.getTitle().equals(newTitle)) { |
+ mEnhancedBookmarksModel.setBookmarkTitle(mBookmarkId, newTitle); |
+ } |
+ if (!bookmarkItem.getUrl().equals(newUrl) |
+ && bookmarkItem.getId().getType() != BookmarkType.PARTNER) { |
+ mEnhancedBookmarksModel.setBookmarkUrl(mBookmarkId, newUrl); |
+ } |
+ if (bookmarkItem.getId().getType() != BookmarkType.PARTNER) { |
+ mEnhancedBookmarksModel.setBookmarkDescription(mBookmarkId, newDescription); |
+ } |
+ } else { |
+ // If user discards change, restore textviews to original values. |
+ updateViews(); |
+ } |
+ |
+ mContentLayout.requestFocus(); |
+ InputMethodManager imm = (InputMethodManager) getSystemService( |
+ Context.INPUT_METHOD_SERVICE); |
+ imm.hideSoftInputFromWindow(mTitleEditText.getWindowToken(), 0); |
+ // Cancel error state of message |
+ mUrlEditText.setError(null); |
+ mTitleEditText.setError(null); |
+ // Reset all EditTexts to be not focosable to postpone showing keyboard |
+ for (EditText editText : mEditTexts) editText.setFocusable(false); |
+ mCurrentAnimator = null; |
+ if (mBottomSpacer.getHeight() != 0) setViewHeight(mBottomSpacer, 0); |
+ dismiss(); |
+ } |
+ |
+ @Override |
+ public void onScrollChanged(int y, int oldY) { |
+ int offset = mScrollView.getHeightCompensation(); |
+ if (y < offset) { |
+ mMaskView.setAlpha(y / (float) offset); |
+ mActionBarLayout.setBackgroundResource(0); |
+ mShadow.setStrength(0.0f); |
+ } else { |
+ mMaskView.setAlpha(1.0f); |
+ mActionBarLayout.setBackgroundColor(mDominantColor); |
+ mShadow.setStrength(1.0f); |
+ } |
+ |
+ } |
+ |
+ /** |
+ * Adds a listener to EditTexts that clears the TextView's error once the user types something. |
+ */ |
+ private void clearErrorWhenNonEmpty(TextView... textViews) { |
+ for (final TextView textView : textViews) { |
+ textView.addTextChangedListener(new TextWatcher() { |
+ @Override |
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
+ } |
+ @Override |
+ public void onTextChanged(CharSequence s, int start, int before, int count) { |
+ } |
+ @Override |
+ public void afterTextChanged(Editable s) { |
+ if (s.length() != 0 && textView.getError() != null) textView.setError(null); |
+ } |
+ }); |
+ } |
+ } |
+ |
+ @Override |
+ public void onDestroy() { |
+ mEnhancedBookmarksModel.removeModelObserver(mBookmarkModelObserver); |
+ mEnhancedBookmarksModel.destroy(); |
+ mEnhancedBookmarksModel = null; |
+ super.onDestroy(); |
+ } |
+ |
+ @Override |
+ public void onFiltersChanged() { |
+ updateAutoFolders(); |
+ } |
+ |
+ private static void setViewHeight(View view, int height) { |
+ ViewGroup.LayoutParams lp = view.getLayoutParams(); |
+ lp.height = height; |
+ view.setLayoutParams(lp); |
+ } |
+ |
+ /** |
+ * Rounded button for auto folders in bookmark detail page. |
+ */ |
+ private static class AutoFolderButton extends Button { |
+ |
+ private static final int TEXT_SIZE_SP = 14; |
+ |
+ public AutoFolderButton(Context context) { |
+ super(context); |
+ setTextSize(TEXT_SIZE_SP); |
+ // Force auto folder buttons smaller than android default size. |
+ setMinHeight(0); |
+ setMinimumHeight(0); |
+ setMinWidth(0); |
+ setMinimumWidth(0); |
+ setBackgroundResource(R.drawable.btn_enhanced_bookmark_auto_folder); |
+ } |
+ } |
+} |