Browse Source

Merge pull request #567 from anthonycr/bookmarks-refactor

Moving bookmarks from flat file to database
master
Anthony Restaino 8 years ago committed by GitHub
parent
commit
5ca24e7d11
  1. 48
      app/src/main/java/acr/browser/lightning/activity/BookmarkUiModel.java
  2. 46
      app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java
  3. 2
      app/src/main/java/acr/browser/lightning/activity/TabsManager.java
  4. 2
      app/src/main/java/acr/browser/lightning/app/AppComponent.java
  5. 9
      app/src/main/java/acr/browser/lightning/app/AppModule.java
  6. 23
      app/src/main/java/acr/browser/lightning/app/BrowserApp.java
  7. 15
      app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java
  8. 2
      app/src/main/java/acr/browser/lightning/constant/HistoryPage.java
  9. 552
      app/src/main/java/acr/browser/lightning/database/BookmarkManager.java
  10. 10
      app/src/main/java/acr/browser/lightning/database/HistoryItem.java
  11. 390
      app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java
  12. 201
      app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkExporter.java
  13. 3
      app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkLocalSync.java
  14. 164
      app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkModel.java
  15. 112
      app/src/main/java/acr/browser/lightning/database/bookmark/legacy/LegacyBookmarkManager.java
  16. 3
      app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.java
  17. 3
      app/src/main/java/acr/browser/lightning/database/history/HistoryModel.java
  18. 76
      app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java
  19. 62
      app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java
  20. 94
      app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java
  21. 19
      app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java
  22. 2
      app/src/main/java/acr/browser/lightning/utils/ThemeUtils.java
  23. 2
      app/src/main/java/acr/browser/lightning/utils/WebUtils.java
  24. 6
      app/src/main/java/acr/browser/lightning/view/LightningView.java

48
app/src/main/java/acr/browser/lightning/activity/BookmarkUiModel.java

@ -0,0 +1,48 @@
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}.
* <p>
* Created by anthonycr on 5/7/17.
*/
public class BookmarkUiModel {
@Nullable private String mCurrentFolder;
/**
* Sets the current folder that is being shown.
* Use null as the root folder.
*
* @param folder the current folder, null for root.
*/
public void setCurrentFolder(@Nullable String folder) {
mCurrentFolder = folder;
}
/**
* Determines if the current folder is
* the root folder.
*
* @return true if the current folder is
* the root, false otherwise.
*/
public boolean isRootFolder() {
return mCurrentFolder == null;
}
/**
* Gets the current folder that is being shown.
*
* @return the current folder, null for root.
*/
@Nullable
public String getCurrentFolder() {
return mCurrentFolder;
}
}

