chromium/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditActivity.java

// Copyright 2015 The Chromium Authors
// 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.app.bookmarks;

import android.content.res.Resources;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.annotation.VisibleForTesting;
import androidx.appcompat.widget.Toolbar;

import org.chromium.base.Log;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.SnackbarActivity;
import org.chromium.chrome.browser.bookmarks.BookmarkImageFetcher;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.BookmarkModelObserver;
import org.chromium.chrome.browser.bookmarks.BookmarkMoveSnackbarManager;
import org.chromium.chrome.browser.bookmarks.BookmarkTextInputLayout;
import org.chromium.chrome.browser.bookmarks.BookmarkUiPrefs;
import org.chromium.chrome.browser.bookmarks.BookmarkUiPrefs.BookmarkRowDisplayPref;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.bookmarks.ImprovedBookmarkRow;
import org.chromium.chrome.browser.bookmarks.ImprovedBookmarkRowCoordinator;
import org.chromium.chrome.browser.bookmarks.ImprovedBookmarkRowProperties;
import org.chromium.chrome.browser.bookmarks.ImprovedBookmarkRowProperties.ImageVisibility;
import org.chromium.chrome.browser.bookmarks.ImprovedBookmarkRowViewBinder;
import org.chromium.chrome.browser.commerce.ShoppingServiceFactory;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkItem;
import org.chromium.components.browser_ui.widget.TintedDrawable;
import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.components.image_fetcher.ImageFetcherConfig;
import org.chromium.components.image_fetcher.ImageFetcherFactory;
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.url.GURL;

/** The activity that enables the user to modify the title, url and parent folder of a bookmark. */
// TODO(crbug.com/40269559): Separate the activity from its view.
// TODO(crbug.com/40269559): Add a coordinator/mediator for business logic.
public class BookmarkEditActivity extends SnackbarActivity {
    /** The intent extra specifying the ID of the bookmark to be edited. */
    public static final String INTENT_BOOKMARK_ID = "BookmarkEditActivity.BookmarkId";

    /** The code when starting the folder move activity for a result. */
    static final int MOVE_REQUEST_CODE = 15;

    private static final String TAG = "BookmarkEdit";

    private final SelectionDelegate mEmptySelectionDelegate = new SelectionDelegate();

    private ImprovedBookmarkRowCoordinator mFolderSelectRowCoordinator;
    private BookmarkModel mModel;
    private BookmarkId mBookmarkId;
    private boolean mInFolderSelect;
    private BookmarkTextInputLayout mTitleEditText;
    private BookmarkTextInputLayout mUrlEditText;
    private MenuItem mDeleteButton;
    private BookmarkUiPrefs mBookmarkUiPrefs;
    private FrameLayout mFolderPickerRowContainer;
    private ImprovedBookmarkRow mFolderSelectRow;
    private PropertyModel mFolderSelectRowModel;
    private BookmarkMoveSnackbarManager mBookmarkMoveSnackbarManager;

    private BookmarkUiPrefs.Observer mBookmarkUiPrefsObserver =
            new BookmarkUiPrefs.Observer() {
                @Override
                public void onBookmarkRowDisplayPrefChanged(
                        @BookmarkRowDisplayPref int displayPref) {
                    updateFolderPickerRow(displayPref);
                }
            };

    private BookmarkModelObserver mBookmarkModelObserver =
            new BookmarkModelObserver() {
                @Override
                public void bookmarkModelChanged() {
                    if (mModel.doesBookmarkExist(mBookmarkId)) {
                        updateViewContent(true);
                    } else if (!mInFolderSelect) {
                        // This happens either when the user clicks delete button or partner
                        // bookmark is removed in background.
                        finish();
                    }
                }
            };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Profile profile = getProfileProvider().getOriginalProfile();
        mModel = BookmarkModel.getForProfile(profile);
        mBookmarkId =
                BookmarkId.getBookmarkIdFromString(getIntent().getStringExtra(INTENT_BOOKMARK_ID));
        mModel.addObserver(mBookmarkModelObserver);
        BookmarkItem item = mModel.getBookmarkById(mBookmarkId);
        if (!mModel.doesBookmarkExist(mBookmarkId) || item == null) {
            finish();
            return;
        }

        mBookmarkMoveSnackbarManager =
                new BookmarkMoveSnackbarManager(
                        /* context= */ this,
                        mModel,
                        getSnackbarManager(),
                        IdentityServicesProvider.get().getIdentityManager(profile));
        setContentView(R.layout.bookmark_edit);
        mTitleEditText = findViewById(R.id.title_text);
        mUrlEditText = findViewById(R.id.url_text);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        View shadow = findViewById(R.id.shadow);
        View scrollView = findViewById(R.id.scroll_view);
        scrollView
                .getViewTreeObserver()
                .addOnScrollChangedListener(
                        () -> {
                            shadow.setVisibility(
                                    scrollView.getScrollY() > 0 ? View.VISIBLE : View.GONE);
                        });

        boolean isFolder = item.isFolder();
        TextView folderTitle = findViewById(R.id.folder_title);
        folderTitle.setText(isFolder ? R.string.bookmark_parent_folder : R.string.bookmark_folder);
        mUrlEditText.setVisibility(isFolder ? View.GONE : View.VISIBLE);
        getSupportActionBar().setTitle(isFolder ? R.string.edit_folder : R.string.edit_bookmark);
        mBookmarkUiPrefs = new BookmarkUiPrefs(ChromeSharedPreferences.getInstance());
        mBookmarkUiPrefs.addObserver(mBookmarkUiPrefsObserver);

