Bug 1232439 - Part 2: convert edit bookmark dialog into fullscreen fragment
Rebased by Tom Klein, with review comments addressed and additional changes.
MozReview-Commit-ID: 7cuMmatZmhJ
--- a/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
+++ b/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
@@ -5,149 +5,221 @@
package org.mozilla.gecko;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UIAsyncTask;
-import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
import android.database.Cursor;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.widget.Toolbar;
import android.support.design.widget.Snackbar;
+import android.support.design.widget.TextInputLayout;
import android.text.Editable;
+import android.view.MenuItem;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.EditText;
/**
* A dialog that allows editing a bookmarks url, title, or keywords
* <p>
- * Invoked by calling one of the {@link org.mozilla.gecko.EditBookmarkDialog#show(String)}
+ * Invoked by calling one of the {@link org.mozilla.gecko.EditBookmarkDialog#show}
* methods.
*/
-public class EditBookmarkDialog {
- private final Context mContext;
+public class EditBookmarkDialog extends DialogFragment {
+ private Bookmark mBookmark;
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
- public EditBookmarkDialog(Context context) {
- mContext = context;
+ mBookmark.saveToBundle(outState);
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ mBookmark = Bookmark.newFromBundle(savedInstanceState);
+ }
}
/**
* A private struct to make it easier to pass bookmark data across threads
*/
- private class Bookmark {
+ private static class Bookmark {
+ private static final String SAVED_STATE_ID = "id";
+ private static final String SAVED_STATE_TITLE = "title";
+ private static final String SAVED_STATE_URL = "url";
+ private static final String SAVED_STATE_KEYWORD = "keyword";
+
final int id;
final String title;
final String url;
final String keyword;
public Bookmark(int aId, String aTitle, String aUrl, String aKeyword) {
id = aId;
title = aTitle;
url = aUrl;
keyword = aKeyword;
}
+
+ static Bookmark newFromBundle(Bundle bundle) {
+ return new Bookmark(bundle.getInt(SAVED_STATE_ID),
+ bundle.getString(SAVED_STATE_TITLE),
+ bundle.getString(SAVED_STATE_URL),
+ bundle.getString(SAVED_STATE_KEYWORD));
+ }
+
+ void saveToBundle(Bundle bundle) {
+ bundle.putInt(SAVED_STATE_ID, id);
+ bundle.putString(SAVED_STATE_TITLE, title);
+ bundle.putString(SAVED_STATE_URL, url);
+ bundle.putString(SAVED_STATE_KEYWORD, keyword);
+ }
}
- /**
- * This text watcher to enable or disable the OK button if the dialog contains
- * valid information. This class is overridden to do data checking on different fields.
- * By itself, it always enables the button.
- *
- * Callers can also assign a paired partner to the TextWatcher, and callers will check
- * that both are enabled before enabling the ok button.
- */
- private class EditBookmarkTextWatcher implements TextWatcher {
- // A stored reference to the dialog containing the text field being watched
- protected AlertDialog mDialog;
+ private class BookmarkUriWatcher implements TextWatcher {
+ private TextInputLayout textInputLayout;
- // A stored text watcher to do the real verification of a field
- protected EditBookmarkTextWatcher mPairedTextWatcher;
-
- // Whether or not the ok button should be enabled.
- protected boolean mEnabled = true;
-
- public EditBookmarkTextWatcher(AlertDialog aDialog) {
- mDialog = aDialog;
+ BookmarkUriWatcher(TextInputLayout textInputLayout) {
+ this.textInputLayout = textInputLayout;
}
- public void setPairedTextWatcher(EditBookmarkTextWatcher aTextWatcher) {
- mPairedTextWatcher = aTextWatcher;
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- // Textwatcher interface
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
- // Disable if the we're disabled or the paired partner is disabled
- boolean enabled = mEnabled && (mPairedTextWatcher == null || mPairedTextWatcher.isEnabled());
- mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
+ final boolean fieldIsEmpty = (s.toString().trim().length() > 0);
+
+ if (fieldIsEmpty) {
+ // This completely removes the layout space otherwise taken up by the error message.
+ textInputLayout.setErrorEnabled(false);
+ // setErrorEnabled destroys or creates the error view, but doesn't set the error
+ // view's text; setError sets the error view's text (creating and displaying the
+ // view first if needed), but only if you're not setting the same error text you set
+ // last time. So set the error text to null here so that the next time we set the
+ // actual error text it will update (i.e. recreate) the error view's text.
+ textInputLayout.setError(null);
+ } else {
+ textInputLayout.setError(getString(R.string.bookmark_edit_location_empty_error));
+ }
}
@Override
public void afterTextChanged(Editable s) {}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
}
- /**
- * A version of the EditBookmarkTextWatcher for the url field of the dialog.
- * Only checks if the field is empty or not.
- */
- private class LocationTextWatcher extends EditBookmarkTextWatcher {
- public LocationTextWatcher(AlertDialog aDialog) {
- super(aDialog);
- }
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
+ final View editView = inflater.inflate(R.layout.bookmark_edit, container, false);
+
+ final EditText nameText = ((EditText) editView.findViewById(R.id.edit_bookmark_name));
+ final EditText locationText = ((EditText) editView.findViewById(R.id.edit_bookmark_location));
+ final EditText keywordText = ((EditText) editView.findViewById(R.id.edit_bookmark_keyword));
+
+ final Toolbar toolbar = (Toolbar) editView.findViewById(R.id.toolbar);
+ toolbar.setTitle(R.string.bookmark_edit_title);
+ toolbar.setNavigationIcon(R.drawable.tabs_panel_nav_back);
+ toolbar.inflateMenu(R.menu.edit_bookmarks_menu);
+
+ // Insert text BEFORE the cursor - when the user opens the dialog we want the cursor to be at the end of the name
+ // field. We've already requested focus in the layout file using <requestFocus/>, however using setText
+ // results in the cursor staying at the start of the text, whereas insert(0,...) results in the text being
+ // inserted in front of the cursor.
+ nameText.getText().insert(0, mBookmark.title);
+ locationText.setText(mBookmark.url);
+ keywordText.setText(mBookmark.keyword);
+
+ final BrowserDB db = BrowserDB.from(getContext());
+ toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.bin) {
+ (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
+ @Override
+ public Void doInBackground() {
+ db.removeBookmarkWithID(getContext().getContentResolver(), mBookmark.id);
+ return null;
+ }
- // Disables the ok button if the location field is empty.
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- mEnabled = (s.toString().trim().length() > 0);
- super.onTextChanged(s, start, before, count);
- }
- }
+ @Override
+ public void onPostExecute(Void result) {
+ Snackbar.make(container, R.string.bookmark_removed, Snackbar.LENGTH_SHORT).show();
+ getFragmentManager().popBackStack();
+ }
+ }).execute();
+ }
+ return false;
+ }
+ });
+
+ toolbar.setNavigationOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (locationText.getText().toString().trim().isEmpty()) {
+ // locationText is already showing an error message when it is empty - we give it focus
+ // to make it obvious to the user that they can't save a bookmark without a URL.
+ locationText.requestFocus();
+ return;
+ }
- /**
- * A version of the EditBookmarkTextWatcher for the keyword field of the dialog.
- * Checks if the field has any (non leading or trailing) spaces.
- */
- private class KeywordTextWatcher extends EditBookmarkTextWatcher {
- public KeywordTextWatcher(AlertDialog aDialog) {
- super(aDialog);
- }
+ (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
+ @Override
+ public Void doInBackground() {
+ String newUrl = locationText.getText().toString().trim();
+ String newKeyword = keywordText.getText().toString().trim();
+
+ db.updateBookmark(getContext().getContentResolver(), mBookmark.id, newUrl, nameText.getText().toString().trim(), newKeyword);
+ return null;
+ }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // Disable if the keyword contains spaces
- mEnabled = (s.toString().trim().indexOf(' ') == -1);
- super.onTextChanged(s, start, before, count);
- }
+ @Override
+ public void onPostExecute(Void result) {
+ Snackbar.make(container, R.string.bookmark_updated, Snackbar.LENGTH_SHORT).show();
+ getFragmentManager().popBackStack();
+ }
+ }).execute();
+ }
+ });
+
+ BookmarkUriWatcher locationTextWatcher = new BookmarkUriWatcher((TextInputLayout) locationText.getParent());
+ locationText.addTextChangedListener(locationTextWatcher);
+
+ return editView;
}
/**
* Show the Edit bookmark dialog for a particular url. If the url is bookmarked multiple times
* this will just edit the first instance it finds.
*
* @param url The url of the bookmark to edit. The dialog will look up other information like the id,
* current title, or keywords associated with this url. If the url isn't bookmarked, the
* dialog will fail silently. If the url is bookmarked multiple times, this will only show
* information about the first it finds.
*/
- public void show(final String url) {
- final ContentResolver cr = mContext.getContentResolver();
- final BrowserDB db = BrowserDB.from(mContext);
+ public void show(Context context, final FragmentManager fm, final String url) {
+ final ContentResolver cr = context.getContentResolver();
+ final BrowserDB db = BrowserDB.from(context);
+
(new UIAsyncTask.WithoutParams<Bookmark>(ThreadUtils.getBackgroundHandler()) {
@Override
public Bookmark doInBackground() {
final Cursor cursor = db.getBookmarkForUrl(cr, url);
if (cursor == null) {
return null;
}
@@ -165,88 +237,21 @@ public class EditBookmarkDialog {
}
@Override
public void onPostExecute(Bookmark bookmark) {
if (bookmark == null) {
return;
}
- show(bookmark.id, bookmark.title, bookmark.url, bookmark.keyword);
+ show(fm, bookmark);
}
}).execute();
}
- /**
- * Show the Edit bookmark dialog for a set of data. This will show the dialog whether
- * a bookmark with this url exists or not, but the results will NOT be saved if the id
- * is not a valid bookmark id.
- *
- * @param id The id of the bookmark to change. If there is no bookmark with this ID, the dialog
- * will fail silently.
- * @param title The initial title to show in the dialog
- * @param url The initial url to show in the dialog
- * @param keyword The initial keyword to show in the dialog
- */
- public void show(final int id, final String title, final String url, final String keyword) {
- final Context context = mContext;
-
- AlertDialog.Builder editPrompt = new AlertDialog.Builder(context);
- final View editView = LayoutInflater.from(context).inflate(R.layout.bookmark_edit, null);
- editPrompt.setTitle(R.string.bookmark_edit_title);
- editPrompt.setView(editView);
-
- final EditText nameText = ((EditText) editView.findViewById(R.id.edit_bookmark_name));
- final EditText locationText = ((EditText) editView.findViewById(R.id.edit_bookmark_location));
- final EditText keywordText = ((EditText) editView.findViewById(R.id.edit_bookmark_keyword));
- nameText.setText(title);
- locationText.setText(url);
- keywordText.setText(keyword);
-
- final BrowserDB db = BrowserDB.from(mContext);
- editPrompt.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public Void doInBackground() {
- String newUrl = locationText.getText().toString().trim();
- String newKeyword = keywordText.getText().toString().trim();
+ private void show(final FragmentManager fm, final Bookmark bookmark) {
+ this.mBookmark = bookmark;
- db.updateBookmark(context.getContentResolver(), id, newUrl, nameText.getText().toString(), newKeyword);
- return null;
- }
-
- @Override
- public void onPostExecute(Void result) {
- SnackbarBuilder.builder((Activity) context)
- .message(R.string.bookmark_updated)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- }).execute();
- }
- });
-
- editPrompt.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- // do nothing
- }
- });
-
- final AlertDialog dialog = editPrompt.create();
-
- // Create our TextWatchers
- LocationTextWatcher locationTextWatcher = new LocationTextWatcher(dialog);
- KeywordTextWatcher keywordTextWatcher = new KeywordTextWatcher(dialog);
-
- // Cross reference the TextWatchers
- locationTextWatcher.setPairedTextWatcher(keywordTextWatcher);
- keywordTextWatcher.setPairedTextWatcher(locationTextWatcher);
-
- // Add the TextWatcher Listeners
- locationText.addTextChangedListener(locationTextWatcher);
- keywordText.addTextChangedListener(keywordTextWatcher);
-
- dialog.show();
+ FragmentTransaction transaction = fm.beginTransaction();
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+ transaction.add(android.R.id.content, this).addToBackStack(null).commitAllowingStateLoss();
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
@@ -172,17 +172,17 @@ public class BookmarkStateChangeDelegate
public void onPromptFinished(final GeckoBundle result) {
final int itemId = result.getInt("button", -1);
if (itemId == 0) {
final String extrasId = res.getResourceEntryName(R.string.contextmenu_edit_bookmark);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
TelemetryContract.Method.DIALOG, extrasId);
- new EditBookmarkDialog(browserApp).show(tab.getURL());
+ new EditBookmarkDialog().show(browserApp, browserApp.getSupportFragmentManager(), tab.getURL());
} else if (itemId == 1) {
final String extrasId = res.getResourceEntryName(R.string.contextmenu_add_to_launcher);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
TelemetryContract.Method.DIALOG, extrasId);
final String url = tab.getURL();
final String title = tab.getDisplayTitle();
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -288,17 +288,17 @@ public abstract class HomeFragment exten
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
return true;
}
if (itemId == R.id.home_edit_bookmark) {
// UI Dialog associates to the activity context, not the applications'.
- new EditBookmarkDialog(context).show(info.url);
+ new EditBookmarkDialog().show(context, getFragmentManager(), info.url);
return true;
}
if (itemId == R.id.home_remove) {
// For Top Sites grid items, position is required in case item is Pinned.
final int position = info instanceof TopSitesGridContextMenuInfo ? info.position : -1;
if (info.hasPartnerBookmarkId()) {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -546,16 +546,17 @@ size. -->
any page is removed frome about:home. This includes pages that are in history,
bookmarks, or reading list. -->
<!ENTITY page_removed "Page removed">
<!ENTITY bookmark_edit_title "Edit Bookmark">
<!ENTITY bookmark_edit_name "Name">
<!ENTITY bookmark_edit_location "Location">
<!ENTITY bookmark_edit_keyword "Keyword">
+<!ENTITY bookmark_edit_location_empty_error "Bookmark location must contain a URL">
<!-- Localization note (site_settings_*) : These strings are used in the "Site Settings"
dialog that appears after selecting the "Edit Site Settings" context menu item. -->
<!ENTITY site_settings_title3 "Site Settings">
<!ENTITY site_settings_cancel "Cancel">
<!ENTITY site_settings_clear "Clear">
<!-- Localization note : These strings are used as alternate text for accessibility.
--- a/mobile/android/base/resources/layout/bookmark_edit.xml
+++ b/mobile/android/base/resources/layout/bookmark_edit.xml
@@ -3,49 +3,79 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:orientation="vertical"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:background="@android:color/white"
+ android:theme="@style/GeckoAlertDialog">
- <android.support.design.widget.TextInputLayout
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:background="?attr/colorPrimary"
+ android:minHeight="?attr/actionBarSize"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ android:title="@string/bookmark_edit_title"/>
- <EditText
- android:id="@+id/edit_bookmark_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:hint="@string/bookmark_edit_name"
- />
- </android.support.design.widget.TextInputLayout>
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scrollview"
+ android:layout_gravity="center_horizontal">
- <android.support.design.widget.TextInputLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:orientation="vertical">
+
+ <android.support.design.widget.TextInputLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:hintAnimationEnabled="true">
- <EditText
- android:id="@+id/edit_bookmark_location"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:hint="@string/bookmark_edit_location"
- android:inputType="textNoSuggestions"/>
- </android.support.design.widget.TextInputLayout>
+ <android.support.design.widget.TextInputEditText
+ android:id="@+id/edit_bookmark_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:hint="@string/bookmark_edit_name">
+ <requestFocus/>
+ </android.support.design.widget.TextInputEditText>
+ </android.support.design.widget.TextInputLayout>
+
+ <android.support.design.widget.TextInputLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:hintAnimationEnabled="true">
- <android.support.design.widget.TextInputLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ <android.support.design.widget.TextInputEditText
+ android:id="@+id/edit_bookmark_location"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:hint="@string/bookmark_edit_location"
+ android:inputType="textNoSuggestions"/>
+ </android.support.design.widget.TextInputLayout>
- <EditText
- android:id="@+id/edit_bookmark_keyword"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:hint="@string/bookmark_edit_keyword"/>
- </android.support.design.widget.TextInputLayout>
+ <android.support.design.widget.TextInputLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:hintAnimationEnabled="true">
+
+ <android.support.design.widget.TextInputEditText
+ android:id="@+id/edit_bookmark_keyword"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:hint="@string/bookmark_edit_keyword"/>
+ </android.support.design.widget.TextInputLayout>
+
+ </LinearLayout>
+
+ </ScrollView>
</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/edit_bookmarks_menu.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item android:id="@+id/bin"
+ android:icon="@android:drawable/ic_menu_delete"
+ android:title="@string/bookmark_remove"
+ app:showAsAction="always"/>
+</menu>
\ No newline at end of file
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -434,16 +434,17 @@
<string name="pref_compact_tabs_summary">&pref_compact_tabs_summary2;</string>
<string name="page_removed">&page_removed;</string>
<string name="bookmark_edit_title">&bookmark_edit_title;</string>
<string name="bookmark_edit_name">&bookmark_edit_name;</string>
<string name="bookmark_edit_location">&bookmark_edit_location;</string>
<string name="bookmark_edit_keyword">&bookmark_edit_keyword;</string>
+ <string name="bookmark_edit_location_empty_error">&bookmark_edit_location_empty_error;</string>
<string name="pref_use_master_password">&pref_use_master_password;</string>
<string name="masterpassword_create_title">&masterpassword_create_title;</string>
<string name="masterpassword_remove_title">&masterpassword_remove_title;</string>
<string name="masterpassword_password">&masterpassword_password;</string>
<string name="masterpassword_confirm">&masterpassword_confirm;</string>
<string name="button_ok">&button_ok;</string>