46
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.Constants;
import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem; 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.BrowserDialog;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.fragment.BookmarksFragment; import acr.browser.lightning.fragment.BookmarksFragment;
@ -181,7 +181,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
private String mCameraPhotoPath; private String mCameraPhotoPath;
// The singleton BookmarkManager // The singleton BookmarkManager
@Inject BookmarkManager mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
@Inject LightningDialogBuilder mBookmarksDialogBuilder; @Inject LightningDialogBuilder mBookmarksDialogBuilder;
@ -827,24 +827,38 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// By using a manager, adds a bookmark and notifies third parties about that // By using a manager, adds a bookmark and notifies third parties about that
private void addBookmark(final String title, final String url) { private void addBookmark(final String title, final String url) {
final HistoryItem item = !mBookmarkManager.isBookmark(url)
? new HistoryItem(url, title) final HistoryItem item = new HistoryItem(url, title);
: null; mBookmarkManager.addBookmarkIfNotExists(item)
if (item != null && mBookmarkManager.addBookmark(item)) { .subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<Boolean>() {
@Override
public void onItem(@Nullable Boolean item) {
if (Boolean.TRUE.equals(item)) {
mSuggestionsAdapter.refreshBookmarks(); mSuggestionsAdapter.refreshBookmarks();
mBookmarksView.handleUpdatedUrl(url); mBookmarksView.handleUpdatedUrl(url);
} }
} }
});
}
private void deleteBookmark(final String title, final String url) { private void deleteBookmark(final String title, final String url) {
final HistoryItem item = mBookmarkManager.isBookmark(url) final HistoryItem item = new HistoryItem(url, title);
? new HistoryItem(url, title)
: null; mBookmarkManager.deleteBookmark(item)
if (item != null && mBookmarkManager.deleteBookmark(item)) { .subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<Boolean>() {
@Override
public void onItem(@Nullable Boolean item) {
if (Boolean.TRUE.equals(item)) {
mSuggestionsAdapter.refreshBookmarks(); mSuggestionsAdapter.refreshBookmarks();
mBookmarksView.handleUpdatedUrl(url); mBookmarksView.handleUpdatedUrl(url);
} }
} }
});
}
private void putToolbarInRoot() { private void putToolbarInRoot() {
if (mToolbarLayout.getParent() != mUiLayout) { if (mToolbarLayout.getParent() != mUiLayout) {
@ -1098,12 +1112,20 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
if (!UrlUtils.isSpecialUrl(url)) { if (!UrlUtils.isSpecialUrl(url)) {
if (!mBookmarkManager.isBookmark(url)) { mBookmarkManager.isBookmark(url)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<Boolean>() {
@Override
public void onItem(@Nullable Boolean item) {
if (Boolean.TRUE.equals(item)) {
addBookmark(title, url); addBookmark(title, url);
} else { } else {
deleteBookmark(title, url); deleteBookmark(title, url);
} }
} }
});
}
} }
@Override @Override

2
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.Constants;
import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.constant.StartPage;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.FileUtils;
@ -64,7 +63,6 @@ public class TabsManager {
private final List<Runnable> mPostInitializationWorkList = new ArrayList<>(); private final List<Runnable> mPostInitializationWorkList = new ArrayList<>();
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@Inject BookmarkManager mBookmarkManager;
@Inject Application mApp; @Inject Application mApp;
public TabsManager() { public TabsManager() {

2
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.BookmarkPage;
import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.constant.StartPage; 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.dialog.LightningDialogBuilder;
import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.download.LightningDownloadListener;
import acr.browser.lightning.fragment.BookmarkSettingsFragment; import acr.browser.lightning.fragment.BookmarkSettingsFragment;

9
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 javax.inject.Singleton;
import acr.browser.lightning.database.bookmark.BookmarkDatabase;
import acr.browser.lightning.database.bookmark.BookmarkModel;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@ -29,6 +31,13 @@ public class AppModule {
return mApp.getApplicationContext(); return mApp.getApplicationContext();
} }
@NonNull
@Provides
@Singleton
public BookmarkModel provideBookmarkMode() {
return new BookmarkDatabase(mApp);
}
@NonNull @NonNull
@Provides @Provides
@Singleton @Singleton

23
app/src/main/java/acr/browser/lightning/app/BrowserApp.java

@ -12,14 +12,20 @@ import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.webkit.WebView; import android.webkit.WebView;
import com.anthonycr.bonsai.Schedulers;
import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.LeakCanary;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.BuildConfig; import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkExporter;
import acr.browser.lightning.database.bookmark.legacy.LegacyBookmarkManager;
import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.FileUtils;
import acr.browser.lightning.utils.MemoryLeakUtils; import acr.browser.lightning.utils.MemoryLeakUtils;
@ -33,6 +39,7 @@ public class BrowserApp extends Application {
private static final Executor mIOThread = Executors.newSingleThreadExecutor(); private static final Executor mIOThread = Executors.newSingleThreadExecutor();
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@Inject BookmarkModel mBookmarkModel;
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
@ -74,6 +81,22 @@ public class BrowserApp extends Application {
sAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build(); sAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
sAppComponent.inject(this); sAppComponent.inject(this);
Schedulers.worker().execute(new Runnable() {
@Override
public void run() {
List<HistoryItem> oldBookmarks = LegacyBookmarkManager.destructiveGetBookmarks(BrowserApp.this);
if (!oldBookmarks.isEmpty()) {
// If there are old bookmarks, import them
mBookmarkModel.addBookmarkList(oldBookmarks).subscribeOn(Schedulers.io()).subscribe();
} else if (mBookmarkModel.count() == 0) {
// If the database is empty, fill it from the assets list
List<HistoryItem> assetsBookmarks = BookmarkExporter.importBookmarksFromAssets(BrowserApp.this);
mBookmarkModel.addBookmarkList(assetsBookmarks).subscribeOn(Schedulers.io()).subscribe();
}
}
});
if (mPreferenceManager.getUseLeakCanary() && !isRelease()) { if (mPreferenceManager.getUseLeakCanary() && !isRelease()) {
LeakCanary.install(this); LeakCanary.install(this);
} }

15
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.Single;
import com.anthonycr.bonsai.SingleAction; import com.anthonycr.bonsai.SingleAction;
import com.anthonycr.bonsai.SingleOnSubscribe;
import com.anthonycr.bonsai.SingleSubscriber; import com.anthonycr.bonsai.SingleSubscriber;
import java.io.File; import java.io.File;
@ -24,8 +25,9 @@ import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem; 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.ThemeUtils;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
@ -71,7 +73,7 @@ public final class BookmarkPage {
private File mCacheDir; private File mCacheDir;
@Inject Application mApp; @Inject Application mApp;
@Inject BookmarkManager mManager; @Inject BookmarkModel mManager;
@NonNull private final Bitmap mFolderIcon; @NonNull private final Bitmap mFolderIcon;
@NonNull private final String mTitle; @NonNull private final String mTitle;
@ -115,7 +117,12 @@ public final class BookmarkPage {
} }
private void buildBookmarkPage(@Nullable final String folder) { private void buildBookmarkPage(@Nullable final String folder) {
final List<HistoryItem> list = mManager.getBookmarksCopyFromFolder(folder, true); mManager.getBookmarksFromFolder(folder)
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override
public void onItem(@Nullable List<HistoryItem> list) {
Preconditions.checkNonNull(list);
final File bookmarkWebPage; final File bookmarkWebPage;
if (folder == null || folder.isEmpty()) { if (folder == null || folder.isEmpty()) {
bookmarkWebPage = new File(mFilesDir, FILENAME); bookmarkWebPage = new File(mFilesDir, FILENAME);
@ -155,5 +162,7 @@ public final class BookmarkPage {
Utils.close(bookWriter); Utils.close(bookWriter);
} }
} }
});
}
} }

2
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.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.HistoryItem; 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.Preconditions;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;

552
app/src/main/java/acr/browser/lightning/database/BookmarkManager.java

@ -1,552 +0,0 @@
package acr.browser.lightning.database;
import android.app.Activity;
import android.content.Context;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
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.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
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.utils.Utils;
@Singleton
public class BookmarkManager {
private static final String TAG = "BookmarkManager";
private static final String TITLE = "title";
private static final String URL = "url";
private static final String FOLDER = "folder";
private static final String ORDER = "order";
private static final String FILE_BOOKMARKS = "bookmarks.dat";
@NonNull private final String DEFAULT_BOOKMARK_TITLE;
private Map<String, HistoryItem> mBookmarksMap;
@NonNull private String mCurrentFolder = "";
@NonNull private final Executor mExecutor;
private File mFilesDir;
@Inject
public BookmarkManager(@NonNull Context context) {
mExecutor = BrowserApp.getIOThread();
DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled);
mExecutor.execute(new BookmarkInitializer(context));
}
/**
* Initialize the BookmarkManager, it's a one-time operation and will be executed asynchronously.
* When done, mReady flag will been set to true.
*/
private class BookmarkInitializer implements Runnable {
private final Context mContext;
BookmarkInitializer(Context context) {
mContext = context;
}
@Override
public void run() {
synchronized (BookmarkManager.this) {
mFilesDir = mContext.getFilesDir();
final Map<String, HistoryItem> bookmarks = new HashMap<>();
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);
BufferedReader bookmarksReader = null;
InputStream inputStream = null;
try {
if (bookmarksFile.exists() && bookmarksFile.isFile()) {
//noinspection IOResourceOpenedButNotSafelyClosed
inputStream = new FileInputStream(bookmarksFile);
} else {
inputStream = mContext.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(TITLE));
final String url = object.getString(URL);
item.setUrl(url);
item.setFolder(object.getString(FOLDER));
item.setOrder(object.getInt(ORDER));
item.setImageId(R.drawable.ic_bookmark);
bookmarks.put(url, 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);
}
mBookmarksMap = bookmarks;
}
}
}
/**
* Dump all the given bookmarks to the bookmark file using a temporary file
*/
private class BookmarksWriter implements Runnable {
private final List<HistoryItem> mBookmarks;
BookmarksWriter(List<HistoryItem> bookmarks) {
mBookmarks = bookmarks;
}
@Override
public void run() {
final File tempFile = new File(mFilesDir,
String.format(Locale.US, "bm_%d.dat", System.currentTimeMillis()));
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);
boolean success = false;
BufferedWriter bookmarkWriter = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
bookmarkWriter = new BufferedWriter(new FileWriter(tempFile, false));
JSONObject object = new JSONObject();
for (HistoryItem item : mBookmarks) {
object.put(TITLE, item.getTitle());
object.put(URL, item.getUrl());
object.put(FOLDER, item.getFolder());
object.put(ORDER, item.getOrder());
bookmarkWriter.write(object.toString());
bookmarkWriter.newLine();
}
success = true;
} catch (@NonNull IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
if (success) {
// Overwrite the bookmarks file by renaming the temp file
//noinspection ResultOfMethodCallIgnored
tempFile.renameTo(bookmarksFile);
}
}
}
/**
* Look for bookmark using the url
*
* @param url the lookup url
* @return the bookmark as an {@link HistoryItem} or null
*/
@Nullable
public HistoryItem findBookmarkForUrl(final String url) {
return mBookmarksMap.get(url);
}
public boolean isBookmark(String url) {
return mBookmarksMap.containsKey(url);
}
/**
* This method adds the the HistoryItem item to permanent bookmark storage.<br>
* This operation is blocking if the manager is still not ready.
*
* @param item the item to add
* @return It returns true if the operation was successful.
*/
public synchronized boolean addBookmark(@NonNull HistoryItem item) {
final String url = item.getUrl();
if (mBookmarksMap.containsKey(url)) {
return false;
}
mBookmarksMap.put(url, item);
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
return true;
}
/**
* This method adds the list of HistoryItems to permanent bookmark storage
*
* @param list the list of HistoryItems to add to bookmarks
*/
public synchronized void addBookmarkList(@Nullable List<HistoryItem> list) {
if (list == null || list.isEmpty()) {
return;
}
for (HistoryItem item : list) {
final String url = item.getUrl();
if (!mBookmarksMap.containsKey(url)) {
mBookmarksMap.put(url, item);
}
}
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
}
/**
* This method deletes the bookmark with the given url. It returns
* true if the deletion was successful.
*
* @param deleteItem the bookmark item to delete
*/
public synchronized boolean deleteBookmark(@Nullable HistoryItem deleteItem) {
if (deleteItem == null || deleteItem.isFolder()) {
return false;
}
mBookmarksMap.remove(deleteItem.getUrl());
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
return true;
}
/**
* renames a folder and moves all it's contents to that folder
*
* @param oldName the folder to be renamed
* @param newName the new name of the folder
*/
public synchronized void renameFolder(@NonNull String oldName, @NonNull String newName) {
if (newName.isEmpty()) {
return;
}
for (HistoryItem item : mBookmarksMap.values()) {
if (item.getFolder().equals(oldName)) {
item.setFolder(newName);
} else if (item.isFolder() && item.getTitle().equals(oldName)) {
item.setTitle(newName);
item.setUrl(Constants.FOLDER + newName);
}
}
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
}
/**
* Delete the folder and move all bookmarks to the top level
*
* @param name the name of the folder to be deleted
*/
public synchronized void deleteFolder(@NonNull String name) {
final Map<String, HistoryItem> bookmarks = new HashMap<>();
for (HistoryItem item : mBookmarksMap.values()) {
final String url = item.getUrl();
if (item.isFolder()) {
if (!item.getTitle().equals(name)) {
bookmarks.put(url, item);
}
} else {
if (item.getFolder().equals(name)) {
item.setFolder("");
}
bookmarks.put(url, item);
}
}
mBookmarksMap = bookmarks;
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
}
/**
* This method deletes ALL bookmarks created
* by the user. Use this method carefully and
* do not use it without explicit user consent.
*/
public synchronized void deleteAllBookmarks() {
mBookmarksMap = new HashMap<>();
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
}
/**
* This method edits a particular bookmark in the bookmark database
*
* @param oldItem This is the old item that you wish to edit
* @param newItem This is the new item that will overwrite the old item
*/
public synchronized void editBookmark(@Nullable HistoryItem oldItem, @Nullable HistoryItem newItem) {
if (oldItem == null || newItem == null || oldItem.isFolder()) {
return;
}
if (newItem.getUrl().isEmpty()) {
deleteBookmark(oldItem);
return;
}
if (newItem.getTitle().isEmpty()) {
newItem.setTitle(DEFAULT_BOOKMARK_TITLE);
}
final String oldUrl = oldItem.getUrl();
final String newUrl = newItem.getUrl();
if (!oldUrl.equals(newUrl)) {
// The url has been changed, remove the old one
mBookmarksMap.remove(oldUrl);
}
mBookmarksMap.put(newUrl, newItem);
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
}
/**
* This method exports the stored bookmarks to a text file in the device's
* external download directory
*/
public synchronized void exportBookmarks(@NonNull Activity activity) {
List<HistoryItem> bookmarkList = getAllBookmarks(true);
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");
}
BufferedWriter bookmarkWriter = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksExport,
false));
JSONObject object = new JSONObject();
for (HistoryItem item : bookmarkList) {
object.put(TITLE, item.getTitle());
object.put(URL, item.getUrl());
object.put(FOLDER, item.getFolder());
object.put(ORDER, item.getOrder());
bookmarkWriter.write(object.toString());
bookmarkWriter.newLine();
}
Utils.showSnackbar(activity, activity.getString(R.string.bookmark_export_path)
+ ' ' + bookmarksExport.getPath());
} catch (@NonNull IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
}
/**
* This method returns a list of ALL stored bookmarks.
* This is a disk-bound operation and should not be
* done very frequently.
*
* @param sort force to sort the returned bookmarkList
* @return returns a list of bookmarks that can be sorted
*/
@NonNull
public synchronized List<HistoryItem> getAllBookmarks(boolean sort) {
final List<HistoryItem> bookmarks = new ArrayList<>(mBookmarksMap.values());
if (sort) {
Collections.sort(bookmarks, new SortIgnoreCase());
}
return bookmarks;
}
/**
* This method returns a list of bookmarks and folders located in the specified folder.
* This method should generally be used by the UI when it needs a list to display to the
* user as it returns a subset of all bookmarks and includes folders as well which are
* really 'fake' bookmarks.
*
* @param folder the name of the folder to retrieve bookmarks from
* @return a list of bookmarks found in that folder
*/
@NonNull
public synchronized List<HistoryItem> getBookmarksFromFolder(@Nullable String folder, boolean sort) {
List<HistoryItem> bookmarks = new ArrayList<>(1);
if (folder == null || folder.isEmpty()) {
bookmarks.addAll(getFolders(sort));
folder = "";
}
mCurrentFolder = folder;
for (HistoryItem item : mBookmarksMap.values()) {
if (item.getFolder().equals(folder))
bookmarks.add(item);
}
if (sort) {
Collections.sort(bookmarks, new SortIgnoreCase());
}
return bookmarks;
}
/**
* Different from {@link #getBookmarksFromFolder(String, boolean)} only in
* that it doesn't affect the internal state of the bookmark manager which
* tracks the current folder used by the bookmark drawer.
* <p/>
* This method returns a list of bookmarks and folders located in the specified folder.
* This method should generally be used by the UI when it needs a list to display to the
* user as it returns a subset of all bookmarks and includes folders as well which are
* really 'fake' bookmarks.
*
* @param folder the name of the folder to retrieve bookmarks from
* @return a list of bookmarks found in that folder
*/
@NonNull
public synchronized List<HistoryItem> getBookmarksCopyFromFolder(@Nullable String folder, boolean sort) {
List<HistoryItem> bookmarks = new ArrayList<>(1);
if (folder == null || folder.isEmpty()) {
bookmarks.addAll(getFolders(sort));
folder = "";
}
for (HistoryItem item : mBookmarksMap.values()) {
if (item.getFolder().equals(folder))
bookmarks.add(item);
}
if (sort) {
Collections.sort(bookmarks, new SortIgnoreCase());
}
return bookmarks;
}
/**
* Tells you if you are at the root folder or in a subfolder
*
* @return returns true if you are in the root folder
*/
public boolean isRootFolder() {
return mCurrentFolder.isEmpty();
}
/**
* Returns the current folder
*
* @return the current folder
*/
@NonNull
public String getCurrentFolder() {
return mCurrentFolder;
}
/**
* This method returns a list of all folders.
* Folders cannot be empty as they are generated from
* the list of bookmarks that have non-empty folder fields.
*
* @return a list of all folders
*/
@NonNull
private synchronized List<HistoryItem> getFolders(boolean sort) {
final HashMap<String, HistoryItem> folders = new HashMap<>();
for (HistoryItem item : mBookmarksMap.values()) {
final String folderName = item.getFolder();
if (!folderName.isEmpty() && !folders.containsKey(folderName)) {
final HistoryItem folder = new HistoryItem();
folder.setIsFolder(true);
folder.setTitle(folderName);
folder.setImageId(R.drawable.ic_folder);
folder.setUrl(Constants.FOLDER + folderName);
folders.put(folderName, folder);
}
}
final List<HistoryItem> result = new ArrayList<>(folders.values());
if (sort) {
Collections.sort(result, new SortIgnoreCase());
}
return result;
}
/**
* returns a list of folder titles that can be used for suggestions in a
* simple list adapter
*
* @return a list of folder title strings
*/
@NonNull
public synchronized List<String> getFolderTitles() {
final Set<String> folders = new HashSet<>();
for (HistoryItem item : mBookmarksMap.values()) {
final String folderName = item.getFolder();
if (!folderName.isEmpty()) {
folders.add(folderName);
}
}
return new ArrayList<>(folders);
}
/**
* This method imports the bookmarks from a backup file that is located on
* external storage
*
* @param file the file to attempt to import bookmarks from
*/
public synchronized void importBookmarksFromFile(@Nullable File file, @NonNull Activity activity) {
if (file == null) {
return;
}
List<HistoryItem> list = new ArrayList<>();
BufferedReader bookmarksReader = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
bookmarksReader = new BufferedReader(new FileReader(file));
String line;
int number = 0;
while ((line = bookmarksReader.readLine()) != null) {
JSONObject object = new JSONObject(line);
HistoryItem item = new HistoryItem();
item.setTitle(object.getString(TITLE));
item.setUrl(object.getString(URL));
item.setFolder(object.getString(FOLDER));
item.setOrder(object.getInt(ORDER));
list.add(item);
number++;
}
addBookmarkList(list);
String message = activity.getResources().getString(R.string.message_import);
Utils.showSnackbar(activity, number + " " + message);
} catch (@NonNull IOException | JSONException e) {
e.printStackTrace();
Utils.createInformativeDialog(activity, R.string.title_error, R.string.import_bookmark_error);
} finally {
Utils.close(bookmarksReader);
}
}
/**
* This class sorts bookmarks alphabetically, with folders coming after bookmarks
*/
private static class SortIgnoreCase implements Comparator<HistoryItem> {
public int compare(@Nullable HistoryItem o1, @Nullable HistoryItem o2) {
if (o1 == null || o2 == null) {
return 0;
}
if (o1.isFolder() == o2.isFolder()) {
return o1.getTitle().toLowerCase(Locale.getDefault())
.compareTo(o2.getTitle().toLowerCase(Locale.getDefault()));
} else {
return o1.isFolder() ? 1 : -1;
}
}
}
}

