From f148283053048f577c8a3a51b0d8e26774be2b70 Mon Sep 17 00:00:00 2001 From: anthony restaino Date: Sun, 7 May 2017 19:25:50 -0400 Subject: [PATCH] Moving bookmarks storage from flat file to database --- .../lightning/activity/BookmarkUiModel.java | 30 ++ .../lightning/activity/BrowserActivity.java | 66 ++- .../lightning/activity/TabsManager.java | 4 +- .../browser/lightning/app/AppComponent.java | 2 +- .../acr/browser/lightning/app/AppModule.java | 9 + .../acr/browser/lightning/app/BrowserApp.java | 21 + .../lightning/constant/BookmarkPage.java | 117 +++--- .../lightning/constant/HistoryPage.java | 2 +- .../lightning/database/HistoryItem.java | 14 +- .../database/bookmark/BookmarkDatabase.java | 375 ++++++++++++++++++ .../database/bookmark/BookmarkExporter.java | 166 ++++++++ .../{ => bookmark}/BookmarkLocalSync.java | 3 +- .../{ => bookmark}/BookmarkManager.java | 12 +- .../database/bookmark/BookmarkModel.java | 60 +++ .../{ => history}/HistoryDatabase.java | 3 +- .../database/{ => history}/HistoryModel.java | 3 +- .../dialog/LightningDialogBuilder.java | 120 ++++-- .../fragment/BookmarkSettingsFragment.java | 62 ++- .../lightning/fragment/BookmarksFragment.java | 103 +++-- .../lightning/search/SuggestionsAdapter.java | 25 +- .../acr/browser/lightning/utils/WebUtils.java | 2 +- .../browser/lightning/view/LightningView.java | 6 +- 22 files changed, 1006 insertions(+), 199 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/activity/BookmarkUiModel.java create mode 100644 app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java create mode 100644 app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkExporter.java rename app/src/main/java/acr/browser/lightning/database/{ => bookmark}/BookmarkLocalSync.java (98%) rename app/src/main/java/acr/browser/lightning/database/{ => bookmark}/BookmarkManager.java (98%) create mode 100644 app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkModel.java rename app/src/main/java/acr/browser/lightning/database/{ => history}/HistoryDatabase.java (98%) rename app/src/main/java/acr/browser/lightning/database/{ => history}/HistoryModel.java (96%) diff --git a/app/src/main/java/acr/browser/lightning/activity/BookmarkUiModel.java b/app/src/main/java/acr/browser/lightning/activity/BookmarkUiModel.java new file mode 100644 index 0000000..5895dee --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/activity/BookmarkUiModel.java @@ -0,0 +1,30 @@ +package acr.browser.lightning.activity; + +import android.support.annotation.Nullable; + +import acr.browser.lightning.browser.BookmarksView; + +/** + * The UI model representing the current folder shown + * by the {@link BookmarksView}. + *

+ * Created by anthonycr on 5/7/17. + */ +public class BookmarkUiModel { + + @Nullable private String mCurrentFolder; + + public void setCurrentFolder(@Nullable String folder) { + mCurrentFolder = folder; + } + + public boolean isRootFolder() { + return mCurrentFolder == null; + } + + @Nullable + public String getCurrentFolder() { + return mCurrentFolder; + } + +} diff --git a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java index 97ab0b3..71f44fa 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -99,9 +99,9 @@ import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.controller.UIController; -import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.database.HistoryModel; +import acr.browser.lightning.database.bookmark.BookmarkModel; +import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.fragment.BookmarksFragment; @@ -181,7 +181,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private String mCameraPhotoPath; // The singleton BookmarkManager - @Inject BookmarkManager mBookmarkManager; + @Inject BookmarkModel mBookmarkManager; @Inject LightningDialogBuilder mBookmarksDialogBuilder; @@ -827,23 +827,37 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements // By using a manager, adds a bookmark and notifies third parties about that private void addBookmark(final String title, final String url) { - final HistoryItem item = !mBookmarkManager.isBookmark(url) - ? new HistoryItem(url, title) - : null; - if (item != null && mBookmarkManager.addBookmark(item)) { - mSuggestionsAdapter.refreshBookmarks(); - mBookmarksView.handleUpdatedUrl(url); - } + + final HistoryItem item = new HistoryItem(url, title); + mBookmarkManager.addBookmarkIfNotExists(item) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + if (Boolean.TRUE.equals(item)) { + mSuggestionsAdapter.refreshBookmarks(); + mBookmarksView.handleUpdatedUrl(url); + } + } + }); } private void deleteBookmark(final String title, final String url) { - final HistoryItem item = mBookmarkManager.isBookmark(url) - ? new HistoryItem(url, title) - : null; - if (item != null && mBookmarkManager.deleteBookmark(item)) { - mSuggestionsAdapter.refreshBookmarks(); - mBookmarksView.handleUpdatedUrl(url); - } + final HistoryItem item = new HistoryItem(url, title); + + mBookmarkManager.deleteBookmark(item) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + if (Boolean.TRUE.equals(item)) { + mSuggestionsAdapter.refreshBookmarks(); + mBookmarksView.handleUpdatedUrl(url); + } + } + }); } private void putToolbarInRoot() { @@ -1098,11 +1112,19 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } if (!UrlUtils.isSpecialUrl(url)) { - if (!mBookmarkManager.isBookmark(url)) { - addBookmark(title, url); - } else { - deleteBookmark(title, url); - } + mBookmarkManager.isBookmark(url) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + if (Boolean.TRUE.equals(item)) { + addBookmark(title, url); + } else { + deleteBookmark(title, url); + } + } + }); } } diff --git a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java index 02f825d..e2bf713 100644 --- a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java +++ b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java @@ -35,7 +35,6 @@ import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.StartPage; -import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.FileUtils; @@ -51,7 +50,7 @@ import acr.browser.lightning.view.LightningView; public class TabsManager { private static final String TAG = "TabsManager"; - + private static final String BUNDLE_KEY = "WEBVIEW_"; private static final String URL_KEY = "URL_KEY"; private static final String BUNDLE_STORAGE = "SAVED_TABS.parcel"; @@ -64,7 +63,6 @@ public class TabsManager { private final List mPostInitializationWorkList = new ArrayList<>(); @Inject PreferenceManager mPreferenceManager; - @Inject BookmarkManager mBookmarkManager; @Inject Application mApp; public TabsManager() { diff --git a/app/src/main/java/acr/browser/lightning/app/AppComponent.java b/app/src/main/java/acr/browser/lightning/app/AppComponent.java index 12ed5ef..6605da1 100644 --- a/app/src/main/java/acr/browser/lightning/app/AppComponent.java +++ b/app/src/main/java/acr/browser/lightning/app/AppComponent.java @@ -11,7 +11,7 @@ import acr.browser.lightning.browser.BrowserPresenter; import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.StartPage; -import acr.browser.lightning.database.HistoryDatabase; +import acr.browser.lightning.database.history.HistoryDatabase; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.fragment.BookmarkSettingsFragment; diff --git a/app/src/main/java/acr/browser/lightning/app/AppModule.java b/app/src/main/java/acr/browser/lightning/app/AppModule.java index 605baf2..d4c68f8 100644 --- a/app/src/main/java/acr/browser/lightning/app/AppModule.java +++ b/app/src/main/java/acr/browser/lightning/app/AppModule.java @@ -8,6 +8,8 @@ import net.i2p.android.ui.I2PAndroidHelper; import javax.inject.Singleton; +import acr.browser.lightning.database.bookmark.BookmarkDatabase; +import acr.browser.lightning.database.bookmark.BookmarkModel; import dagger.Module; import dagger.Provides; @@ -29,6 +31,13 @@ public class AppModule { return mApp.getApplicationContext(); } + @NonNull + @Provides + @Singleton + public BookmarkModel provideBookmarkMode() { + return new BookmarkDatabase(mApp); + } + @NonNull @Provides @Singleton diff --git a/app/src/main/java/acr/browser/lightning/app/BrowserApp.java b/app/src/main/java/acr/browser/lightning/app/BrowserApp.java index 1fd4962..916a31f 100644 --- a/app/src/main/java/acr/browser/lightning/app/BrowserApp.java +++ b/app/src/main/java/acr/browser/lightning/app/BrowserApp.java @@ -12,14 +12,19 @@ import android.support.annotation.Nullable; import android.util.Log; import android.webkit.WebView; +import com.anthonycr.bonsai.Schedulers; import com.squareup.leakcanary.LeakCanary; +import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import javax.inject.Inject; import acr.browser.lightning.BuildConfig; +import acr.browser.lightning.database.HistoryItem; +import acr.browser.lightning.database.bookmark.BookmarkManager; +import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.MemoryLeakUtils; @@ -33,6 +38,8 @@ public class BrowserApp extends Application { private static final Executor mIOThread = Executors.newSingleThreadExecutor(); @Inject PreferenceManager mPreferenceManager; + @Inject BookmarkManager mOldBookmarkManager; + @Inject BookmarkModel mBookmarkModel; @Override protected void attachBaseContext(Context base) { @@ -74,6 +81,20 @@ public class BrowserApp extends Application { sAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build(); sAppComponent.inject(this); + Schedulers.worker().execute(new Runnable() { + @Override + public void run() { + List oldBookmarks = mOldBookmarkManager.getAllBookmarks(true); + mOldBookmarkManager.deleteAllBookmarks(); + + if (!oldBookmarks.isEmpty()) { + mBookmarkModel.addBookmarkList(oldBookmarks).subscribeOn(Schedulers.io()).subscribe(); + } else { + // TODO: 5/7/17 Import bookmarks from assets if not empty + } + } + }); + if (mPreferenceManager.getUseLeakCanary() && !isRelease()) { LeakCanary.install(this); } diff --git a/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java b/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java index bdf878d..208dc81 100644 --- a/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java @@ -11,6 +11,7 @@ import android.support.annotation.Nullable; import com.anthonycr.bonsai.Single; import com.anthonycr.bonsai.SingleAction; +import com.anthonycr.bonsai.SingleOnSubscribe; import com.anthonycr.bonsai.SingleSubscriber; import java.io.File; @@ -24,8 +25,9 @@ import javax.inject.Inject; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; -import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; +import acr.browser.lightning.database.bookmark.BookmarkModel; +import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.Utils; @@ -37,25 +39,25 @@ public final class BookmarkPage { public static final String FILENAME = "bookmarks.html"; private static final String HEADING_1 = "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - ""; + "<head>\n" + + "<meta content=en-us http-equiv=Content-Language />\n" + + "<meta content='text/html; charset=utf-8' http-equiv=Content-Type />\n" + + "<meta name=viewport content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>\n" + + "<title>"; private static final String HEADING_2 = "\n" + - "\n" + - "\n" + - "

