Bug 1232439 - Part 2: convert edit bookmark dialog into fullscreen fragment r=mcomella
MozReview-Commit-ID: 4rRHyevmSU6
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1164,17 +1164,17 @@ public class BrowserApp extends GeckoApp
return;
}
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.this).show(tab.getURL());
+ new EditBookmarkDialog().show(BrowserApp.this, 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/EditBookmarkDialog.java
+++ b/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
@@ -5,40 +5,70 @@
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)}
* methods.
*/
-public class EditBookmarkDialog {
- private final Context mContext;
+public class EditBookmarkDialog extends DialogFragment {
+ private Bookmark mBookmark;
+
+ 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";
+
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
- public EditBookmarkDialog(Context context) {
- mContext = context;
+ outState.putInt(SAVED_STATE_ID, mBookmark.id);
+ outState.putString(SAVED_STATE_TITLE, mBookmark.title);
+ outState.putString(SAVED_STATE_URL, mBookmark.url);
+ outState.putString(SAVED_STATE_KEYWORD, mBookmark.keyword);
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ mBookmark = new Bookmark(savedInstanceState.getInt(SAVED_STATE_ID),
+ savedInstanceState.getString(SAVED_STATE_TITLE),
+ savedInstanceState.getString(SAVED_STATE_URL),
+ savedInstanceState.getString(SAVED_STATE_KEYWORD));
+ }
}
/**
* A private struct to make it easier to pass bookmark data across threads
*/
private class Bookmark {
final int id;
final String title;
@@ -56,98 +86,143 @@ public class EditBookmarkDialog {
/**
* 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 {
+ private class LocationTextWatcher implements TextWatcher {
// A stored reference to the dialog containing the text field being watched
- protected AlertDialog mDialog;
-
- // 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;
+ protected EditText mEditText;
+ protected TextInputLayout mTextInputLayout;
- public EditBookmarkTextWatcher(AlertDialog aDialog) {
- mDialog = aDialog;
- }
-
- public void setPairedTextWatcher(EditBookmarkTextWatcher aTextWatcher) {
- mPairedTextWatcher = aTextWatcher;
- }
-
- public boolean isEnabled() {
- return mEnabled;
+ public LocationTextWatcher(TextInputLayout textInputLayout) {
+ mTextInputLayout = textInputLayout;
}
// 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);
+ boolean enabled = (s.toString().trim().length() > 0);
+
+ if (enabled) {
+ // By doing setErrorEnabled to false, we completely remove the error line from the layout. Instead
+ // if we setError(null) or setError(EMPTY_STRING) we still leave an empty space in the layout
+ // (which is the equivalent of having errorEnabled=true in the xml layout). Leaving the empty space
+ // results in disproportionately large spacing, hence it seems nicer to enable/disable the error (which
+ // results in the rest of the layout content being shifted down when the error appears, and back up
+ // when the error disappears).
+ mTextInputLayout.setErrorEnabled(false);
+ } else {
+ mTextInputLayout.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.my_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 = GeckoProfile.get(getContext()).getDB();
+ 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();
+ }
+ });
+
+ LocationTextWatcher locationTextWatcher = new LocationTextWatcher((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 = GeckoProfile.get(mContext).getDB();
+ public void show(Context context, final FragmentManager fm, final String url) {
+ final ContentResolver cr = context.getContentResolver();
+ final BrowserDB db = GeckoProfile.get(context).getDB();
+
(new UIAsyncTask.WithoutParams<Bookmark>(ThreadUtils.getBackgroundHandler()) {
@Override
public Bookmark doInBackground() {
final Cursor cursor = db.getBookmarkForUrl(cr, url);
if (cursor == null) {
return null;
}
@@ -165,87 +240,32 @@ 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 = GeckoProfile.get(mContext).getDB();
- 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();
-
- db.updateBookmark(context.getContentResolver(), id, newUrl, nameText.getText().toString(), newKeyword);
- return null;
- }
+ private void show(final FragmentManager fm, final Bookmark bookmark) {
+ this.mBookmark = bookmark;
- @Override
- public void onPostExecute(Void result) {
- SnackbarHelper.showSnackbar((Activity) context,
- context.getString(R.string.bookmark_updated),
- Snackbar.LENGTH_SHORT);
- }
- }).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/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -255,17 +255,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;
(new RemoveItemByUrlTask(context, info.url, info.itemType, position)).execute();
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -476,16 +476,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 (reading_list_added3) : Used in a toast, please keep as short
--- a/mobile/android/base/resources/layout/bookmark_edit.xml
+++ b/mobile/android/base/resources/layout/bookmark_edit.xml
@@ -3,49 +3,80 @@
- 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/my_toolbar"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ 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="match_parent"
+ 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>
+ <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">
+ <requestFocus/>
+ </EditText>
+ </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">
+ <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>
- <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">
+
+ <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>
+
+ </LinearLayout>
+
+ </ScrollView>
</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/edit_bookmarks_menu.xml
@@ -0,0 +1,9 @@
+<?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
@@ -409,16 +409,17 @@
<string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_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>