10
app/src/main/java/acr/browser/lightning/database/HistoryItem.java

@ -25,7 +25,7 @@ public class HistoryItem implements Comparable<HistoryItem> {
private Bitmap mBitmap = null; private Bitmap mBitmap = null;
private int mImageId = 0; private int mImageId = 0;
private int mOrder = 0; private int mPosition = 0;
private boolean mIsFolder = false; private boolean mIsFolder = false;
public HistoryItem() {} public HistoryItem() {}
@ -63,12 +63,12 @@ public class HistoryItem implements Comparable<HistoryItem> {
mFolder = (folder == null) ? "" : folder; mFolder = (folder == null) ? "" : folder;
} }
public void setOrder(int order) { public void setPosition(int order) {
mOrder = order; mPosition = order;
} }
public int getOrder() { public int getPosition() {
return mOrder; return mPosition;
} }
@NonNull @NonNull

390
app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkDatabase.java

@ -0,0 +1,390 @@
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.
* See {@link BookmarkModel} for method
* documentation.
* <p>
* 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);
}
/**
* Lazily initializes the database
* field when called.
*
* @return a non null writable database.
*/
@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 bookmarkItem) {
ContentValues contentValues = new ContentValues(4);
contentValues.put(KEY_TITLE, bookmarkItem.getTitle());
contentValues.put(KEY_URL, bookmarkItem.getUrl());
contentValues.put(KEY_FOLDER, bookmarkItem.getFolder());
contentValues.put(KEY_POSITION, bookmarkItem.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<HistoryItem> bindCursorToHistoryItemList(@NonNull Cursor cursor) {
List<HistoryItem> bookmarks = new ArrayList<>();
while (cursor.moveToNext()) {
bookmarks.add(bindCursorToHistoryItem(cursor));
}
cursor.close();
return bookmarks;
}
@NonNull
@Override
public Single<HistoryItem> findBookmarkForUrl(@NonNull final String url) {
return Single.create(new SingleAction<HistoryItem>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<HistoryItem> subscriber) {
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1");
if (cursor.moveToFirst()) {
subscriber.onItem(bindCursorToHistoryItem(cursor));
} else {
subscriber.onItem(null);
}
cursor.close();
subscriber.onComplete();
}
});
}
@NonNull
@Override
public Single<Boolean> isBookmark(@NonNull final String url) {
return Single.create(new SingleAction<Boolean>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<Boolean> 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<Boolean> addBookmarkIfNotExists(@NonNull final HistoryItem item) {
return Single.create(new SingleAction<Boolean>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<Boolean> 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<HistoryItem> 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<Boolean> deleteBookmark(@NonNull final HistoryItem bookmark) {
return Single.create(new SingleAction<Boolean>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<Boolean> 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<List<HistoryItem>> getAllBookmarks() {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, null, null, null, null, null);
subscriber.onItem(bindCursorToHistoryItemList(cursor));
subscriber.onComplete();
}
});
}
@NonNull
@Override
public Single<List<HistoryItem>> getBookmarksFromFolder(@Nullable final String folder) {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> 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<List<HistoryItem>> getFolders() {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
Cursor cursor = lazyDatabase().query(true, TABLE_BOOKMARK, new String[]{KEY_FOLDER}, null, null, null, null, null, null);
List<HistoryItem> 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<List<String>> getFolderNames() {
return Single.create(new SingleAction<List<String>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<String>> subscriber) {
Cursor cursor = lazyDatabase().query(true, TABLE_BOOKMARK, new String[]{KEY_FOLDER}, null, null, null, null, null, null);
List<String> 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();
}
});
}
@Override
public long count() {
return DatabaseUtils.queryNumEntries(lazyDatabase(), TABLE_BOOKMARK);
}
}

201
app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkExporter.java

@ -0,0 +1,201 @@
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.
* <p>
* 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";
/**
* Retrieves all the default bookmarks stored
* in the raw file within assets.
*
* @param context the context necessary to open assets.
* @return a non null list of the bookmarks stored in assets.
*/
@NonNull
public static List<HistoryItem> importBookmarksFromAssets(@NonNull Context context) {
List<HistoryItem> 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;
}
/**
* Exports the list of bookmarks to a file.
*
* @param bookmarkList the bookmarks to export.
* @param file the file to export to.
* @return an observable that emits a completion
* event when the export is complete, or an error
* event if there is a problem.
*/
@NonNull
public static Completable exportBookmarksToFile(@NonNull final List<HistoryItem> 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);
}
}
});
}
/**
* Attempts to import bookmarks from the
* given file. If the file is not in a
* supported format, it will fail.
*
* @param file the file to import from.
* @return an observable that emits the
* imported bookmarks, or an error if the
* file cannot be imported.
*/
@NonNull
public static Single<List<HistoryItem>> importBookmarksFromFile(@NonNull final File file) {
return Single.create(new SingleAction<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
BufferedReader bookmarksReader = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
bookmarksReader = new BufferedReader(new FileReader(file));
String line;
List<HistoryItem> 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);
}
}
});
}
/**
* A blocking call that creates a new export file with
* the name "BookmarkExport.txt" and an appropriate
* numerical appendage if a file already exists with
* that name.
*
* @return a non null empty file that can be used
* to export bookmarks to.
*/
@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;
}
}