"; + "\n" + + "\n" + + "
"; private static final String PART1 = "
\n" + - "
\n" + - "
\n" + - "

\n" + - " list = mManager.getBookmarksCopyFromFolder(folder, true); - final File bookmarkWebPage; - if (folder == null || folder.isEmpty()) { - bookmarkWebPage = new File(mFilesDir, FILENAME); - } else { - bookmarkWebPage = new File(mFilesDir, folder + '-' + FILENAME); - } - final StringBuilder bookmarkBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2); - - final String folderIconPath = Constants.FILE + mCacheDir + '/' + FOLDER_ICON; - for (int n = 0, size = list.size(); n < size; n++) { - final HistoryItem item = list.get(n); - bookmarkBuilder.append(PART1); - if (item.isFolder()) { - final File folderPage = new File(mFilesDir, item.getTitle() + '-' + FILENAME); - bookmarkBuilder.append(Constants.FILE).append(folderPage); - bookmarkBuilder.append(PART2); - bookmarkBuilder.append(folderIconPath); - buildBookmarkPage(item.getTitle()); - } else { - bookmarkBuilder.append(item.getUrl()); - bookmarkBuilder.append(PART2).append(PART3); - bookmarkBuilder.append(item.getUrl()); - } - bookmarkBuilder.append(PART4); - bookmarkBuilder.append(item.getTitle()); - bookmarkBuilder.append(PART5); - } - bookmarkBuilder.append(END); - FileWriter bookWriter = null; - try { - //noinspection IOResourceOpenedButNotSafelyClosed - bookWriter = new FileWriter(bookmarkWebPage, false); - bookWriter.write(bookmarkBuilder.toString()); - } catch (IOException e) { - e.printStackTrace(); - } finally { - Utils.close(bookWriter); - } + mManager.getBookmarksFromFolder(folder) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List list) { + Preconditions.checkNonNull(list); + + final File bookmarkWebPage; + if (folder == null || folder.isEmpty()) { + bookmarkWebPage = new File(mFilesDir, FILENAME); + } else { + bookmarkWebPage = new File(mFilesDir, folder + '-' + FILENAME); + } + final StringBuilder bookmarkBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2); + + final String folderIconPath = Constants.FILE + mCacheDir + '/' + FOLDER_ICON; + for (int n = 0, size = list.size(); n < size; n++) { + final HistoryItem item = list.get(n); + bookmarkBuilder.append(PART1); + if (item.isFolder()) { + final File folderPage = new File(mFilesDir, item.getTitle() + '-' + FILENAME); + bookmarkBuilder.append(Constants.FILE).append(folderPage); + bookmarkBuilder.append(PART2); + bookmarkBuilder.append(folderIconPath); + buildBookmarkPage(item.getTitle()); + } else { + bookmarkBuilder.append(item.getUrl()); + bookmarkBuilder.append(PART2).append(PART3); + bookmarkBuilder.append(item.getUrl()); + } + bookmarkBuilder.append(PART4); + bookmarkBuilder.append(item.getTitle()); + bookmarkBuilder.append(PART5); + } + bookmarkBuilder.append(END); + FileWriter bookWriter = null; + try { + //noinspection IOResourceOpenedButNotSafelyClosed + bookWriter = new FileWriter(bookmarkWebPage, false); + bookWriter.write(bookmarkBuilder.toString()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + Utils.close(bookWriter); + } + } + }); } } diff --git a/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java b/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java index 323c190..b175082 100644 --- a/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java @@ -27,7 +27,7 @@ import javax.inject.Inject; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.database.HistoryModel; +import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Utils; diff --git a/app/src/main/java/acr/browser/lightning/database/HistoryItem.java b/app/src/main/java/acr/browser/lightning/database/HistoryItem.java index b439b1b..9021e73 100644 --- a/app/src/main/java/acr/browser/lightning/database/HistoryItem.java +++ b/app/src/main/java/acr/browser/lightning/database/HistoryItem.java @@ -25,7 +25,7 @@ public class HistoryItem implements Comparable { private Bitmap mBitmap = null; private int mImageId = 0; - private int mOrder = 0; + private int mPosition = 0; private boolean mIsFolder = false; public HistoryItem() {} @@ -63,12 +63,12 @@ public class HistoryItem implements Comparable { mFolder = (folder == null) ? "" : folder; } - public void setOrder(int order) { - mOrder = order; + public void setPosition(int order) { + mPosition = order; } - public int getOrder() { - return mOrder; + public int getPosition() { + return mPosition; } @NonNull @@ -132,8 +132,8 @@ public class HistoryItem implements Comparable { HistoryItem that = (HistoryItem) object; return mImageId == that.mImageId && - this.mTitle.equals(that.mTitle) && this.mUrl.equals(that.mUrl) && - this.mFolder.equals(that.mFolder); + this.mTitle.equals(that.mTitle) && this.mUrl.equals(that.mUrl) && + this.mFolder.equals(that.mFolder); } @Override diff --git a/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java new file mode 100644 index 0000000..3bfcf1f --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java @@ -0,0 +1,375 @@ +package acr.browser.lightning.database.bookmark; + +import android.app.Application; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.anthonycr.bonsai.Completable; +import com.anthonycr.bonsai.CompletableAction; +import com.anthonycr.bonsai.CompletableSubscriber; +import com.anthonycr.bonsai.Single; +import com.anthonycr.bonsai.SingleAction; +import com.anthonycr.bonsai.SingleSubscriber; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import acr.browser.lightning.R; +import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.database.HistoryItem; + +/** + * The disk backed bookmark database. + *

+ * Created by anthonycr on 5/6/17. + */ +@Singleton +public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel { + + private static final String TAG = "BookmarkDatabase"; + + // Database Version + private static final int DATABASE_VERSION = 1; + + // Database Name + private static final String DATABASE_NAME = "bookmarkManager"; + + // HistoryItems table name + private static final String TABLE_BOOKMARK = "bookmark"; + + // HistoryItems Table Columns names + private static final String KEY_ID = "id"; + private static final String KEY_URL = "url"; + private static final String KEY_TITLE = "title"; + private static final String KEY_FOLDER = "folder"; + private static final String KEY_POSITION = "position"; + + + @NonNull private final String DEFAULT_BOOKMARK_TITLE; + + @Nullable private SQLiteDatabase mDatabase; + + @Inject + public BookmarkDatabase(@NonNull Application application) { + super(application, DATABASE_NAME, null, DATABASE_VERSION); + DEFAULT_BOOKMARK_TITLE = application.getString(R.string.untitled); + } + + @NonNull + private SQLiteDatabase lazyDatabase() { + if (mDatabase == null) { + mDatabase = getWritableDatabase(); + } + + return mDatabase; + } + + // Creating Tables + @Override + public void onCreate(@NonNull SQLiteDatabase db) { + String CREATE_BOOKMARK_TABLE = "CREATE TABLE " + + DatabaseUtils.sqlEscapeString(TABLE_BOOKMARK) + '(' + + DatabaseUtils.sqlEscapeString(KEY_ID) + " INTEGER PRIMARY KEY," + + DatabaseUtils.sqlEscapeString(KEY_URL) + " TEXT," + + DatabaseUtils.sqlEscapeString(KEY_TITLE) + " TEXT," + + DatabaseUtils.sqlEscapeString(KEY_FOLDER) + " TEXT," + + DatabaseUtils.sqlEscapeString(KEY_POSITION) + " INTEGER" + ')'; + db.execSQL(CREATE_BOOKMARK_TABLE); + } + + // Upgrading database + @Override + public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + // Drop older table if it exists + db.execSQL("DROP TABLE IF EXISTS " + DatabaseUtils.sqlEscapeString(TABLE_BOOKMARK)); + // Create tables again + onCreate(db); + } + + @NonNull + private static ContentValues bindBookmarkToContentValues(@NonNull HistoryItem historyItem) { + ContentValues contentValues = new ContentValues(4); + contentValues.put(KEY_TITLE, historyItem.getTitle()); + contentValues.put(KEY_URL, historyItem.getUrl()); + contentValues.put(KEY_FOLDER, historyItem.getFolder()); + contentValues.put(KEY_POSITION, historyItem.getPosition()); + + return contentValues; + } + + @NonNull + private static HistoryItem bindCursorToHistoryItem(@NonNull Cursor cursor) { + HistoryItem bookmark = new HistoryItem(); + + bookmark.setImageId(R.drawable.ic_bookmark); + bookmark.setUrl(cursor.getString(cursor.getColumnIndex(KEY_URL))); + bookmark.setTitle(cursor.getString(cursor.getColumnIndex(KEY_TITLE))); + bookmark.setFolder(cursor.getString(cursor.getColumnIndex(KEY_FOLDER))); + bookmark.setPosition(cursor.getInt(cursor.getColumnIndex(KEY_POSITION))); + + return bookmark; + } + + @NonNull + private static List bindCursorToHistoryItemList(@NonNull Cursor cursor) { + List bookmarks = new ArrayList<>(); + + while (cursor.moveToNext()) { + bookmarks.add(bindCursorToHistoryItem(cursor)); + } + + cursor.close(); + + return bookmarks; + } + + @NonNull + @Override + public Single findBookmarkForUrl(@NonNull final String url) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1"); + + if (cursor.moveToFirst()) { + subscriber.onItem(bindCursorToHistoryItem(cursor)); + } + + cursor.close(); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single isBookmark(@NonNull final String url) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1"); + + subscriber.onItem(cursor.moveToFirst()); + + cursor.close(); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single addBookmarkIfNotExists(@NonNull final HistoryItem item) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{item.getUrl()}, null, null, null, "1"); + + if (cursor.moveToFirst()) { + cursor.close(); + subscriber.onItem(false); + subscriber.onComplete(); + return; + } + + cursor.close(); + + long id = lazyDatabase().insert(TABLE_BOOKMARK, null, bindBookmarkToContentValues(item)); + + subscriber.onItem(id != -1); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Completable addBookmarkList(@NonNull final List bookmarkItems) { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + lazyDatabase().beginTransaction(); + + for (HistoryItem item : bookmarkItems) { + addBookmarkIfNotExists(item).subscribe(); + } + + lazyDatabase().setTransactionSuccessful(); + lazyDatabase().endTransaction(); + + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single deleteBookmark(@NonNull final HistoryItem bookmark) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + int rows = lazyDatabase().delete(TABLE_BOOKMARK, KEY_URL + "=?", new String[]{bookmark.getUrl()}); + + subscriber.onItem(rows > 0); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Completable renameFolder(@NonNull final String oldName, @NonNull final String newName) { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + ContentValues contentValues = new ContentValues(1); + contentValues.put(KEY_FOLDER, newName); + + lazyDatabase().update(TABLE_BOOKMARK, contentValues, KEY_FOLDER + "=?", new String[]{oldName}); + + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Completable deleteFolder(@NonNull final String folderToDelete) { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + renameFolder(folderToDelete, "").subscribe(); + + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Completable deleteAllBookmarks() { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + lazyDatabase().delete(TABLE_BOOKMARK, null, null); + + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Completable editBookmark(@NonNull final HistoryItem oldBookmark, @NonNull final HistoryItem newBookmark) { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + if (newBookmark.getTitle().isEmpty()) { + newBookmark.setTitle(DEFAULT_BOOKMARK_TITLE); + } + ContentValues contentValues = bindBookmarkToContentValues(newBookmark); + + lazyDatabase().update(TABLE_BOOKMARK, contentValues, KEY_URL + "=?", new String[]{oldBookmark.getUrl()}); + + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single> getAllBookmarks() { + return Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> subscriber) { + Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, null, null, null, null, null); + + subscriber.onItem(bindCursorToHistoryItemList(cursor)); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single> getBookmarksFromFolder(@Nullable final String folder) { + return Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> subscriber) { + String finalFolder = folder != null ? folder : ""; + Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_FOLDER + "=?", new String[]{finalFolder}, null, null, null); + + subscriber.onItem(bindCursorToHistoryItemList(cursor)); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single> getFolders() { + return Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> subscriber) { + Cursor cursor = lazyDatabase().query(true, TABLE_BOOKMARK, new String[]{KEY_FOLDER}, null, null, null, null, null, null); + + List folders = new ArrayList<>(); + while (cursor.moveToNext()) { + String folderName = cursor.getString(cursor.getColumnIndex(KEY_FOLDER)); + if (TextUtils.isEmpty(folderName)) { + continue; + } + + final HistoryItem folder = new HistoryItem(); + folder.setIsFolder(true); + folder.setTitle(folderName); + folder.setImageId(R.drawable.ic_folder); + folder.setUrl(Constants.FOLDER + folderName); + + folders.add(folder); + } + + cursor.close(); + + subscriber.onItem(folders); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single> getFolderNames() { + return Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> subscriber) { + Cursor cursor = lazyDatabase().query(true, TABLE_BOOKMARK, new String[]{KEY_FOLDER}, null, null, null, null, null, null); + + List folders = new ArrayList<>(); + while (cursor.moveToNext()) { + String folderName = cursor.getString(cursor.getColumnIndex(KEY_FOLDER)); + if (TextUtils.isEmpty(folderName)) { + continue; + } + + folders.add(folderName); + } + + cursor.close(); + + subscriber.onItem(folders); + subscriber.onComplete(); + } + }); + } + +} diff --git a/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkExporter.java b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkExporter.java new file mode 100644 index 0000000..05557f8 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkExporter.java @@ -0,0 +1,166 @@ +package acr.browser.lightning.database.bookmark; + +import android.content.Context; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; +import android.util.Log; + +import com.anthonycr.bonsai.Completable; +import com.anthonycr.bonsai.CompletableAction; +import com.anthonycr.bonsai.CompletableSubscriber; +import com.anthonycr.bonsai.Single; +import com.anthonycr.bonsai.SingleAction; +import com.anthonycr.bonsai.SingleSubscriber; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import acr.browser.lightning.R; +import acr.browser.lightning.database.HistoryItem; +import acr.browser.lightning.utils.Preconditions; +import acr.browser.lightning.utils.Utils; + +/** + * The class responsible for importing and exporting + * bookmarks in the JSON format. + *

+ * Created by anthonycr on 5/7/17. + */ +public class BookmarkExporter { + + private static final String TAG = "BookmarkExporter"; + + private static final String KEY_URL = "url"; + private static final String KEY_TITLE = "title"; + private static final String KEY_FOLDER = "folder"; + private static final String KEY_ORDER = "order"; + + @NonNull + public static List importBookmarksFromAssets(@NonNull Context context) { + List bookmarks = new ArrayList<>(); + BufferedReader bookmarksReader = null; + InputStream inputStream = null; + try { + inputStream = context.getResources().openRawResource(R.raw.default_bookmarks); + //noinspection IOResourceOpenedButNotSafelyClosed + bookmarksReader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = bookmarksReader.readLine()) != null) { + try { + JSONObject object = new JSONObject(line); + HistoryItem item = new HistoryItem(); + item.setTitle(object.getString(KEY_TITLE)); + final String url = object.getString(KEY_URL); + item.setUrl(url); + item.setFolder(object.getString(KEY_FOLDER)); + item.setPosition(object.getInt(KEY_ORDER)); + item.setImageId(R.drawable.ic_bookmark); + bookmarks.add(item); + } catch (JSONException e) { + Log.e(TAG, "Can't parse line " + line, e); + } + } + } catch (IOException e) { + Log.e(TAG, "Error reading the bookmarks file", e); + } finally { + Utils.close(bookmarksReader); + Utils.close(inputStream); + } + + return bookmarks; + } + + @NonNull + public static Completable exportBookmarksToFile(@NonNull final List bookmarkList, + @NonNull final File file) { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull final CompletableSubscriber subscriber) { + Preconditions.checkNonNull(bookmarkList); + BufferedWriter bookmarkWriter = null; + try { + //noinspection IOResourceOpenedButNotSafelyClosed + bookmarkWriter = new BufferedWriter(new FileWriter(file, false)); + + JSONObject object = new JSONObject(); + for (HistoryItem item : bookmarkList) { + object.put(KEY_TITLE, item.getTitle()); + object.put(KEY_URL, item.getUrl()); + object.put(KEY_FOLDER, item.getFolder()); + object.put(KEY_ORDER, item.getPosition()); + bookmarkWriter.write(object.toString()); + bookmarkWriter.newLine(); + } + subscriber.onComplete(); + } catch (@NonNull IOException | JSONException e) { + subscriber.onError(e); + } finally { + Utils.close(bookmarkWriter); + } + } + }); + } + + @NonNull + public static Single> importBookmarksFromFile(@NonNull final File file) { + return Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> subscriber) { + BufferedReader bookmarksReader = null; + try { + //noinspection IOResourceOpenedButNotSafelyClosed + bookmarksReader = new BufferedReader(new FileReader(file)); + String line; + + List bookmarks = new ArrayList<>(); + while ((line = bookmarksReader.readLine()) != null) { + JSONObject object = new JSONObject(line); + HistoryItem item = new HistoryItem(); + item.setTitle(object.getString(KEY_TITLE)); + item.setUrl(object.getString(KEY_URL)); + item.setFolder(object.getString(KEY_FOLDER)); + item.setPosition(object.getInt(KEY_ORDER)); + bookmarks.add(item); + } + + subscriber.onItem(bookmarks); + subscriber.onComplete(); + } catch (IOException | JSONException e) { + subscriber.onError(e); + } finally { + Utils.close(bookmarksReader); + } + } + }); + } + + @WorkerThread + @NonNull + public static File createNewExportFile() { + File bookmarksExport = new File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + "BookmarksExport.txt"); + int counter = 0; + while (bookmarksExport.exists()) { + counter++; + bookmarksExport = new File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + "BookmarksExport-" + counter + ".txt"); + } + + return bookmarksExport; + } + +} diff --git a/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkLocalSync.java similarity index 98% rename from app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java rename to app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkLocalSync.java index ce329a9..d7bd6d7 100644 --- a/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java +++ b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkLocalSync.java @@ -1,4 +1,4 @@ -package acr.browser.lightning.database; +package acr.browser.lightning.database.bookmark; import android.content.Context; import android.database.Cursor; @@ -15,6 +15,7 @@ import com.anthonycr.bonsai.SingleSubscriber; import java.util.ArrayList; import java.util.List; +import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.utils.Utils; public class BookmarkLocalSync { diff --git a/app/src/main/java/acr/browser/lightning/database/BookmarkManager.java b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkManager.java similarity index 98% rename from app/src/main/java/acr/browser/lightning/database/BookmarkManager.java rename to app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkManager.java index cf6fac4..8eb1132 100644 --- a/app/src/main/java/acr/browser/lightning/database/BookmarkManager.java +++ b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkManager.java @@ -1,4 +1,4 @@ -package acr.browser.lightning.database; +package acr.browser.lightning.database.bookmark; import android.app.Activity; import android.content.Context; @@ -37,8 +37,10 @@ import javax.inject.Singleton; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.utils.Utils; +@Deprecated @Singleton public class BookmarkManager { @@ -102,7 +104,7 @@ public class BookmarkManager { final String url = object.getString(URL); item.setUrl(url); item.setFolder(object.getString(FOLDER)); - item.setOrder(object.getInt(ORDER)); + item.setPosition(object.getInt(ORDER)); item.setImageId(R.drawable.ic_bookmark); bookmarks.put(url, item); } catch (JSONException e) { @@ -147,7 +149,7 @@ public class BookmarkManager { object.put(TITLE, item.getTitle()); object.put(URL, item.getUrl()); object.put(FOLDER, item.getFolder()); - object.put(ORDER, item.getOrder()); + object.put(ORDER, item.getPosition()); bookmarkWriter.write(object.toString()); bookmarkWriter.newLine(); } @@ -339,7 +341,7 @@ public class BookmarkManager { object.put(TITLE, item.getTitle()); object.put(URL, item.getUrl()); object.put(FOLDER, item.getFolder()); - object.put(ORDER, item.getOrder()); + object.put(ORDER, item.getPosition()); bookmarkWriter.write(object.toString()); bookmarkWriter.newLine(); } @@ -515,7 +517,7 @@ public class BookmarkManager { item.setTitle(object.getString(TITLE)); item.setUrl(object.getString(URL)); item.setFolder(object.getString(FOLDER)); - item.setOrder(object.getInt(ORDER)); + item.setPosition(object.getInt(ORDER)); list.add(item); number++; } diff --git a/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkModel.java b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkModel.java new file mode 100644 index 0000000..9d27d2c --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkModel.java @@ -0,0 +1,60 @@ +package acr.browser.lightning.database.bookmark; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.anthonycr.bonsai.Completable; +import com.anthonycr.bonsai.Single; + +import java.io.File; +import java.util.List; + +import acr.browser.lightning.database.HistoryItem; + +/** + * The interface that should be used to + * communicate with the bookmark database. + *

+ * Created by anthonycr on 5/6/17. + */ +public interface BookmarkModel { + + @NonNull + Single findBookmarkForUrl(@NonNull String url); + + @NonNull + Single isBookmark(@NonNull String url); + + @NonNull + Single addBookmarkIfNotExists(@NonNull HistoryItem item); + + @NonNull + Completable addBookmarkList(@NonNull List bookmarkItems); + + @NonNull + Single deleteBookmark(@NonNull HistoryItem bookmark); + + @NonNull + Completable renameFolder(@NonNull String oldName, @NonNull String newName); + + @NonNull + Completable deleteFolder(@NonNull String folderToDelete); + + @NonNull + Completable deleteAllBookmarks(); + + @NonNull + Completable editBookmark(@NonNull HistoryItem oldBookmark, @NonNull HistoryItem newBookmark); + + @NonNull + Single> getAllBookmarks(); + + @NonNull + Single> getBookmarksFromFolder(@Nullable String folder); + + @NonNull + Single> getFolders(); + + @NonNull + Single> getFolderNames(); +} diff --git a/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java b/app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.java similarity index 98% rename from app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java rename to app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.java index 07ae7cc..7bbc07e 100644 --- a/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java +++ b/app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.java @@ -1,7 +1,7 @@ /* * Copyright 2014 A.C.R. Development */ -package acr.browser.lightning.database; +package acr.browser.lightning.database.history; import android.app.Application; import android.content.ContentValues; @@ -20,6 +20,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import acr.browser.lightning.R; +import acr.browser.lightning.database.HistoryItem; @Singleton @WorkerThread diff --git a/app/src/main/java/acr/browser/lightning/database/HistoryModel.java b/app/src/main/java/acr/browser/lightning/database/history/HistoryModel.java similarity index 96% rename from app/src/main/java/acr/browser/lightning/database/HistoryModel.java rename to app/src/main/java/acr/browser/lightning/database/history/HistoryModel.java index a577c09..3660584 100644 --- a/app/src/main/java/acr/browser/lightning/database/HistoryModel.java +++ b/app/src/main/java/acr/browser/lightning/database/history/HistoryModel.java @@ -1,4 +1,4 @@ -package acr.browser.lightning.database; +package acr.browser.lightning.database.history; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -13,6 +13,7 @@ import com.anthonycr.bonsai.SingleSubscriber; import java.util.List; import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.database.HistoryItem; /** * A model class providing reactive bindings diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java index b07cb88..1202299 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java @@ -5,6 +5,7 @@ import android.app.Dialog; import android.content.DialogInterface; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.view.View; @@ -14,6 +15,7 @@ import android.widget.EditText; import com.anthonycr.bonsai.CompletableOnSubscribe; import com.anthonycr.bonsai.Schedulers; +import com.anthonycr.bonsai.SingleOnSubscribe; import java.util.List; @@ -25,10 +27,11 @@ import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.controller.UIController; -import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.database.HistoryModel; +import acr.browser.lightning.database.bookmark.BookmarkModel; +import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Utils; /** @@ -44,7 +47,7 @@ public class LightningDialogBuilder { INCOGNITO } - @Inject BookmarkManager mBookmarkManager; + @Inject BookmarkModel mBookmarkManager; @Inject PreferenceManager mPreferenceManager; @Inject @@ -60,7 +63,7 @@ public class LightningDialogBuilder { * @param url the long pressed url */ public void showLongPressedDialogForBookmarkUrl(@NonNull final Activity activity, - @NonNull UIController uiController, + @NonNull final UIController uiController, @NonNull final String url) { final HistoryItem item; if (url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME)) { @@ -73,15 +76,19 @@ public class LightningDialogBuilder { item.setTitle(folderTitle); item.setImageId(R.drawable.ic_folder); item.setUrl(Constants.FOLDER + folderTitle); + showBookmarkFolderLongPressedDialog(activity, uiController, item); } else { - item = mBookmarkManager.findBookmarkForUrl(url); - } - if (item != null) { - if (item.isFolder()) { - showBookmarkFolderLongPressedDialog(activity, uiController, item); - } else { - showLongPressedDialogForBookmarkUrl(activity, uiController, item); - } + mBookmarkManager.findBookmarkForUrl(url) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable HistoryItem historyItem) { + if (historyItem != null) { + showLongPressedDialogForBookmarkUrl(activity, uiController, historyItem); + } + } + }); } } @@ -116,9 +123,18 @@ public class LightningDialogBuilder { new BrowserDialog.Item(R.string.dialog_remove_bookmark) { @Override public void onClick() { - if (mBookmarkManager.deleteBookmark(item)) { - uiController.handleBookmarkDeleted(item); - } + mBookmarkManager.deleteBookmark(item) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean success) { + Preconditions.checkNonNull(success); + if (success) { + uiController.handleBookmarkDeleted(item); + } + } + }); } }, new BrowserDialog.Item(R.string.dialog_edit_bookmark) { @@ -143,28 +159,44 @@ public class LightningDialogBuilder { (AutoCompleteTextView) dialogLayout.findViewById(R.id.bookmark_folder); getFolder.setHint(R.string.folder); getFolder.setText(item.getFolder()); - final List folders = mBookmarkManager.getFolderTitles(); - final ArrayAdapter suggestionsAdapter = new ArrayAdapter<>(activity, - android.R.layout.simple_dropdown_item_1line, folders); - getFolder.setThreshold(1); - getFolder.setAdapter(suggestionsAdapter); - editBookmarkDialog.setView(dialogLayout); - editBookmarkDialog.setPositiveButton(activity.getString(R.string.action_ok), - new DialogInterface.OnClickListener() { + mBookmarkManager.getFolderNames() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe>() { @Override - public void onClick(DialogInterface dialog, int which) { - HistoryItem editedItem = new HistoryItem(); - editedItem.setTitle(getTitle.getText().toString()); - editedItem.setUrl(getUrl.getText().toString()); - editedItem.setUrl(getUrl.getText().toString()); - editedItem.setFolder(getFolder.getText().toString()); - mBookmarkManager.editBookmark(item, editedItem); - uiController.handleBookmarksChange(); + public void onItem(@Nullable List folders) { + Preconditions.checkNonNull(folders); + final ArrayAdapter suggestionsAdapter = new ArrayAdapter<>(activity, + android.R.layout.simple_dropdown_item_1line, folders); + getFolder.setThreshold(1); + getFolder.setAdapter(suggestionsAdapter); + editBookmarkDialog.setView(dialogLayout); + editBookmarkDialog.setPositiveButton(activity.getString(R.string.action_ok), + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + HistoryItem editedItem = new HistoryItem(); + editedItem.setTitle(getTitle.getText().toString()); + editedItem.setUrl(getUrl.getText().toString()); + editedItem.setUrl(getUrl.getText().toString()); + editedItem.setFolder(getFolder.getText().toString()); + mBookmarkManager.editBookmark(item, editedItem) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new CompletableOnSubscribe() { + @Override + public void onComplete() { + uiController.handleBookmarksChange(); + } + }); + } + }); + Dialog dialog = editBookmarkDialog.show(); + BrowserDialog.setDialogSize(activity, dialog); } }); - Dialog dialog = editBookmarkDialog.show(); - BrowserDialog.setDialogSize(activity, dialog); } public void showBookmarkFolderLongPressedDialog(@NonNull final Activity activity, @@ -181,8 +213,15 @@ public class LightningDialogBuilder { new BrowserDialog.Item(R.string.dialog_remove_folder) { @Override public void onClick() { - mBookmarkManager.deleteFolder(item.getTitle()); - uiController.handleBookmarkDeleted(item); + mBookmarkManager.deleteFolder(item.getTitle()) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new CompletableOnSubscribe() { + @Override + public void onComplete() { + uiController.handleBookmarkDeleted(item); + } + }); } }); } @@ -202,8 +241,15 @@ public class LightningDialogBuilder { editedItem.setUrl(Constants.FOLDER + text); editedItem.setFolder(item.getFolder()); editedItem.setIsFolder(true); - mBookmarkManager.renameFolder(oldTitle, text); - uiController.handleBookmarksChange(); + mBookmarkManager.renameFolder(oldTitle, text) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new CompletableOnSubscribe() { + @Override + public void onComplete() { + uiController.handleBookmarksChange(); + } + }); } } }); diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java index 3bf2590..5a7e1af 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java @@ -21,6 +21,7 @@ import android.support.v7.app.AlertDialog; import android.util.Log; import android.widget.ArrayAdapter; +import com.anthonycr.bonsai.CompletableOnSubscribe; import com.anthonycr.bonsai.SingleOnSubscribe; import com.anthonycr.grant.PermissionsManager; import com.anthonycr.grant.PermissionsResultAction; @@ -36,13 +37,14 @@ import javax.inject.Inject; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; -import acr.browser.lightning.database.BookmarkLocalSync; -import acr.browser.lightning.database.BookmarkLocalSync.Source; -import acr.browser.lightning.database.BookmarkManager; +import acr.browser.lightning.database.bookmark.BookmarkExporter; +import acr.browser.lightning.database.bookmark.BookmarkLocalSync; +import acr.browser.lightning.database.bookmark.BookmarkLocalSync.Source; import acr.browser.lightning.database.HistoryItem; import com.anthonycr.bonsai.Schedulers; +import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Utils; @@ -58,7 +60,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref @Nullable private Activity mActivity; - @Inject BookmarkManager mBookmarkManager; + @Inject BookmarkModel mBookmarkManager; private File[] mFileList; private String[] mFileNameList; @Nullable private BookmarkLocalSync mSync; @@ -187,7 +189,28 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref new PermissionsResultAction() { @Override public void onGranted() { - mBookmarkManager.exportBookmarks(getActivity()); + mBookmarkManager.getAllBookmarks() + .subscribeOn(Schedulers.io()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List item) { + Preconditions.checkNonNull(item); + final File exportFile = BookmarkExporter.createNewExportFile(); + BookmarkExporter.exportBookmarksToFile(item, exportFile) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new CompletableOnSubscribe() { + @Override + public void onComplete() { + Activity activity = getActivity(); + if (activity != null) { + Utils.showSnackbar(activity, activity.getString(R.string.bookmark_export_path) + + ' ' + exportFile.getPath()); + } + } + }); + } + }); } @Override @@ -245,7 +268,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mBookmarkManager.deleteAllBookmarks(); + mBookmarkManager.deleteAllBookmarks().subscribeOn(Schedulers.io()).subscribe(); } }); Dialog dialog = builder.show(); @@ -408,7 +431,32 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref Dialog dialog1 = builder.show(); BrowserDialog.setDialogSize(mActivity, dialog1); } else { - mBookmarkManager.importBookmarksFromFile(mFileList[which], getActivity()); + BookmarkExporter.importBookmarksFromFile(mFileList[which]) + .subscribeOn(Schedulers.io()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable final List importList) { + Preconditions.checkNonNull(importList); + mBookmarkManager.addBookmarkList(importList) + .observeOn(Schedulers.main()) + .subscribe(new CompletableOnSubscribe() { + @Override + public void onComplete() { + Activity activity = getActivity(); + if (activity != null) { + String message = activity.getResources().getString(R.string.message_import); + Utils.showSnackbar(activity, importList.size() + " " + message); + } + } + }); + } + + @Override + public void onError(@NonNull Throwable throwable) { + Log.e(TAG, "onError: importing bookmarks", throwable); + Utils.createInformativeDialog(getActivity(), R.string.title_error, R.string.import_bookmark_error); + } + }); } } diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java index c48cffe..be9724e 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java @@ -35,6 +35,7 @@ import java.util.List; import javax.inject.Inject; import acr.browser.lightning.R; +import acr.browser.lightning.activity.BookmarkUiModel; import acr.browser.lightning.activity.ReadingActivity; import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.animation.AnimationUtils; @@ -42,8 +43,8 @@ import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.browser.BookmarksView; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.controller.UIController; -import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; +import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.favicon.FaviconModel; import acr.browser.lightning.preference.PreferenceManager; @@ -71,7 +72,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, public final static String INCOGNITO_MODE = TAG + ".INCOGNITO_MODE"; // Managers - @Inject BookmarkManager mBookmarkManager; + @Inject BookmarkModel mBookmarkManager; // Dialog builder @Inject LightningDialogBuilder mBookmarksDialogBuilder; @@ -109,16 +110,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, @Nullable private Subscription mBookmarksSubscription; - private static Single> initBookmarks(@NonNull final BookmarkManager bookmarkManager) { - return Single.create(new SingleAction>() { - @Override - public void onSubscribe(@NonNull SingleSubscriber> subscriber) { - subscriber.onItem(bookmarkManager.getBookmarksFromFolder(null, true)); - subscriber.onComplete(); - - } - }); - } + @NonNull + private final BookmarkUiModel mUiModel = new BookmarkUiModel(); @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -150,7 +143,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, final HistoryItem item = mBookmarks.get(position); if (item.isFolder()) { mScrollIndex = ((LinearLayoutManager) mBookmarksListView.getLayoutManager()).findFirstVisibleItemPosition(); - setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(item.getTitle(), true), true); + setBookmarksShown(item.getTitle(), true); } else { mUiController.bookmarkItemClicked(item); } @@ -170,7 +163,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, public void onResume() { super.onResume(); if (mBookmarkAdapter != null) { - setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false); + setBookmarksShown(null, false); } } @@ -184,9 +177,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, backView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (mBookmarkManager == null) return; - if (!mBookmarkManager.isRootFolder()) { - setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true); + if (!mUiModel.isRootFolder()) { + setBookmarksShown(null, true); mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex); } } @@ -201,17 +193,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, mBookmarksListView.setLayoutManager(new LinearLayoutManager(getContext())); mBookmarksListView.setAdapter(mBookmarkAdapter); - mBookmarksSubscription = initBookmarks(mBookmarkManager) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe>() { - @Override - public void onItem(@Nullable List item) { - mBookmarksSubscription = null; - Preconditions.checkNonNull(item); - setBookmarkDataSet(item, true); - } - }); + setBookmarksShown(null, true); return view; } @@ -251,31 +233,70 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, } private void updateBookmarkIndicator(final String url) { - if (!mBookmarkManager.isBookmark(url)) { - mBookmarkImage.setImageResource(R.drawable.ic_action_star); - mBookmarkImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); - } else { - mBookmarkImage.setImageResource(R.drawable.ic_bookmark); - mBookmarkImage.setColorFilter(ThemeUtils.getAccentColor(getContext()), PorterDuff.Mode.SRC_IN); - } + mBookmarkManager.isBookmark(url) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + Preconditions.checkNonNull(item); + if (!item) { + mBookmarkImage.setImageResource(R.drawable.ic_action_star); + mBookmarkImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); + } else { + mBookmarkImage.setImageResource(R.drawable.ic_bookmark); + mBookmarkImage.setColorFilter(ThemeUtils.getAccentColor(getContext()), PorterDuff.Mode.SRC_IN); + } + } + }); } @Override public void handleBookmarkDeleted(@NonNull HistoryItem item) { mBookmarks.remove(item); if (item.isFolder()) { - setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false); + setBookmarksShown(null, false); } else { mBookmarkAdapter.notifyDataSetChanged(); } } + private void setBookmarksShown(@Nullable final String folder, final boolean animate) { + mBookmarksSubscription = mBookmarkManager.getBookmarksFromFolder(folder) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable final List item) { + mBookmarksSubscription = null; + Preconditions.checkNonNull(item); + + mUiModel.setCurrentFolder(folder); + if (folder == null) { + mBookmarkManager.getFolders() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List folders) { + Preconditions.checkNonNull(folders); + item.addAll(folders); + setBookmarkDataSet(item, animate); + } + }); + } else { + setBookmarkDataSet(item, animate); + } + } + }); + } + private void setBookmarkDataSet(@NonNull List items, boolean animate) { mBookmarks.clear(); mBookmarks.addAll(items); mBookmarkAdapter.notifyDataSetChanged(); final int resource; - if (mBookmarkManager.isRootFolder()) { + if (mUiModel.isRootFolder()) { resource = R.drawable.ic_action_star; } else { resource = R.drawable.ic_action_back; @@ -339,10 +360,10 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, @Override public void navigateBack() { - if (mBookmarkManager.isRootFolder()) { + if (mUiModel.isRootFolder()) { mUiController.closeBookmarksDrawer(); } else { - setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true); + setBookmarksShown(null, true); mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex); } } @@ -350,8 +371,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, @Override public void handleUpdatedUrl(@NonNull String url) { updateBookmarkIndicator(url); - String folder = mBookmarkManager.getCurrentFolder(); - setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(folder, true), false); + String folder = mUiModel.getCurrentFolder(); + setBookmarksShown(folder, false); } static class BookmarkViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java index a29f83e..2937f70 100644 --- a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java @@ -38,9 +38,9 @@ import javax.inject.Inject; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; -import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.database.HistoryModel; +import acr.browser.lightning.database.bookmark.BookmarkModel; +import acr.browser.lightning.database.history.HistoryModel; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.ThemeUtils; @@ -65,7 +65,7 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable { private final Comparator mFilterComparator = new SuggestionsComparator(); - @Inject BookmarkManager mBookmarkManager; + @Inject BookmarkModel mBookmarkManager; @Inject PreferenceManager mPreferenceManager; @Inject Application mApplication; @@ -102,15 +102,16 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable { } public void refreshBookmarks() { - Completable.create(new CompletableAction() { - @Override - public void onSubscribe(@NonNull CompletableSubscriber subscriber) { - mAllBookmarks.clear(); - mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true)); - - subscriber.onComplete(); - } - }).subscribeOn(Schedulers.io()).subscribe(); + mBookmarkManager.getAllBookmarks() + .subscribeOn(Schedulers.io()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List item) { + Preconditions.checkNonNull(item); + mAllBookmarks.clear(); + mAllBookmarks.addAll(item); + } + }); } @Override diff --git a/app/src/main/java/acr/browser/lightning/utils/WebUtils.java b/app/src/main/java/acr/browser/lightning/utils/WebUtils.java index 7905664..2ae62c6 100644 --- a/app/src/main/java/acr/browser/lightning/utils/WebUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/WebUtils.java @@ -13,7 +13,7 @@ import android.webkit.WebViewDatabase; import com.anthonycr.bonsai.Schedulers; -import acr.browser.lightning.database.HistoryModel; +import acr.browser.lightning.database.history.HistoryModel; /** * Copyright 8/4/2015 Anthony Restaino diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.java b/app/src/main/java/acr/browser/lightning/view/LightningView.java index 4bf514e..4148fe5 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.java +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.java @@ -32,6 +32,7 @@ import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebSettings.PluginState; import android.webkit.WebView; +import com.anthonycr.bonsai.Schedulers; import com.anthonycr.bonsai.Single; import com.anthonycr.bonsai.SingleAction; import com.anthonycr.bonsai.SingleOnSubscribe; @@ -49,13 +50,9 @@ import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.controller.UIController; -import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.preference.PreferenceManager; - -import com.anthonycr.bonsai.Schedulers; - import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.UrlUtils; @@ -110,7 +107,6 @@ public class LightningView { @Inject PreferenceManager mPreferences; @Inject LightningDialogBuilder mBookmarksDialogBuilder; @Inject ProxyUtils mProxyUtils; - @Inject BookmarkManager mBookmarkManager; public LightningView(@NonNull Activity activity, @Nullable String url, boolean isIncognito) { BrowserApp.getAppComponent().inject(this);