        Resources res = getResources();
        mFolderSelectRowCoordinator =
                new ImprovedBookmarkRowCoordinator(
                        this,
                        new BookmarkImageFetcher(
                                profile,
                                this,
                                mModel,
                                ImageFetcherFactory.createImageFetcher(
                                        ImageFetcherConfig.DISK_CACHE_ONLY,
                                        profile.getProfileKey()),
                                new LargeIconBridge(profile),
                                BookmarkUtils.getRoundedIconGenerator(
                                        this, BookmarkRowDisplayPref.VISUAL),
                                BookmarkUtils.getImageIconSize(res, BookmarkRowDisplayPref.VISUAL),
                                BookmarkUtils.getFaviconDisplaySize(res)),
                        mModel,
                        mBookmarkUiPrefs,
                        ShoppingServiceFactory.getForProfile(profile));

        mFolderPickerRowContainer = findViewById(R.id.folder_row_container);

        updateViewContent(false);
    }

    /**
     * @param modelChanged Whether this view update is due to a model change in background.
     */
    private void updateViewContent(boolean modelChanged) {
        BookmarkItem bookmarkItem = mModel.getBookmarkById(mBookmarkId);
        // While the user is editing the bookmark, do not override user's input.
        if (!modelChanged) {
            mTitleEditText.getEditText().setText(bookmarkItem.getTitle());
            mUrlEditText.getEditText().setText(bookmarkItem.getUrl().getSpec());
        }
        mTitleEditText.setEnabled(bookmarkItem.isEditable());
        mUrlEditText.setEnabled(bookmarkItem.isUrlEditable());
        updateFolderPickerRow(mBookmarkUiPrefs.getBookmarkRowDisplayPref());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        mDeleteButton =
                menu.add(R.string.bookmark_toolbar_delete)
                        .setIcon(
                                TintedDrawable.constructTintedDrawable(
                                        this, R.drawable.ic_delete_white_24dp))
                        .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item == mDeleteButton) {
            // Log added for detecting delete button double clicking.
            Log.i(TAG, "Delete button pressed by user! isFinishing() == " + isFinishing());

            mModel.deleteBookmark(mBookmarkId);
            finish();
            return true;
        } else if (item.getItemId() == android.R.id.home) {
            finish();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onStop() {
        if (mModel.doesBookmarkExist(mBookmarkId)) {
            final GURL originalUrl = mModel.getBookmarkById(mBookmarkId).getUrl();
            final String title = mTitleEditText.getTrimmedText();
            final String url = mUrlEditText.getTrimmedText();

            if (!mTitleEditText.isEmpty()) {
                mModel.setBookmarkTitle(mBookmarkId, title);
            }

            if (!mUrlEditText.isEmpty() && mModel.getBookmarkById(mBookmarkId).isUrlEditable()) {
                GURL fixedUrl = UrlFormatter.fixupUrl(url);
                if (fixedUrl.isValid() && !fixedUrl.equals(originalUrl)) {
                    mModel.setBookmarkUrl(mBookmarkId, fixedUrl);
                }
            }
        }

        super.onStop();
    }

    @Override
    protected void onDestroy() {
        mModel.removeObserver(mBookmarkModelObserver);
        if (mBookmarkUiPrefs != null) {
            mBookmarkUiPrefs.removeObserver(mBookmarkUiPrefsObserver);
        }
        if (mBookmarkMoveSnackbarManager != null) {
            mBookmarkMoveSnackbarManager.destroy();
        }
        super.onDestroy();
    }

    @VisibleForTesting
    BookmarkTextInputLayout getTitleEditText() {
        return mTitleEditText;
    }

    @VisibleForTesting
    BookmarkTextInputLayout getUrlEditText() {
        return mUrlEditText;
    }

    @VisibleForTesting
    MenuItem getDeleteButton() {
        return mDeleteButton;
    }

    private void updateFolderPickerRow(@BookmarkRowDisplayPref int displayPref) {
        BookmarkItem bookmarkItem = mModel.getBookmarkById(mBookmarkId);
        mFolderSelectRowModel =
                mFolderSelectRowCoordinator.createBasePropertyModel(bookmarkItem.getParentId());

        mFolderSelectRowModel.set(
                ImprovedBookmarkRowProperties.END_IMAGE_RES, R.drawable.outline_chevron_right_24dp);
        mFolderSelectRowModel.set(
                ImprovedBookmarkRowProperties.END_IMAGE_VISIBILITY, ImageVisibility.DRAWABLE);
        mFolderSelectRowModel.set(
                ImprovedBookmarkRowProperties.ROW_CLICK_LISTENER,
                () -> mBookmarkMoveSnackbarManager.startFolderPickerAndObserveResult(mBookmarkId));

        mFolderSelectRow =
                ImprovedBookmarkRow.buildView(this, displayPref == BookmarkRowDisplayPref.VISUAL);
        PropertyModelChangeProcessor.create(
                mFolderSelectRowModel, mFolderSelectRow, ImprovedBookmarkRowViewBinder::bind);

        mFolderPickerRowContainer.removeAllViews();
        mFolderPickerRowContainer.addView(mFolderSelectRow);
    }

    View getFolderSelectRowForTesting() {
        return mFolderSelectRow;
    }

    PropertyModel getFolderSelectRowPropertyModelForTesting() {
        return mFolderSelectRowModel;
    }
}