3
app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java → 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.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -15,6 +15,7 @@ import com.anthonycr.bonsai.SingleSubscriber;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
public class BookmarkLocalSync { public class BookmarkLocalSync {

164
app/src/main/java/acr/browser/lightning/database/bookmark/BookmarkModel.java

@ -0,0 +1,164 @@
package acr.browser.lightning.database.bookmark;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import com.anthonycr.bonsai.Completable;
import com.anthonycr.bonsai.Single;
import java.util.List;
import acr.browser.lightning.database.HistoryItem;
/**
* The interface that should be used to
* communicate with the bookmark database.
* <p>
* Created by anthonycr on 5/6/17.
*/
public interface BookmarkModel {
/**
* Gets the bookmark associated with the URL.
*
* @param url the URL to look for.
* @return an observable that will emit either
* the bookmark associated with the URL or null.
*/
@NonNull
Single<HistoryItem> findBookmarkForUrl(@NonNull String url);
/**
* Determines if a URL is associated with a bookmark.
*
* @param url the URL to check.
* @return an observable that will emit true if
* the URL is a bookmark, false otherwise.
*/
@NonNull
Single<Boolean> isBookmark(@NonNull String url);
/**
* Adds a bookmark if one does not already exist with
* the same URL.
*
* @param item the bookmark to add.
* @return an observable that emits true if the bookmark
* was added, false otherwise.
*/
@NonNull
Single<Boolean> addBookmarkIfNotExists(@NonNull HistoryItem item);
/**
* Adds a list of bookmarks to the database.
*
* @param bookmarkItems the bookmarks to add.
* @return an observable that emits a complete event
* when all the bookmarks have been added.
*/
@NonNull
Completable addBookmarkList(@NonNull List<HistoryItem> bookmarkItems);
/**
* Deletes a bookmark from the database.
*
* @param bookmark the bookmark to delete.
* @return an observable that emits true when
* the bookmark is deleted, false otherwise.
*/
@NonNull
Single<Boolean> deleteBookmark(@NonNull HistoryItem bookmark);
/**
* Moves all bookmarks in the old folder to the new folder.
*
* @param oldName the name of the old folder.
* @param newName the name of the new folder.
* @return an observable that emits a completion
* event when the folder is renamed.
*/
@NonNull
Completable renameFolder(@NonNull String oldName, @NonNull String newName);
/**
* Deletes a folder from the database, all bookmarks
* in that folder will be moved to the root level.
*
* @param folderToDelete the folder to delete.
* @return an observable that emits a completion
* event when the folder has been deleted.
*/
@NonNull
Completable deleteFolder(@NonNull String folderToDelete);
/**
* Deletes all bookmarks in the database.
*
* @return an observable that emits a completion
* event when all bookmarks have been deleted.
*/
@NonNull
Completable deleteAllBookmarks();
/**
* Changes the bookmark with the original URL
* with all the data from the new bookmark.
*
* @param oldBookmark the old bookmark to replace.
* @param newBookmark the new bookmark.
* @return an observable that emits a completion event
* when the bookmark edit is done.
*/
@NonNull
Completable editBookmark(@NonNull HistoryItem oldBookmark, @NonNull HistoryItem newBookmark);
/**
* Emits a list of all bookmarks
*
* @return an observable that emits a list
* of all bookmarks.
*/
@NonNull
Single<List<HistoryItem>> getAllBookmarks();
/**
* Emits all bookmarks in a certain folder.
* If the folder chosen is null, then all bookmarks
* without a specified folder will be returned.
*
* @param folder gets the bookmarks from this folder, may be null.
* @return an observable that emits a list of bookmarks
* in the given folder.
*/
@NonNull
Single<List<HistoryItem>> getBookmarksFromFolder(@Nullable String folder);
/**
* Returns all folders as {@link HistoryItem}.
* The root folder is omitted.
*
* @return an observable that emits a list of folders.
*/
@NonNull
Single<List<HistoryItem>> getFolders();
/**
* Returns the names of all folders.
* The root folder is omitted.
*
* @return an observable that emits a list of folder names.
*/
@NonNull
Single<List<String>> getFolderNames();
/**
* A synchronous call to the model
* that returns the number of bookmarks.
* Should be called from a background thread.
*
* @return the number of bookmarks in the database.
*/
@WorkerThread
long count();
}

112
app/src/main/java/acr/browser/lightning/database/bookmark/legacy/LegacyBookmarkManager.java

@ -0,0 +1,112 @@
package acr.browser.lightning.database.bookmark.legacy;
import android.app.Application;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.Utils;
@Deprecated
public class LegacyBookmarkManager {
private static final String TAG = "LegacyBookmarkManager";
private static final String TITLE = "title";
private static final String URL = "url";
private static final String FOLDER = "folder";
private static final String ORDER = "order";
private static final String FILE_BOOKMARKS = "bookmarks.dat";
/**
* Gets all bookmarks from the old bookmark file
* and then deletes the file.
*
* @param application the context needed to open the file.
* @return a list of bookmarks from the old bookmark file.
*/
@WorkerThread
@NonNull
public static List<HistoryItem> destructiveGetBookmarks(@NonNull Application application) {
File filesDir = application.getFilesDir();
List<HistoryItem> bookmarks = new ArrayList<>();
final File bookmarksFile = new File(filesDir, FILE_BOOKMARKS);
BufferedReader bookmarksReader = null;
InputStream inputStream = null;
try {
if (bookmarksFile.exists() && bookmarksFile.isFile()) {
//noinspection IOResourceOpenedButNotSafelyClosed
inputStream = new FileInputStream(bookmarksFile);
} else {
return 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(TITLE));
item.setUrl(object.getString(URL));
item.setFolder(object.getString(FOLDER));
item.setPosition(object.getInt(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);
}
bookmarksFile.delete();
return bookmarks;
}
/**
* This class sorts bookmarks alphabetically, with folders coming after bookmarks
*/
private static class SortIgnoreCase implements Comparator<HistoryItem> {
public int compare(@Nullable HistoryItem o1, @Nullable HistoryItem o2) {
if (o1 == null || o2 == null) {
return 0;
}
if (o1.isFolder() == o2.isFolder()) {
return o1.getTitle().toLowerCase(Locale.getDefault())
.compareTo(o2.getTitle().toLowerCase(Locale.getDefault()));
} else {
return o1.isFolder() ? 1 : -1;
}
}
}
}

3
app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java → app/src/main/java/acr/browser/lightning/database/history/HistoryDatabase.java

@ -1,7 +1,7 @@
/* /*
* Copyright 2014 A.C.R. Development * Copyright 2014 A.C.R. Development
*/ */
package acr.browser.lightning.database; package acr.browser.lightning.database.history;
import android.app.Application; import android.app.Application;
import android.content.ContentValues; import android.content.ContentValues;
@ -20,6 +20,7 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryItem;
@Singleton @Singleton
@WorkerThread @WorkerThread

3
app/src/main/java/acr/browser/lightning/database/HistoryModel.java → 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.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -13,6 +13,7 @@ import com.anthonycr.bonsai.SingleSubscriber;
import java.util.List; import java.util.List;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.HistoryItem;
/** /**
* A model class providing reactive bindings * A model class providing reactive bindings

76
app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java

@ -5,6 +5,7 @@ import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@ -14,6 +15,7 @@ import android.widget.EditText;
import com.anthonycr.bonsai.CompletableOnSubscribe; import com.anthonycr.bonsai.CompletableOnSubscribe;
import com.anthonycr.bonsai.Schedulers; import com.anthonycr.bonsai.Schedulers;
import com.anthonycr.bonsai.SingleOnSubscribe;
import java.util.List; 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.BookmarkPage;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem; 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.preference.PreferenceManager;
import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
/** /**
@ -44,7 +47,7 @@ public class LightningDialogBuilder {
INCOGNITO INCOGNITO
} }
@Inject BookmarkManager mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@Inject @Inject
@ -60,7 +63,7 @@ public class LightningDialogBuilder {
* @param url the long pressed url * @param url the long pressed url
*/ */
public void showLongPressedDialogForBookmarkUrl(@NonNull final Activity activity, public void showLongPressedDialogForBookmarkUrl(@NonNull final Activity activity,
@NonNull UIController uiController, @NonNull final UIController uiController,
@NonNull final String url) { @NonNull final String url) {
final HistoryItem item; final HistoryItem item;
if (url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME)) { if (url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME)) {
@ -73,15 +76,19 @@ public class LightningDialogBuilder {
item.setTitle(folderTitle); item.setTitle(folderTitle);
item.setImageId(R.drawable.ic_folder); item.setImageId(R.drawable.ic_folder);
item.setUrl(Constants.FOLDER + folderTitle); item.setUrl(Constants.FOLDER + folderTitle);
} else {
item = mBookmarkManager.findBookmarkForUrl(url);
}
if (item != null) {
if (item.isFolder()) {
showBookmarkFolderLongPressedDialog(activity, uiController, item); showBookmarkFolderLongPressedDialog(activity, uiController, item);
} else { } else {
showLongPressedDialogForBookmarkUrl(activity, uiController, item); mBookmarkManager.findBookmarkForUrl(url)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<HistoryItem>() {
@Override
public void onItem(@Nullable HistoryItem historyItem) {
if (historyItem != null) {
showLongPressedDialogForBookmarkUrl(activity, uiController, historyItem);
}
} }
});
} }
} }
@ -116,10 +123,19 @@ public class LightningDialogBuilder {
new BrowserDialog.Item(R.string.dialog_remove_bookmark) { new BrowserDialog.Item(R.string.dialog_remove_bookmark) {
@Override @Override
public void onClick() { public void onClick() {
if (mBookmarkManager.deleteBookmark(item)) { mBookmarkManager.deleteBookmark(item)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<Boolean>() {
@Override
public void onItem(@Nullable Boolean success) {
Preconditions.checkNonNull(success);
if (success) {
uiController.handleBookmarkDeleted(item); uiController.handleBookmarkDeleted(item);
} }
} }
});
}
}, },
new BrowserDialog.Item(R.string.dialog_edit_bookmark) { new BrowserDialog.Item(R.string.dialog_edit_bookmark) {
@Override @Override
@ -143,7 +159,14 @@ public class LightningDialogBuilder {
(AutoCompleteTextView) dialogLayout.findViewById(R.id.bookmark_folder); (AutoCompleteTextView) dialogLayout.findViewById(R.id.bookmark_folder);
getFolder.setHint(R.string.folder); getFolder.setHint(R.string.folder);
getFolder.setText(item.getFolder()); getFolder.setText(item.getFolder());
final List<String> folders = mBookmarkManager.getFolderTitles();
mBookmarkManager.getFolderNames()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<List<String>>() {
@Override
public void onItem(@Nullable List<String> folders) {
Preconditions.checkNonNull(folders);
final ArrayAdapter<String> suggestionsAdapter = new ArrayAdapter<>(activity, final ArrayAdapter<String> suggestionsAdapter = new ArrayAdapter<>(activity,
android.R.layout.simple_dropdown_item_1line, folders); android.R.layout.simple_dropdown_item_1line, folders);
getFolder.setThreshold(1); getFolder.setThreshold(1);
@ -159,13 +182,22 @@ public class LightningDialogBuilder {
editedItem.setUrl(getUrl.getText().toString()); editedItem.setUrl(getUrl.getText().toString());
editedItem.setUrl(getUrl.getText().toString()); editedItem.setUrl(getUrl.getText().toString());
editedItem.setFolder(getFolder.getText().toString()); editedItem.setFolder(getFolder.getText().toString());
mBookmarkManager.editBookmark(item, editedItem); mBookmarkManager.editBookmark(item, editedItem)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new CompletableOnSubscribe() {
@Override
public void onComplete() {
uiController.handleBookmarksChange(); uiController.handleBookmarksChange();
} }
}); });
}
});
Dialog dialog = editBookmarkDialog.show(); Dialog dialog = editBookmarkDialog.show();
BrowserDialog.setDialogSize(activity, dialog); BrowserDialog.setDialogSize(activity, dialog);
} }
});
}
public void showBookmarkFolderLongPressedDialog(@NonNull final Activity activity, public void showBookmarkFolderLongPressedDialog(@NonNull final Activity activity,
@NonNull final UIController uiController, @NonNull final UIController uiController,
@ -181,11 +213,18 @@ public class LightningDialogBuilder {
new BrowserDialog.Item(R.string.dialog_remove_folder) { new BrowserDialog.Item(R.string.dialog_remove_folder) {
@Override @Override
public void onClick() { public void onClick() {
mBookmarkManager.deleteFolder(item.getTitle()); mBookmarkManager.deleteFolder(item.getTitle())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new CompletableOnSubscribe() {
@Override
public void onComplete() {
uiController.handleBookmarkDeleted(item); uiController.handleBookmarkDeleted(item);
} }
}); });
} }
});
}
private void showRenameFolderDialog(@NonNull final Activity activity, private void showRenameFolderDialog(@NonNull final Activity activity,
@NonNull final UIController uiController, @NonNull final UIController uiController,
@ -202,9 +241,16 @@ public class LightningDialogBuilder {
editedItem.setUrl(Constants.FOLDER + text); editedItem.setUrl(Constants.FOLDER + text);
editedItem.setFolder(item.getFolder()); editedItem.setFolder(item.getFolder());
editedItem.setIsFolder(true); editedItem.setIsFolder(true);
mBookmarkManager.renameFolder(oldTitle, text); mBookmarkManager.renameFolder(oldTitle, text)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new CompletableOnSubscribe() {
@Override
public void onComplete() {
uiController.handleBookmarksChange(); uiController.handleBookmarksChange();
} }
});
}
} }
}); });
} }

62
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.util.Log;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import com.anthonycr.bonsai.CompletableOnSubscribe;
import com.anthonycr.bonsai.SingleOnSubscribe; import com.anthonycr.bonsai.SingleOnSubscribe;
import com.anthonycr.grant.PermissionsManager; import com.anthonycr.grant.PermissionsManager;
import com.anthonycr.grant.PermissionsResultAction; import com.anthonycr.grant.PermissionsResultAction;
@ -36,13 +37,14 @@ import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.BookmarkLocalSync; import acr.browser.lightning.database.bookmark.BookmarkExporter;
import acr.browser.lightning.database.BookmarkLocalSync.Source; import acr.browser.lightning.database.bookmark.BookmarkLocalSync;
import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.bookmark.BookmarkLocalSync.Source;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import com.anthonycr.bonsai.Schedulers; import com.anthonycr.bonsai.Schedulers;
import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
@ -58,7 +60,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
@Nullable private Activity mActivity; @Nullable private Activity mActivity;
@Inject BookmarkManager mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
private File[] mFileList; private File[] mFileList;
private String[] mFileNameList; private String[] mFileNameList;
@Nullable private BookmarkLocalSync mSync; @Nullable private BookmarkLocalSync mSync;
@ -187,7 +189,28 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
new PermissionsResultAction() { new PermissionsResultAction() {
@Override @Override
public void onGranted() { public void onGranted() {
mBookmarkManager.exportBookmarks(getActivity()); mBookmarkManager.getAllBookmarks()
.subscribeOn(Schedulers.io())
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override
public void onItem(@Nullable List<HistoryItem> 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 @Override
@ -245,7 +268,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
mBookmarkManager.deleteAllBookmarks(); mBookmarkManager.deleteAllBookmarks().subscribeOn(Schedulers.io()).subscribe();
} }
}); });
Dialog dialog = builder.show(); Dialog dialog = builder.show();
@ -408,7 +431,32 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
Dialog dialog1 = builder.show(); Dialog dialog1 = builder.show();
BrowserDialog.setDialogSize(mActivity, dialog1); BrowserDialog.setDialogSize(mActivity, dialog1);
} else { } else {
mBookmarkManager.importBookmarksFromFile(mFileList[which], getActivity()); BookmarkExporter.importBookmarksFromFile(mFileList[which])
.subscribeOn(Schedulers.io())
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override
public void onItem(@Nullable final List<HistoryItem> 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);
}
});
} }
} }

94
app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java

@ -22,10 +22,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.anthonycr.bonsai.Schedulers; import com.anthonycr.bonsai.Schedulers;
import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction;
import com.anthonycr.bonsai.SingleOnSubscribe; import com.anthonycr.bonsai.SingleOnSubscribe;
import com.anthonycr.bonsai.SingleSubscriber;
import com.anthonycr.bonsai.Subscription; import com.anthonycr.bonsai.Subscription;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -35,6 +32,7 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.BookmarkUiModel;
import acr.browser.lightning.activity.ReadingActivity; import acr.browser.lightning.activity.ReadingActivity;
import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.activity.TabsManager;
import acr.browser.lightning.animation.AnimationUtils; import acr.browser.lightning.animation.AnimationUtils;
@ -42,8 +40,8 @@ import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.browser.BookmarksView; import acr.browser.lightning.browser.BookmarksView;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.favicon.FaviconModel; import acr.browser.lightning.favicon.FaviconModel;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
@ -71,7 +69,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
public final static String INCOGNITO_MODE = TAG + ".INCOGNITO_MODE"; public final static String INCOGNITO_MODE = TAG + ".INCOGNITO_MODE";
// Managers // Managers
@Inject BookmarkManager mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
// Dialog builder // Dialog builder
@Inject LightningDialogBuilder mBookmarksDialogBuilder; @Inject LightningDialogBuilder mBookmarksDialogBuilder;
@ -109,16 +107,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
@Nullable @Nullable
private Subscription mBookmarksSubscription; private Subscription mBookmarksSubscription;
private static Single<List<HistoryItem>> initBookmarks(@NonNull final BookmarkManager bookmarkManager) { @NonNull
return Single.create(new SingleAction<List<HistoryItem>>() { private final BookmarkUiModel mUiModel = new BookmarkUiModel();
@Override
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) {
subscriber.onItem(bookmarkManager.getBookmarksFromFolder(null, true));
subscriber.onComplete();
}
});
}
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
@ -150,7 +140,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
final HistoryItem item = mBookmarks.get(position); final HistoryItem item = mBookmarks.get(position);
if (item.isFolder()) { if (item.isFolder()) {
mScrollIndex = ((LinearLayoutManager) mBookmarksListView.getLayoutManager()).findFirstVisibleItemPosition(); mScrollIndex = ((LinearLayoutManager) mBookmarksListView.getLayoutManager()).findFirstVisibleItemPosition();
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(item.getTitle(), true), true); setBookmarksShown(item.getTitle(), true);
} else { } else {
mUiController.bookmarkItemClicked(item); mUiController.bookmarkItemClicked(item);
} }
@ -170,7 +160,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (mBookmarkAdapter != null) { if (mBookmarkAdapter != null) {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false); setBookmarksShown(null, false);
} }
} }
@ -184,9 +174,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
backView.setOnClickListener(new View.OnClickListener() { backView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (mBookmarkManager == null) return; if (!mUiModel.isRootFolder()) {
if (!mBookmarkManager.isRootFolder()) { setBookmarksShown(null, true);
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true);
mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex); mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex);
} }
} }
@ -201,17 +190,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
mBookmarksListView.setLayoutManager(new LinearLayoutManager(getContext())); mBookmarksListView.setLayoutManager(new LinearLayoutManager(getContext()));
mBookmarksListView.setAdapter(mBookmarkAdapter); mBookmarksListView.setAdapter(mBookmarkAdapter);
mBookmarksSubscription = initBookmarks(mBookmarkManager) setBookmarksShown(null, true);
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override
public void onItem(@Nullable List<HistoryItem> item) {
mBookmarksSubscription = null;
Preconditions.checkNonNull(item);
setBookmarkDataSet(item, true);
}
});
return view; return view;
} }
@ -251,7 +230,14 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
} }
private void updateBookmarkIndicator(final String url) { private void updateBookmarkIndicator(final String url) {
if (!mBookmarkManager.isBookmark(url)) { mBookmarkManager.isBookmark(url)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<Boolean>() {
@Override
public void onItem(@Nullable Boolean item) {
Preconditions.checkNonNull(item);
if (!item) {
mBookmarkImage.setImageResource(R.drawable.ic_action_star); mBookmarkImage.setImageResource(R.drawable.ic_action_star);
mBookmarkImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); mBookmarkImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
} else { } else {
@ -259,23 +245,55 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
mBookmarkImage.setColorFilter(ThemeUtils.getAccentColor(getContext()), PorterDuff.Mode.SRC_IN); mBookmarkImage.setColorFilter(ThemeUtils.getAccentColor(getContext()), PorterDuff.Mode.SRC_IN);
} }
} }
});
}
@Override @Override
public void handleBookmarkDeleted(@NonNull HistoryItem item) { public void handleBookmarkDeleted(@NonNull HistoryItem item) {
mBookmarks.remove(item); mBookmarks.remove(item);
if (item.isFolder()) { if (item.isFolder()) {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false); setBookmarksShown(null, false);
} else { } else {
mBookmarkAdapter.notifyDataSetChanged(); 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<List<HistoryItem>>() {
@Override
public void onItem(@Nullable final List<HistoryItem> item) {
mBookmarksSubscription = null;
Preconditions.checkNonNull(item);
mUiModel.setCurrentFolder(folder);
if (folder == null) {
mBookmarkManager.getFolders()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override
public void onItem(@Nullable List<HistoryItem> folders) {
Preconditions.checkNonNull(folders);
item.addAll(folders);
setBookmarkDataSet(item, animate);
}
});
} else {
setBookmarkDataSet(item, animate);
}
}
});
}
private void setBookmarkDataSet(@NonNull List<HistoryItem> items, boolean animate) { private void setBookmarkDataSet(@NonNull List<HistoryItem> items, boolean animate) {
mBookmarks.clear(); mBookmarks.clear();
mBookmarks.addAll(items); mBookmarks.addAll(items);
mBookmarkAdapter.notifyDataSetChanged(); mBookmarkAdapter.notifyDataSetChanged();
final int resource; final int resource;
if (mBookmarkManager.isRootFolder()) { if (mUiModel.isRootFolder()) {
resource = R.drawable.ic_action_star; resource = R.drawable.ic_action_star;
} else { } else {
resource = R.drawable.ic_action_back; resource = R.drawable.ic_action_back;
@ -339,10 +357,10 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
@Override @Override
public void navigateBack() { public void navigateBack() {
if (mBookmarkManager.isRootFolder()) { if (mUiModel.isRootFolder()) {
mUiController.closeBookmarksDrawer(); mUiController.closeBookmarksDrawer();
} else { } else {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true); setBookmarksShown(null, true);
mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex); mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex);
} }
} }
@ -350,8 +368,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
@Override @Override
public void handleUpdatedUrl(@NonNull String url) { public void handleUpdatedUrl(@NonNull String url) {
updateBookmarkIndicator(url); updateBookmarkIndicator(url);
String folder = mBookmarkManager.getCurrentFolder(); String folder = mUiModel.getCurrentFolder();
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(folder, true), false); setBookmarksShown(folder, false);
} }
static class BookmarkViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { static class BookmarkViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

19
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.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem; 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.preference.PreferenceManager;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
@ -65,7 +65,7 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable {
private final Comparator<HistoryItem> mFilterComparator = new SuggestionsComparator(); private final Comparator<HistoryItem> mFilterComparator = new SuggestionsComparator();
@Inject BookmarkManager mBookmarkManager; @Inject BookmarkModel mBookmarkManager;
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@Inject Application mApplication; @Inject Application mApplication;
@ -102,15 +102,16 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable {
} }
public void refreshBookmarks() { public void refreshBookmarks() {
Completable.create(new CompletableAction() { mBookmarkManager.getAllBookmarks()
.subscribeOn(Schedulers.io())
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override @Override
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { public void onItem(@Nullable List<HistoryItem> item) {
Preconditions.checkNonNull(item);
mAllBookmarks.clear(); mAllBookmarks.clear();
mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true)); mAllBookmarks.addAll(item);
subscriber.onComplete();
} }
}).subscribeOn(Schedulers.io()).subscribe(); });
} }
@Override @Override

2
app/src/main/java/acr/browser/lightning/utils/ThemeUtils.java

@ -9,7 +9,6 @@ import android.graphics.ColorFilter;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.support.annotation.AttrRes; import android.support.annotation.AttrRes;
@ -19,7 +18,6 @@ import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.graphics.drawable.DrawableCompat;
import android.util.TypedValue; import android.util.TypedValue;
import android.widget.ImageView;
import acr.browser.lightning.R; import acr.browser.lightning.R;

2
app/src/main/java/acr/browser/lightning/utils/WebUtils.java

@ -13,7 +13,7 @@ import android.webkit.WebViewDatabase;
import com.anthonycr.bonsai.Schedulers; import com.anthonycr.bonsai.Schedulers;
import acr.browser.lightning.database.HistoryModel; import acr.browser.lightning.database.history.HistoryModel;
/** /**
* Copyright 8/4/2015 Anthony Restaino * Copyright 8/4/2015 Anthony Restaino

6
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.WebSettings.PluginState;
import android.webkit.WebView; import android.webkit.WebView;
import com.anthonycr.bonsai.Schedulers;
import com.anthonycr.bonsai.Single; import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction; import com.anthonycr.bonsai.SingleAction;
import com.anthonycr.bonsai.SingleOnSubscribe; 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.HistoryPage;
import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.constant.StartPage;
import acr.browser.lightning.controller.UIController; import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.download.LightningDownloadListener;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import com.anthonycr.bonsai.Schedulers;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ProxyUtils;
import acr.browser.lightning.utils.UrlUtils; import acr.browser.lightning.utils.UrlUtils;
@ -110,7 +107,6 @@ public class LightningView {
@Inject PreferenceManager mPreferences; @Inject PreferenceManager mPreferences;
@Inject LightningDialogBuilder mBookmarksDialogBuilder; @Inject LightningDialogBuilder mBookmarksDialogBuilder;
@Inject ProxyUtils mProxyUtils; @Inject ProxyUtils mProxyUtils;
@Inject BookmarkManager mBookmarkManager;
public LightningView(@NonNull Activity activity, @Nullable String url, boolean isIncognito) { public LightningView(@NonNull Activity activity, @Nullable String url, boolean isIncognito) {
BrowserApp.getAppComponent().inject(this); BrowserApp.getAppComponent().inject(this);

Loading…
Cancel
Save