Browse Source

Merge pull request #284 from stefano-cliqz/dev

Refactoring: Bookmarks as Fragment
master
Anthony Restaino 9 years ago
parent
commit
dcc67fbdb6
  1. 7
      app/build.gradle
  2. 2
      app/src/LightningPlus/java/acr/browser/lightning/utils/ProxyUtils.java
  3. 5
      app/src/main/AndroidManifest.xml
  4. 727
      app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java
  5. 5
      app/src/main/java/acr/browser/lightning/activity/MainActivity.java
  6. 31
      app/src/main/java/acr/browser/lightning/app/AppComponent.java
  7. 41
      app/src/main/java/acr/browser/lightning/app/AppModule.java
  8. 13
      app/src/main/java/acr/browser/lightning/app/BrowserApp.java
  9. 92
      app/src/main/java/acr/browser/lightning/bus/BookmarkEvents.java
  10. 44
      app/src/main/java/acr/browser/lightning/bus/BrowserEvents.java
  11. 38
      app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java
  12. 5
      app/src/main/java/acr/browser/lightning/constant/Constants.java
  13. 2
      app/src/main/java/acr/browser/lightning/constant/HistoryPage.java
  14. 2
      app/src/main/java/acr/browser/lightning/constant/StartPage.java
  15. 2
      app/src/main/java/acr/browser/lightning/controller/BrowserController.java
  16. 427
      app/src/main/java/acr/browser/lightning/database/BookmarkManager.java
  17. 201
      app/src/main/java/acr/browser/lightning/dialog/BookmarksDialogBuilder.java
  18. 8
      app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java
  19. 361
      app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java
  20. 7
      app/src/main/java/acr/browser/lightning/object/SearchAdapter.java
  21. 11
      app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java
  22. 127
      app/src/main/java/acr/browser/lightning/utils/DownloadImageTask.java
  23. 4
      app/src/main/java/acr/browser/lightning/utils/Utils.java
  24. 9
      app/src/main/java/acr/browser/lightning/view/LightningView.java
  25. 15
      app/src/main/res/layout/activity_main.xml
  26. 7
      app/src/main/res/layout/bookmark_drawer.xml
  27. 29
      app/src/main/res/layout/dialog_edit_bookmark.xml
  28. 5
      app/src/main/res/raw/default_bookmarks.dat
  29. 1
      build.gradle
  30. 2
      external/netcipher

7
app/build.gradle

@ -1,4 +1,5 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android { android {
compileSdkVersion 23 compileSdkVersion 23
@ -48,9 +49,15 @@ dependencies {
compile 'com.android.support:design:23.0.0' compile 'com.android.support:design:23.0.0'
compile 'com.android.support:recyclerview-v7:23.0.0' compile 'com.android.support:recyclerview-v7:23.0.0'
compile 'org.jsoup:jsoup:1.8.1' compile 'org.jsoup:jsoup:1.8.1'
compile 'com.squareup:otto:1.3.8'
compile 'com.google.dagger:dagger:2.0.1'
apt 'com.google.dagger:dagger-compiler:2.0.1'
// Only Lightning Plus needs the proxy libraries // Only Lightning Plus needs the proxy libraries
lightningPlusCompile 'net.i2p.android:client:0.7' lightningPlusCompile 'net.i2p.android:client:0.7'
lightningPlusCompile(project(':libnetcipher')) lightningPlusCompile(project(':libnetcipher'))
provided 'javax.annotation:jsr250-api:1.0'
// git submodule foreach git reset --hard // git submodule foreach git reset --hard
// git submodule update --remote // git submodule update --remote
} }

2
app/src/LightningPlus/java/acr/browser/lightning/utils/ProxyUtils.java

@ -10,7 +10,7 @@ import android.util.Log;
import net.i2p.android.ui.I2PAndroidHelper; import net.i2p.android.ui.I2PAndroidHelper;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import info.guardianproject.netcipher.proxy.OrbotHelper; import info.guardianproject.netcipher.proxy.OrbotHelper;

5
app/src/main/AndroidManifest.xml

@ -20,7 +20,7 @@
android:required="false" /> android:required="false" />
<application <application
android:name=".activity.BrowserApp" android:name=".app.BrowserApp"
android:allowBackup="true" android:allowBackup="true"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -110,7 +110,8 @@
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/Theme.DarkTheme" android:theme="@style/Theme.DarkTheme"
android:windowSoftInputMode="stateHidden" > android:windowSoftInputMode="stateHidden"
android:process=":incognito">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.INCOGNITO" /> <action android:name="android.intent.action.INCOGNITO" />

727
app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java

@ -30,7 +30,6 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -65,7 +64,6 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener; import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
@ -86,26 +84,27 @@ import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
import android.widget.VideoView; import android.widget.VideoView;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.bus.BookmarkEvents;
import acr.browser.lightning.bus.BrowserEvents;
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.constant.HistoryPage; import acr.browser.lightning.constant.HistoryPage;
@ -113,6 +112,7 @@ import acr.browser.lightning.controller.BrowserController;
import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryDatabase; import acr.browser.lightning.database.HistoryDatabase;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.dialog.BookmarksDialogBuilder;
import acr.browser.lightning.object.ClickHandler; import acr.browser.lightning.object.ClickHandler;
import acr.browser.lightning.object.DrawerArrowDrawable; import acr.browser.lightning.object.DrawerArrowDrawable;
import acr.browser.lightning.object.SearchAdapter; import acr.browser.lightning.object.SearchAdapter;
@ -133,9 +133,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
private DrawerLayout mDrawerLayout; private DrawerLayout mDrawerLayout;
private FrameLayout mBrowserFrame; private FrameLayout mBrowserFrame;
private FullscreenHolder mFullscreenContainer; private FullscreenHolder mFullscreenContainer;
private ListView mDrawerListRight;
private RecyclerView mDrawerListLeft; private RecyclerView mDrawerListLeft;
private LinearLayout mDrawerLeft, mDrawerRight, mUiLayout, mToolbarLayout; private ViewGroup mDrawerLeft, mDrawerRight, mUiLayout, mToolbarLayout;
private RelativeLayout mSearchBar; private RelativeLayout mSearchBar;
// List // List
@ -147,12 +146,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// Views // Views
private AnimatedProgressBar mProgressBar; private AnimatedProgressBar mProgressBar;
private AutoCompleteTextView mSearch; private AutoCompleteTextView mSearch;
private ImageView mArrowImage, mBookmarkTitleImage, mBookmarkImage; private ImageView mArrowImage;
private VideoView mVideoView; private VideoView mVideoView;
private View mCustomView, mVideoProgressView; private View mCustomView, mVideoProgressView;
// Adapter // Adapter
private BookmarkViewAdapter mBookmarkAdapter;
private LightningViewAdapter mTabAdapter; private LightningViewAdapter mTabAdapter;
private SearchAdapter mSearchAdapter; private SearchAdapter mSearchAdapter;
@ -176,11 +174,24 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// Storage // Storage
private HistoryDatabase mHistoryDatabase; private HistoryDatabase mHistoryDatabase;
private BookmarkManager mBookmarkManager;
private PreferenceManager mPreferences; private PreferenceManager mPreferences;
// The singleton BookmarkManager
@Inject
BookmarkManager bookmarkManager;
// Event bus
@Inject
Bus eventBus;
@Inject
BookmarkPage bookmarkPage;
@Inject
BookmarksDialogBuilder bookmarksDialogBuilder;
// Image // Image
private Bitmap mDefaultVideoPoster, mWebpageBitmap, mFolderBitmap; private Bitmap mDefaultVideoPoster, mWebpageBitmap;
private final ColorDrawable mBackground = new ColorDrawable(); private final ColorDrawable mBackground = new ColorDrawable();
private Drawable mDeleteIcon, mRefreshIcon, mClearIcon, mIcon; private Drawable mDeleteIcon, mRefreshIcon, mClearIcon, mIcon;
private DrawerArrowDrawable mArrowDrawable; private DrawerArrowDrawable mArrowDrawable;
@ -196,7 +207,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams( private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
abstract boolean isIncognito(); public abstract boolean isIncognito();
abstract void initializeTabs(); abstract void initializeTabs();
@ -210,6 +221,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
BrowserApp.getAppComponent().inject(this);
initialize(); initialize();
} }
@ -241,11 +253,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// Drawer stutters otherwise // Drawer stutters otherwise
mDrawerLeft.setLayerType(View.LAYER_TYPE_HARDWARE, null); mDrawerLeft.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerRight = (LinearLayout) findViewById(R.id.right_drawer); mDrawerRight = (ViewGroup) findViewById(R.id.right_drawer);
mDrawerRight.setLayerType(View.LAYER_TYPE_HARDWARE, null); mDrawerRight.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mDrawerListRight = (ListView) findViewById(R.id.right_drawer_list);
mBookmarkTitleImage = (ImageView) findViewById(R.id.starIcon);
mBookmarkTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
ImageView tabTitleImage = (ImageView) findViewById(R.id.plusIcon); ImageView tabTitleImage = (ImageView) findViewById(R.id.plusIcon);
tabTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); tabTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
@ -257,7 +266,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
mDrawerLayout.setDrawerListener(new DrawerLocker()); mDrawerLayout.setDrawerListener(new DrawerLocker());
mWebpageBitmap = ThemeUtils.getThemedBitmap(this, R.drawable.ic_webpage, mDarkTheme); mWebpageBitmap = ThemeUtils.getThemedBitmap(this, R.drawable.ic_webpage, mDarkTheme);
mFolderBitmap = ThemeUtils.getThemedBitmap(this, R.drawable.ic_folder, mDarkTheme);
mHomepage = mPreferences.getHomepage(); mHomepage = mPreferences.getHomepage();
@ -283,11 +291,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
mDrawerListLeft.setAdapter(mTabAdapter); mDrawerListLeft.setAdapter(mTabAdapter);
// mDrawerListLeft.setOnItemClickListener(new DrawerItemClickListener());
// mDrawerListLeft.setOnItemLongClickListener(new DrawerItemLongClickListener());
mDrawerListRight.setOnItemClickListener(new BookmarkItemClickListener());
mDrawerListRight.setOnItemLongClickListener(new BookmarkItemLongClickListener());
mHistoryDatabase = HistoryDatabase.getInstance(getApplicationContext()); mHistoryDatabase = HistoryDatabase.getInstance(getApplicationContext());
@ -324,12 +327,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
setupFrameLayoutButton(R.id.action_back, R.id.icon_back); setupFrameLayoutButton(R.id.action_back, R.id.icon_back);
setupFrameLayoutButton(R.id.action_forward, R.id.icon_forward); setupFrameLayoutButton(R.id.action_forward, R.id.icon_forward);
setupFrameLayoutButton(R.id.action_add_bookmark, R.id.icon_star);
setupFrameLayoutButton(R.id.action_toggle_desktop, R.id.icon_desktop); setupFrameLayoutButton(R.id.action_toggle_desktop, R.id.icon_desktop);
setupFrameLayoutButton(R.id.action_reading, R.id.icon_reading); setupFrameLayoutButton(R.id.action_reading, R.id.icon_reading);
mBookmarkImage = (ImageView) findViewById(R.id.icon_star);
// create the search EditText in the ToolBar // create the search EditText in the ToolBar
mSearch = (AutoCompleteTextView) actionBar.getCustomView().findViewById(R.id.search); mSearch = (AutoCompleteTextView) actionBar.getCustomView().findViewById(R.id.search);
mUntitledTitle = getString(R.string.untitled); mUntitledTitle = getString(R.string.untitled);
@ -358,38 +358,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
@Override @Override
public void run() { public void run() {
mBookmarkManager = BookmarkManager.getInstance(mActivity.getApplicationContext());
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
if (mBookmarkList.isEmpty() && mPreferences.getDefaultBookmarks()) {
for (String[] array : BookmarkManager.DEFAULT_BOOKMARKS) {
HistoryItem bookmark = new HistoryItem(array[0], array[1]);
if (mBookmarkManager.addBookmark(bookmark)) {
mBookmarkList.add(bookmark);
}
}
Collections.sort(mBookmarkList, new BookmarkManager.SortIgnoreCase());
mPreferences.setDefaultBookmarks(false);
}
mBookmarkAdapter = new BookmarkViewAdapter(mActivity, R.layout.bookmark_list_item,
mBookmarkList);
mDrawerListRight.setAdapter(mBookmarkAdapter);
initializeSearchSuggestions(mSearch); initializeSearchSuggestions(mSearch);
} }
}).run(); }).run();
View view = findViewById(R.id.bookmark_back_button);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mBookmarkManager == null)
return;
if (!mBookmarkManager.isRootFolder()) {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true);
}
}
});
mDrawerLayout.setDrawerShadow(R.drawable.drawer_right_shadow, GravityCompat.END); mDrawerLayout.setDrawerShadow(R.drawable.drawer_right_shadow, GravityCompat.END);
mDrawerLayout.setDrawerShadow(R.drawable.drawer_left_shadow, GravityCompat.START); mDrawerLayout.setDrawerShadow(R.drawable.drawer_left_shadow, GravityCompat.START);
@ -807,7 +780,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
return true; return true;
case R.id.action_add_bookmark: case R.id.action_add_bookmark:
if (mCurrentView != null && !mCurrentView.getUrl().startsWith(Constants.FILE)) { if (mCurrentView != null && !mCurrentView.getUrl().startsWith(Constants.FILE)) {
addBookmark(mCurrentView.getTitle(), mCurrentView.getUrl()); eventBus
.post(new BrowserEvents.AddBookmark(mCurrentView.getTitle(),
mCurrentView.getUrl()));
} }
return true; return true;
case R.id.action_find: case R.id.action_find:
@ -823,75 +798,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
} }
/**
* refreshes the underlying list of the Bookmark adapter since the bookmark
* adapter doesn't always change when notifyDataChanged gets called.
*/
private void notifyBookmarkDataSetChanged() {
if (mBookmarkAdapter != null)
mBookmarkAdapter.notifyDataSetChanged();
}
private void addBookmark(String title, String url) {
HistoryItem bookmark = new HistoryItem(url, title);
if (mBookmarkManager.addBookmark(bookmark)) {
mBookmarkList.add(bookmark);
Collections.sort(mBookmarkList, new BookmarkManager.SortIgnoreCase());
notifyBookmarkDataSetChanged();
mSearchAdapter.refreshBookmarks();
updateBookmarkIndicator(mCurrentView.getUrl());
}
}
private void setBookmarkDataSet(List<HistoryItem> items, boolean animate) {
mBookmarkList.clear();
mBookmarkList.addAll(items);
notifyBookmarkDataSetChanged();
final int resource;
if (mBookmarkManager.isRootFolder())
resource = R.drawable.ic_action_star;
else
resource = R.drawable.ic_action_back;
final Animation startRotation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mBookmarkTitleImage.setRotationY(90 * interpolatedTime);
}
};
final Animation finishRotation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mBookmarkTitleImage.setRotationY((-90) + (90 * interpolatedTime));
}
};
startRotation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mBookmarkTitleImage.setImageResource(resource);
mBookmarkTitleImage.startAnimation(finishRotation);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
startRotation.setInterpolator(new AccelerateInterpolator());
finishRotation.setInterpolator(new DecelerateInterpolator());
startRotation.setDuration(250);
finishRotation.setDuration(250);
if (animate) {
mBookmarkTitleImage.startAnimation(startRotation);
} else {
mBookmarkTitleImage.setImageResource(resource);
}
}
/** /**
* method that shows a dialog asking what string the user wishes to search * method that shows a dialog asking what string the user wishes to search
* for. It highlights the text entered. * for. It highlights the text entered.
@ -991,146 +897,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
} }
private class BookmarkItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mBookmarkList.get(position).isFolder()) {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(mBookmarkList.get(position).getTitle(), true), true);
return;
}
if (mCurrentView != null) {
mCurrentView.loadUrl(mBookmarkList.get(position).getUrl());
}
// keep any jank from happening when the drawer is closed after the
// URL starts to load
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
mDrawerLayout.closeDrawer(mDrawerRight);
}
}, 150);
}
}
private class BookmarkItemLongClickListener implements ListView.OnItemLongClickListener {
@Override
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, final int position, long arg3) {
longPressBookmarkLink(mBookmarkList.get(position).getUrl());
return true;
}
}
/**
* Takes in the id of which bookmark was selected and shows a dialog that
* allows the user to rename and change the url of the bookmark
*
* @param id which id in the list was chosen
*/
private synchronized void editBookmark(final int id) {
final AlertDialog.Builder editBookmarkDialog = new AlertDialog.Builder(mActivity);
editBookmarkDialog.setTitle(R.string.title_edit_bookmark);
final EditText getTitle = new EditText(mActivity);
getTitle.setHint(R.string.hint_title);
getTitle.setText(mBookmarkList.get(id).getTitle());
getTitle.setSingleLine();
final EditText getUrl = new EditText(mActivity);
getUrl.setHint(R.string.hint_url);
getUrl.setText(mBookmarkList.get(id).getUrl());
getUrl.setSingleLine();
final AutoCompleteTextView getFolder = new AutoCompleteTextView(mActivity);
getFolder.setHint(R.string.folder);
getFolder.setText(mBookmarkList.get(id).getFolder());
getFolder.setSingleLine();
List<String> folders = mBookmarkManager.getFolderTitles();
ArrayAdapter<String> suggestionsAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, folders);
getFolder.setThreshold(1);
getFolder.setAdapter(suggestionsAdapter);
LinearLayout layout = new LinearLayout(mActivity);
layout.setOrientation(LinearLayout.VERTICAL);
int padding = Utils.dpToPx(10);
layout.setPadding(padding, padding, padding, padding);
layout.addView(getTitle);
layout.addView(getUrl);
layout.addView(getFolder);
editBookmarkDialog.setView(layout);
editBookmarkDialog.setPositiveButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
HistoryItem item = new HistoryItem();
String currentFolder = mBookmarkList.get(id).getFolder();
item.setTitle(getTitle.getText().toString());
item.setUrl(getUrl.getText().toString());
item.setUrl(getUrl.getText().toString());
item.setFolder(getFolder.getText().toString());
mBookmarkManager.editBookmark(mBookmarkList.get(id), item);
List<HistoryItem> list = mBookmarkManager.getBookmarksFromFolder(currentFolder, true);
if (list.isEmpty()) {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true);
} else {
setBookmarkDataSet(list, false);
}
if (mCurrentView != null && mCurrentView.getUrl().startsWith(Constants.FILE)
&& mCurrentView.getUrl().endsWith(BookmarkPage.FILENAME)) {
openBookmarkPage(mWebView);
}
if (mCurrentView != null) {
updateBookmarkIndicator(mCurrentView.getUrl());
}
}
});
editBookmarkDialog.show();
}
/**
* Show a dialog to rename a folder
*
* @param id the position of the HistoryItem (folder) in the bookmark list
*/
private synchronized void renameFolder(final int id) {
final AlertDialog.Builder editFolderDialog = new AlertDialog.Builder(mActivity);
editFolderDialog.setTitle(R.string.title_rename_folder);
final EditText getTitle = new EditText(mActivity);
getTitle.setHint(R.string.hint_title);
getTitle.setText(mBookmarkList.get(id).getTitle());
getTitle.setSingleLine();
LinearLayout layout = new LinearLayout(mActivity);
layout.setOrientation(LinearLayout.VERTICAL);
int padding = Utils.dpToPx(10);
layout.setPadding(padding, padding, padding, padding);
layout.addView(getTitle);
editFolderDialog.setView(layout);
editFolderDialog.setPositiveButton(getResources().getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String oldTitle = mBookmarkList.get(id).getTitle();
String newTitle = getTitle.getText().toString();
mBookmarkManager.renameFolder(oldTitle, newTitle);
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
if (mCurrentView != null && mCurrentView.getUrl().startsWith(Constants.FILE)
&& mCurrentView.getUrl().endsWith(BookmarkPage.FILENAME)) {
openBookmarkPage(mWebView);
}
if (mCurrentView != null) {
updateBookmarkIndicator(mCurrentView.getUrl());
}
}
});
editFolderDialog.show();
}
/** /**
* displays the WebView contained in the LightningView Also handles the * displays the WebView contained in the LightningView Also handles the
* removal of previous views * removal of previous views
@ -1192,7 +958,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
}, 150); }, 150);
updateBookmarkIndicator(mWebView.getUrl()); // Should update the bookmark status in BookmarksFragment
eventBus
.post(new BrowserEvents.CurrentPageUrl(mCurrentView.getUrl()));
// new Handler().postDelayed(new Runnable() { // new Handler().postDelayed(new Runnable() {
// @Override // @Override
@ -1218,7 +986,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
source = intent.getExtras().getString("SOURCE"); source = intent.getExtras().getString("SOURCE");
} }
if (num == 1) { if (num == 1) {
mCurrentView.loadUrl(url); loadUrlInCurrentView(url);
} else if (url != null) { } else if (url != null) {
if (url.startsWith(Constants.FILE)) { if (url.startsWith(Constants.FILE)) {
Utils.showSnackbar(this, R.string.message_blocked_local); Utils.showSnackbar(this, R.string.message_blocked_local);
@ -1229,6 +997,16 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
} }
private void loadUrlInCurrentView(final String url) {
if (mCurrentView == null) {
// This is a problem, probably an assert will be better than a return
return;
}
mCurrentView.loadUrl(url);
eventBus.post(new BrowserEvents.CurrentPageUrl(url));
}
@Override @Override
public void closeEmptyTab() { public void closeEmptyTab() {
if (mWebView != null && mWebView.copyBackForwardList().getSize() == 0) { if (mWebView != null && mWebView.copyBackForwardList().getSize() == 0) {
@ -1401,11 +1179,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
if (mDrawerLayout.isDrawerOpen(mDrawerLeft)) { if (mDrawerLayout.isDrawerOpen(mDrawerLeft)) {
mDrawerLayout.closeDrawer(mDrawerLeft); mDrawerLayout.closeDrawer(mDrawerLeft);
} else if (mDrawerLayout.isDrawerOpen(mDrawerRight)) { } else if (mDrawerLayout.isDrawerOpen(mDrawerRight)) {
if (!mBookmarkManager.isRootFolder()) { eventBus
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true); .post(new BrowserEvents.UserPressedBack());
} else {
mDrawerLayout.closeDrawer(mDrawerRight);
}
} else { } else {
if (mCurrentView != null) { if (mCurrentView != null) {
Log.d(Constants.TAG, "onBackPressed"); Log.d(Constants.TAG, "onBackPressed");
@ -1443,6 +1218,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
if (isIncognito() && isFinishing()) { if (isIncognito() && isFinishing()) {
overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_down_out); overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_down_out);
} }
eventBus.unregister(busEventListener);
} }
void saveOpenTabs() { void saveOpenTabs() {
@ -1492,7 +1269,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
mCurrentView.onResume(); mCurrentView.onResume();
} }
mHistoryDatabase = HistoryDatabase.getInstance(getApplicationContext()); mHistoryDatabase = HistoryDatabase.getInstance(getApplicationContext());
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
initializePreferences(); initializePreferences();
for (int n = 0; n < mWebViewList.size(); n++) { for (int n = 0; n < mWebViewList.size(); n++) {
if (mWebViewList.get(n) != null) { if (mWebViewList.get(n) != null) {
@ -1507,6 +1283,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(NETWORK_BROADCAST_ACTION); filter.addAction(NETWORK_BROADCAST_ACTION);
registerReceiver(mNetworkReceiver, filter); registerReceiver(mNetworkReceiver, filter);
eventBus.register(busEventListener);
} }
/** /**
@ -1521,7 +1299,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
query = query.trim(); query = query.trim();
mCurrentView.stopLoading(); mCurrentView.stopLoading();
if (mCurrentView != null) { if (mCurrentView != null) {
mCurrentView.loadUrl(UrlUtils.smartUrlFilter(query, true, searchUrl)); loadUrlInCurrentView(UrlUtils.smartUrlFilter(query, true, searchUrl));
} }
} }
@ -1667,6 +1445,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
/** /**
* TODO Can this method been removed?
* Animates the color of the toolbar from one color to another. Optionally animates * Animates the color of the toolbar from one color to another. Optionally animates
* the color of the tab background, for use when the tabs are displayed on the top * the color of the tab background, for use when the tabs are displayed on the top
* of the screen. * of the screen.
@ -1721,177 +1500,13 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
}); });
} }
public class BookmarkViewAdapter extends ArrayAdapter<HistoryItem> {
final Context context;
List<HistoryItem> data = null;
final int layoutResourceId;
final Bitmap folderIcon;
public BookmarkViewAdapter(Context context, int layoutResourceId, List<HistoryItem> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
this.folderIcon = mFolderBitmap;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
BookmarkViewHolder holder;
if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new BookmarkViewHolder();
holder.txtTitle = (TextView) row.findViewById(R.id.textBookmark);
holder.favicon = (ImageView) row.findViewById(R.id.faviconBookmark);
row.setTag(holder);
} else {
holder = (BookmarkViewHolder) row.getTag();
}
ViewCompat.jumpDrawablesToCurrentState(row);
HistoryItem web = data.get(position);
holder.txtTitle.setText(web.getTitle());
holder.favicon.setImageBitmap(mWebpageBitmap);
if (web.isFolder()) {
holder.favicon.setImageBitmap(this.folderIcon);
} else if (web.getBitmap() == null) {
getImage(holder.favicon, web);
} else {
holder.favicon.setImageBitmap(web.getBitmap());
}
return row;
}
class BookmarkViewHolder {
TextView txtTitle;
ImageView favicon;
}
}
private void getImage(ImageView image, HistoryItem web) {
new DownloadImageTask(image, web).execute(web.getUrl());
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
final ImageView bmImage;
final HistoryItem mWeb;
public DownloadImageTask(ImageView bmImage, HistoryItem web) {
this.bmImage = bmImage;
this.mWeb = web;
}
protected Bitmap doInBackground(String... urls) {
String url = urls[0];
Bitmap mIcon = null;
// unique path for each url that is bookmarked.
String hash = String.valueOf(Utils.getDomainName(url).hashCode());
File image = new File(mActivity.getCacheDir(), hash + ".png");
String urldisplay;
try {
urldisplay = Utils.getProtocol(url) + getDomainName(url) + "/favicon.ico";
} catch (URISyntaxException e) {
e.printStackTrace();
urldisplay = "https://www.google.com/s2/favicons?domain_url=" + url;
}
// checks to see if the image exists
if (!image.exists()) {
FileOutputStream fos = null;
InputStream in = null;
try {
// if not, download it...
URL urlDownload = new URL(urldisplay);
HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection();
connection.setDoInput(true);
connection.connect();
in = connection.getInputStream();
if (in != null) {
mIcon = BitmapFactory.decodeStream(in);
}
// ...and cache it
if (mIcon != null) {
fos = new FileOutputStream(image);
mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
Log.d(Constants.TAG, "Downloaded: " + urldisplay);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Utils.close(in);
Utils.close(fos);
}
} else {
// if it exists, retrieve it from the cache
mIcon = BitmapFactory.decodeFile(image.getPath());
}
if (mIcon == null) {
InputStream in = null;
FileOutputStream fos = null;
try {
// if not, download it...
URL urlDownload = new URL("https://www.google.com/s2/favicons?domain_url="
+ url);
HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection();
connection.setDoInput(true);
connection.connect();
in = connection.getInputStream();
if (in != null) {
mIcon = BitmapFactory.decodeStream(in);
}
// ...and cache it
if (mIcon != null) {
fos = new FileOutputStream(image);
mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Utils.close(in);
Utils.close(fos);
}
}
if (mIcon == null) {
return mWebpageBitmap;
} else {
return mIcon;
}
}
protected void onPostExecute(Bitmap result) {
Bitmap fav = Utils.padFavicon(result);
bmImage.setImageBitmap(fav);
mWeb.setBitmap(fav);
notifyBookmarkDataSetChanged();
}
}
private static String getDomainName(String url) throws URISyntaxException {
URI uri = new URI(url);
String domain = uri.getHost();
if (domain == null) {
return url;
}
return domain.startsWith("www.") ? domain.substring(4) : domain;
}
@Override @Override
public void updateUrl(String url, boolean shortUrl) { public void updateUrl(String url, boolean shortUrl) {
if (url == null || mSearch == null || mSearch.hasFocus()) { if (url == null || mSearch == null || mSearch.hasFocus()) {
return; return;
} }
eventBus
.post(new BrowserEvents.CurrentPageUrl(url));
if (shortUrl && !url.startsWith(Constants.FILE)) { if (shortUrl && !url.startsWith(Constants.FILE)) {
switch (mPreferences.getUrlBoxContentChoice()) { switch (mPreferences.getUrlBoxContentChoice()) {
case 0: // Default, show only the domain case 0: // Default, show only the domain
@ -2005,7 +1620,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
@Override @Override
public void run() { public void run() {
mCurrentView.loadUrl(HistoryPage.getHistoryPage(mActivity)); loadUrlInCurrentView(HistoryPage.getHistoryPage(mActivity));
mSearch.setText(""); mSearch.setText("");
} }
@ -2057,9 +1672,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} finally { } finally {
Utils.close(outputStream); Utils.close(outputStream);
} }
File bookmarkWebPage = new File(mActivity.getFilesDir(), BookmarkPage.FILENAME); File bookmarkWebPage = new File(mActivity.getFilesDir(), Constants.BOOKMARKS_FILENAME);
BookmarkPage.buildBookmarkPage(this, null, mBookmarkManager.getBookmarksFromFolder(null, true)); bookmarkPage.buildBookmarkPage(null, bookmarkManager.getBookmarksFromFolder(null, true));
view.loadUrl(Constants.FILE + bookmarkWebPage); view.loadUrl(Constants.FILE + bookmarkWebPage);
} }
@ -2492,12 +2107,12 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
final String newUrl = result.getExtra(); final String newUrl = result.getExtra();
longPressHistoryLink(newUrl); longPressHistoryLink(newUrl);
} }
} else if (currentUrl.endsWith(BookmarkPage.FILENAME)) { } else if (currentUrl.endsWith(Constants.BOOKMARKS_FILENAME)) {
if (url != null) { if (url != null) {
longPressBookmarkLink(url); bookmarksDialogBuilder.showLongPressedDialogForUrl(this, url);
} else if (result != null && result.getExtra() != null) { } else if (result != null && result.getExtra() != null) {
final String newUrl = result.getExtra(); final String newUrl = result.getExtra();
longPressBookmarkLink(newUrl); bookmarksDialogBuilder.showLongPressedDialogForUrl(this, newUrl);
} }
} }
} else { } else {
@ -2522,103 +2137,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
} }
private void longPressFolder(String url) {
String title;
if (url.startsWith(Constants.FILE)) {
// We are getting the title from the url
// Strip '-bookmarks.html' from the end of the url
title = url.substring(0, url.length() - BookmarkPage.FILENAME.length() - 1);
// Strip the beginning of the url off and leave only the title
title = title.substring(Constants.FILE.length() + mActivity.getFilesDir().toString().length() + 1);
} else if (url.startsWith(Constants.FOLDER)) {
title = url.substring(Constants.FOLDER.length());
} else {
title = url;
}
final int position = BookmarkManager.getIndexOfBookmark(mBookmarkList, Constants.FOLDER + title);
if (position == -1) {
return;
}
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
renameFolder(position);
break;
case DialogInterface.BUTTON_NEGATIVE:
mBookmarkManager.deleteFolder(mBookmarkList.get(position).getTitle());
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
if (mCurrentView != null && mCurrentView.getUrl().startsWith(Constants.FILE)
&& mCurrentView.getUrl().endsWith(BookmarkPage.FILENAME)) {
openBookmarkPage(mWebView);
}
if (mCurrentView != null) {
updateBookmarkIndicator(mCurrentView.getUrl());
}
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(R.string.action_folder)
.setMessage(R.string.dialog_folder)
.setCancelable(true)
.setPositiveButton(R.string.action_rename, dialogClickListener)
.setNegativeButton(R.string.action_delete, dialogClickListener)
.show();
}
private void longPressBookmarkLink(final String url) {
if (url.startsWith(Constants.FILE) || url.startsWith(Constants.FOLDER)) {
longPressFolder(url);
return;
}
final int position = BookmarkManager.getIndexOfBookmark(mBookmarkList, url);
if (position == -1) {
return;
}
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
newTab(mBookmarkList.get(position).getUrl(), false);
mDrawerLayout.closeDrawers();
break;
case DialogInterface.BUTTON_NEGATIVE:
if (mBookmarkManager.deleteBookmark(mBookmarkList.get(position))) {
mBookmarkList.remove(position);
notifyBookmarkDataSetChanged();
mSearchAdapter.refreshBookmarks();
openBookmarks();
if (mCurrentView != null) {
updateBookmarkIndicator(mCurrentView.getUrl());
}
}
break;
case DialogInterface.BUTTON_NEUTRAL:
editBookmark(position);
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(R.string.action_bookmarks)
.setMessage(R.string.dialog_bookmark)
.setCancelable(true)
.setPositiveButton(R.string.action_new_tab, dialogClickListener)
.setNegativeButton(R.string.action_delete, dialogClickListener)
.setNeutralButton(R.string.action_edit, dialogClickListener)
.show();
}
private void longPressHistoryLink(final String url) { private void longPressHistoryLink(final String url) {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override @Override
@ -2636,7 +2154,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
case DialogInterface.BUTTON_NEUTRAL: case DialogInterface.BUTTON_NEUTRAL:
if (mCurrentView != null) { if (mCurrentView != null) {
mCurrentView.loadUrl(url); loadUrlInCurrentView(url);
} }
break; break;
} }
@ -2663,7 +2181,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
break; break;
case DialogInterface.BUTTON_NEGATIVE: case DialogInterface.BUTTON_NEGATIVE:
mCurrentView.loadUrl(url); loadUrlInCurrentView(url);
break; break;
case DialogInterface.BUTTON_NEUTRAL: case DialogInterface.BUTTON_NEUTRAL:
@ -2696,7 +2214,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
break; break;
case DialogInterface.BUTTON_NEGATIVE: case DialogInterface.BUTTON_NEGATIVE:
mCurrentView.loadUrl(url); loadUrlInCurrentView(url);
break; break;
case DialogInterface.BUTTON_NEUTRAL: case DialogInterface.BUTTON_NEUTRAL:
@ -2757,17 +2275,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
} }
} }
@Override
public void updateBookmarkIndicator(String url) {
if (url == null || !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(this), PorterDuff.Mode.SRC_IN);
}
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
switch (v.getId()) { switch (v.getId()) {
@ -2819,11 +2326,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
mCurrentView.reload(); mCurrentView.reload();
closeDrawers(); closeDrawers();
break; break;
case R.id.action_add_bookmark:
if (mCurrentView != null && !mCurrentView.getUrl().startsWith(Constants.FILE)) {
addBookmark(mCurrentView.getTitle(), mCurrentView.getUrl());
}
break;
} }
} }
@ -2869,4 +2371,103 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
PermissionsManager.getInstance().notifyPermissionsChange(permissions); PermissionsManager.getInstance().notifyPermissionsChange(permissions);
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
} }
private final Object busEventListener = new Object() {
/**
* Load the given bookmark in the current tab, used by the the
* {@link acr.browser.lightning.fragment.BookmarksFragment}
* @param event The event as it comes from the bus
*/
@Subscribe
public void loadBookmarkInCurrentTab(final BookmarkEvents.Clicked event) {
loadUrlInCurrentView(event.bookmark.getUrl());
// keep any jank from happening when the drawer is closed after the
// URL starts to load
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
mDrawerLayout.closeDrawer(mDrawerRight);
}
}, 150);
}
/**
* Load the given bookmark in a new tab, used by the the
* {@link acr.browser.lightning.fragment.BookmarksFragment}
* @param event The event as it comes from the bus
*/
@Subscribe
public void loadBookmarkInNewTab(final BookmarkEvents.AsNewTab event) {
newTab(event.bookmark.getUrl(), true);
mDrawerLayout.closeDrawers();
}
/**
* When receive a {@link acr.browser.lightning.bus.BookmarkEvents.WantToBookmarkCurrentPage}
* message this receiver answer firing the
* {@link acr.browser.lightning.bus.BrowserEvents.AddBookmark} message
*
* @param event basically a marker
*/
@Subscribe
public void bookmarkCurrentPage(final BookmarkEvents.WantToBookmarkCurrentPage event) {
eventBus
.post(new BrowserEvents
.AddBookmark(mCurrentView.getTitle(), mCurrentView.getUrl()));
}
/**
* This message is received when a bookmark was added by the
* {@link acr.browser.lightning.fragment.BookmarksFragment}
*
* @param event a marker
*/
@Subscribe
public void bookmarkAdded(final BookmarkEvents.Added event) {
mSearchAdapter.refreshBookmarks();
}
/**
* This is received when the user edit a bookmark
*
* @param event
*/
@Subscribe
public void bookmarkChanged(final BookmarkEvents.BookmarkChanged event) {
if (mCurrentView != null && mCurrentView.getUrl().startsWith(Constants.FILE)
&& mCurrentView.getUrl().endsWith(Constants.BOOKMARKS_FILENAME)) {
openBookmarkPage(mWebView);
}
eventBus
.post(new BrowserEvents.CurrentPageUrl(mCurrentView.getUrl()));
}
/**
* Notify the browser that a bookmark was deleted
*
* @param event
*/
@Subscribe
public void bookmarkDeleted(final BookmarkEvents.Deleted event) {
if (mCurrentView != null && mCurrentView.getUrl().startsWith(Constants.FILE)
&& mCurrentView.getUrl().endsWith(Constants.BOOKMARKS_FILENAME)) {
openBookmarkPage(mWebView);
}
eventBus
.post(new BrowserEvents.CurrentPageUrl(mCurrentView.getUrl()));
}
/**
* The {@link acr.browser.lightning.fragment.BookmarksFragment} send this message on reply
* to {@link acr.browser.lightning.bus.BrowserEvents.UserPressedBack} message if the
* fragement is showing the boomarks root folder.
*
* @param event a marker
*/
@Subscribe
public void closeBookmarks(final BookmarkEvents.CloseBookmarks event) {
mDrawerLayout.closeDrawer(mDrawerRight);
}
};
} }

5
app/src/main/java/acr/browser/lightning/activity/MainActivity.java

@ -6,7 +6,10 @@ import android.view.Menu;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.CookieSyncManager; import android.webkit.CookieSyncManager;
import com.squareup.otto.Subscribe;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.bus.BookmarkEvents;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@ -60,4 +63,6 @@ public class MainActivity extends BrowserActivity {
closeDrawers(); closeDrawers();
moveTaskToBack(true); moveTaskToBack(true);
} }
} }

31
app/src/main/java/acr/browser/lightning/app/AppComponent.java

@ -0,0 +1,31 @@
package acr.browser.lightning.app;
import javax.inject.Singleton;
import acr.browser.lightning.activity.BrowserActivity;
import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.dialog.BookmarksDialogBuilder;
import acr.browser.lightning.fragment.BookmarkSettingsFragment;
import acr.browser.lightning.fragment.BookmarksFragment;
import acr.browser.lightning.object.SearchAdapter;
import dagger.Component;
/**
* Created by Stefano Pacifici on 01/09/15.
*/
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
void inject(BrowserActivity activity);
void inject(BookmarksFragment fragment);
void inject(BookmarkSettingsFragment fragment);
void inject(SearchAdapter adapter);
void inject(BookmarksDialogBuilder builder);
void inject(BookmarkPage bookmarkPage);
}

41
app/src/main/java/acr/browser/lightning/app/AppModule.java

@ -0,0 +1,41 @@
package acr.browser.lightning.app;
import android.content.Context;
import com.squareup.otto.Bus;
import javax.inject.Singleton;
import acr.browser.lightning.database.BookmarkManager;
import dagger.Module;
import dagger.Provides;
/**
* Created by Stefano Pacifici on 01/09/15.
*/
@Module
public class AppModule {
private final BrowserApp app;
private final Bus bus;
public AppModule(BrowserApp app) {
this.app = app;
this.bus = new Bus();
}
@Provides
public Context provideContext() {
return app.getApplicationContext();
}
@Provides
@Singleton
public BookmarkManager provideBookmarkManager() {
return new BookmarkManager(app.getApplicationContext());
}
@Provides
public Bus provideBus() {
return bus;
}
}

13
app/src/main/java/acr/browser/lightning/activity/BrowserApp.java → app/src/main/java/acr/browser/lightning/app/BrowserApp.java

@ -1,4 +1,4 @@
package acr.browser.lightning.activity; package acr.browser.lightning.app;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
@ -6,14 +6,25 @@ import android.content.Context;
public class BrowserApp extends Application { public class BrowserApp extends Application {
private static Context context; private static Context context;
private static AppComponent appComponent;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
context = getApplicationContext(); context = getApplicationContext();
buildDepencyGraph();
} }
public static Context getAppContext() { public static Context getAppContext() {
return context; return context;
} }
public static AppComponent getAppComponent() {
return appComponent;
}
public void buildDepencyGraph() {
appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
}
} }

92
app/src/main/java/acr/browser/lightning/bus/BookmarkEvents.java

@ -0,0 +1,92 @@
package acr.browser.lightning.bus;
import acr.browser.lightning.database.HistoryItem;
/**
* Created by Stefano Pacifici on 26/08/15.
*/
public final class BookmarkEvents {
private BookmarkEvents() {
// No instances
}
/**
* A bookmark was clicked
*/
public final static class Clicked {
public final HistoryItem bookmark;
public Clicked(final HistoryItem bookmark) {
this.bookmark = bookmark;
}
}
/**
* The user ask to open the bookmark as new tab
*/
public final static class AsNewTab {
public final HistoryItem bookmark;
public AsNewTab(final HistoryItem bookmark) {
this.bookmark = bookmark;
}
}
/**
* The user ask to delete the selected bookmark
*/
public static class Deleted {
public final HistoryItem item;
public Deleted(final HistoryItem item) {
this.item = item;
}
}
/**
* The user ask to bookmark the currently displayed page
*/
public static class WantToBookmarkCurrentPage {
}
/**
* The bookmark was added
*/
public static class Added {
public final HistoryItem item;
public Added(final HistoryItem item) {
this.item = item;
}
}
/**
* The {@link acr.browser.lightning.fragment.BookmarksFragment} want to know the url (and title)
* of the currently shown web page.
*/
// public static class WantInfoAboutCurrentPage {
// }
/**
* Sended by the {@link acr.browser.lightning.fragment.BookmarksFragment} when it wants to close
* itself (generally in reply to a {@link acr.browser.lightning.bus.BrowserEvents.UserPressedBack}
* event.
*/
public static class CloseBookmarks {
}
/**
* Sended when a bookmark is edited
*/
public static class BookmarkChanged {
public final HistoryItem oldBookmark;
public final HistoryItem newBookmark;
public BookmarkChanged(final HistoryItem oldItem, final HistoryItem newItem) {
oldBookmark = oldItem;
newBookmark = newItem;
}
}
}

44
app/src/main/java/acr/browser/lightning/bus/BrowserEvents.java

@ -0,0 +1,44 @@
package acr.browser.lightning.bus;
/**
* Created by Stefano Pacifici on 26/08/15.
*/
public final class BrowserEvents {
private BrowserEvents() {
// No instances
}
/**
* Used to reply to the {@link acr.browser.lightning.fragment.BookmarksFragment} message
* {@link acr.browser.lightning.bus.BookmarkEvents.WantToBookmarkCurrentPage}. The interaction
* result is a new bookmark added.
*/
public static class AddBookmark {
public final String title, url;
public AddBookmark(final String title, final String url) {
this.title = title;
this.url = url;
}
}
/**
* Used to reply to {@link acr.browser.lightning.fragment.BookmarksFragment} message
* {@link acr.browser.lightning.bus.BookmarkEvents.WantInfoAboutCurrentPage}. This is generally
* used to update the {@link acr.browser.lightning.fragment.BookmarksFragment} interface.
*/
public static class CurrentPageUrl {
public final String url;
public CurrentPageUrl(final String url) {
this.url = url;
}
}
/**
* Notify the BookmarksFragment and TabsFragment that the user pressed the back button
*/
public static class UserPressedBack {
}
}

38
app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java

@ -3,22 +3,22 @@
*/ */
package acr.browser.lightning.constant; package acr.browser.lightning.constant;
import android.app.Activity; import android.content.Context;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.activity.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
public class BookmarkPage { public final class BookmarkPage {
public static final String FILENAME = "bookmarks.html";
public static final String HEADING = "<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml>\n" + public static final String HEADING = "<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml>\n" +
"<head>\n" + "<head>\n" +
@ -49,29 +49,41 @@ public class BookmarkPage {
public static final String END = "</div></body></html>"; public static final String END = "</div></body></html>";
public static void buildBookmarkPage(final Activity activity, final String folder, final List<HistoryItem> list) { @Inject
final BookmarkManager manager = BookmarkManager.getInstance(activity); BookmarkManager manager;
File bookmarkWebPage;
private final File FILES_DIR;
private final File CACHE_DIR;
@Inject
public BookmarkPage(Context context) {
BrowserApp.getAppComponent().inject(this);
FILES_DIR = context.getFilesDir();
CACHE_DIR = context.getCacheDir();
}
public void buildBookmarkPage(final String folder, final List<HistoryItem> list) {
final File bookmarkWebPage;
if (folder == null || folder.isEmpty()) { if (folder == null || folder.isEmpty()) {
bookmarkWebPage = new File(activity.getFilesDir(), BookmarkPage.FILENAME); bookmarkWebPage = new File(FILES_DIR, Constants.BOOKMARKS_FILENAME);
} else { } else {
bookmarkWebPage = new File(activity.getFilesDir(), folder + '-' + BookmarkPage.FILENAME); bookmarkWebPage = new File(FILES_DIR, folder + '-' + Constants.BOOKMARKS_FILENAME);
} }
final StringBuilder bookmarkBuilder = new StringBuilder(BookmarkPage.HEADING); final StringBuilder bookmarkBuilder = new StringBuilder(BookmarkPage.HEADING);
String folderIconPath = Constants.FILE + activity.getCacheDir() + "/folder.png"; final String folderIconPath = Constants.FILE + CACHE_DIR + "/folder.png";
for (int n = 0; n < list.size(); n++) { for (int n = 0; n < list.size(); n++) {
final HistoryItem item = list.get(n); final HistoryItem item = list.get(n);
bookmarkBuilder.append(BookmarkPage.PART1); bookmarkBuilder.append(BookmarkPage.PART1);
if (item.isFolder()) { if (item.isFolder()) {
File folderPage = new File(activity.getFilesDir(), item.getTitle() + '-' + BookmarkPage.FILENAME); final File folderPage = new File(FILES_DIR, item.getTitle() + '-' + Constants.BOOKMARKS_FILENAME);
bookmarkBuilder.append(Constants.FILE).append(folderPage); bookmarkBuilder.append(Constants.FILE).append(folderPage);
bookmarkBuilder.append(BookmarkPage.PART2); bookmarkBuilder.append(BookmarkPage.PART2);
bookmarkBuilder.append(folderIconPath); bookmarkBuilder.append(folderIconPath);
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
buildBookmarkPage(activity, item.getTitle(), manager.getBookmarksFromFolder(item.getTitle(), true)); buildBookmarkPage(item.getTitle(), manager.getBookmarksFromFolder(item.getTitle(), true));
} }
}).run(); }).run();
} else { } else {

5
app/src/main/java/acr/browser/lightning/constant/Constants.java

@ -47,6 +47,11 @@ public final class Constants {
public static final int PROXY_I2P = 2; public static final int PROXY_I2P = 2;
public static final int PROXY_MANUAL = 3; public static final int PROXY_MANUAL = 3;
/**
* The bookmark page standard suffix
*/
public static final String BOOKMARKS_FILENAME = "bookmarks.html";
public static final String DEFAULT_ENCODING = "UTF-8"; public static final String DEFAULT_ENCODING = "UTF-8";
public static final String[] TEXT_ENCODINGS = {"ISO-8859-1", "UTF-8", "GBK", "Big5", "ISO-2022-JP", "SHIFT_JS", "EUC-JP", "EUC-KR"}; public static final String[] TEXT_ENCODINGS = {"ISO-8859-1", "UTF-8", "GBK", "Big5", "ISO-2022-JP", "SHIFT_JS", "EUC-JP", "EUC-KR"};

2
app/src/main/java/acr/browser/lightning/constant/HistoryPage.java

@ -11,7 +11,7 @@ import java.util.List;
import android.content.Context; import android.content.Context;
import acr.browser.lightning.activity.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.database.HistoryDatabase; import acr.browser.lightning.database.HistoryDatabase;

2
app/src/main/java/acr/browser/lightning/constant/StartPage.java

@ -9,7 +9,7 @@ import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import acr.browser.lightning.activity.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;

2
app/src/main/java/acr/browser/lightning/controller/BrowserController.java

@ -56,6 +56,6 @@ public interface BrowserController {
boolean proxyIsNotReady(); boolean proxyIsNotReady();
void updateBookmarkIndicator(String url); // void updateBookmarkIndicator(String url);
} }

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

@ -2,10 +2,10 @@ package acr.browser.lightning.database;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.os.Environment; import android.os.Environment;
import android.provider.Browser;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -13,82 +13,198 @@ import org.json.JSONObject;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Singleton;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
@Singleton
public class BookmarkManager { public class BookmarkManager {
private final Context mContext; private static final String TAG = BookmarkManager.class.getSimpleName();
private static final String TITLE = "title"; private static final String TITLE = "title";
private static final String URL = "url"; private static final String URL = "url";
private static final String FOLDER = "folder"; private static final String FOLDER = "folder";
private static final String ORDER = "order"; private static final String ORDER = "order";
private static final String FILE_BOOKMARKS = "bookmarks.dat"; private static final String FILE_BOOKMARKS = "bookmarks.dat";
private Set<String> mBookmarkSearchSet = new HashSet<>();
private final List<HistoryItem> mBookmarkList = new ArrayList<>(); private final String DEFAULT_BOOKMARK_TITLE;
private Map<String, HistoryItem> mBookmarksMap = new HashMap<>();
// private final List<HistoryItem> mBookmarkList = new ArrayList<>();
private String mCurrentFolder = ""; private String mCurrentFolder = "";
private static BookmarkManager mInstance; private final ExecutorService mExecutor;
private boolean mReady = false;
private final File mFilesDir;
public BookmarkManager(Context context) {
mExecutor = Executors.newSingleThreadExecutor();
mFilesDir = context.getFilesDir();
DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled);
mExecutor.execute(new BookmarkInitializer(context));
}
/**
*
* @return true if the BookmarkManager was initialized, false otherwise
*/
public boolean isReady() {
return mReady;
}
/**
* 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);
}
/**
* Initialize the Bookmaneger, it's a one-time operation and will be executed asynchronously.
* When done, mReady flag will been setted to true.
*/
private class BookmarkInitializer implements Runnable{
private final Context mContext;
public BookmarkInitializer(Context context) {
mContext = context;
}
@Override
public void run() {
synchronized (BookmarkManager.this) {
final Map<String, HistoryItem> bookmarks = new HashMap<>();
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);
BufferedReader bookmarksReader = null;
try {
final InputStream inputStream;
if (bookmarksFile.exists() && bookmarksFile.isFile()) {
inputStream = new FileInputStream(bookmarksFile);
} else {
inputStream = mContext.getResources().openRawResource(R.raw.default_bookmarks);
}
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);
}
mBookmarksMap = bookmarks;
mReady = true;
}
}
}
/**
* Dump all the given bookmarks to the bookmark file using a temporary file
*/
private class BookmarksWriter implements Runnable {
private final List<HistoryItem> mBookmarks;
public BookmarksWriter(List<HistoryItem> bookmarks) {
mBookmarks = bookmarks;
}
@Override
public void run() {
final File tempFile = new File(mFilesDir,
String.format("bm_%d.dat", System.currentTimeMillis()));
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);
boolean success = false;
BufferedWriter bookmarkWriter = null;
try {
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 (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
public static BookmarkManager getInstance(Context context) { if (success) {
if (mInstance == null) { // Overwrite the bookmarks file by renaming the temp file
mInstance = new BookmarkManager(context); tempFile.renameTo(bookmarksFile);
}
} }
return mInstance;
} }
private BookmarkManager(Context context) { @Override
mContext = context; protected void finalize() throws Throwable {
mBookmarkList.clear(); mExecutor.shutdownNow();
mBookmarkList.addAll(getAllBookmarks(true)); super.finalize();
mBookmarkSearchSet = getBookmarkUrls(mBookmarkList);
} }
public boolean isBookmark(String url) { public boolean isBookmark(String url) {
return mBookmarkSearchSet.contains(url); return mBookmarksMap.containsKey(url);
} }
/** /**
* This method adds the the HistoryItem item to permanent bookmark storage. * This method adds the the HistoryItem item to permanent bookmark storage.<br>
* It returns true if the operation was successful. * This operation is blocking if the manager is still not ready.
* *
* @param item the item to add * @param item the item to add
* @return It returns true if the operation was successful.
*/ */
public synchronized boolean addBookmark(HistoryItem item) { public synchronized boolean addBookmark(@NonNull HistoryItem item) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS); final String url = item.getUrl();
if (item.getUrl() == null || mBookmarkSearchSet.contains(item.getUrl())) { if (url == null || mBookmarksMap.containsKey(url)) {
return false; return false;
} }
mBookmarkList.add(item); mBookmarksMap.put(url, item);
BufferedWriter bookmarkWriter = null; mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
try {
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksFile, true));
JSONObject object = new JSONObject();
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();
mBookmarkSearchSet.add(item.getUrl());
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
return true; return true;
} }
@ -98,28 +214,16 @@ public class BookmarkManager {
* @param list the list of HistoryItems to add to bookmarks * @param list the list of HistoryItems to add to bookmarks
*/ */
private synchronized void addBookmarkList(List<HistoryItem> list) { private synchronized void addBookmarkList(List<HistoryItem> list) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS); if (list == null || list.isEmpty()) {
BufferedWriter bookmarkWriter = null; return;
try { }
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksFile, true)); for (HistoryItem item : list) {
JSONObject object = new JSONObject(); final String url = item.getUrl();
for (HistoryItem item : list) { if (url != null && !mBookmarksMap.containsKey(url)) {
if (item.getUrl() != null && !mBookmarkSearchSet.contains(item.getUrl())) { mBookmarksMap.put(url, item);
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();
mBookmarkSearchSet.add(item.getUrl());
mBookmarkList.add(item);
}
} }
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
} }
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
} }
/** /**
@ -132,9 +236,8 @@ public class BookmarkManager {
if (deleteItem == null || deleteItem.isFolder()) { if (deleteItem == null || deleteItem.isFolder()) {
return false; return false;
} }
mBookmarkSearchSet.remove(deleteItem.getUrl()); mBookmarksMap.remove(deleteItem.getUrl());
mBookmarkList.remove(deleteItem); mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
overwriteBookmarks(mBookmarkList);
return true; return true;
} }
@ -148,15 +251,15 @@ public class BookmarkManager {
if (newName.isEmpty()) { if (newName.isEmpty()) {
return; return;
} }
for (int n = 0; n < mBookmarkList.size(); n++) { for (HistoryItem item: mBookmarksMap.values()) {
if (mBookmarkList.get(n).getFolder().equals(oldName)) { if (item.getFolder().equals(oldName)) {
mBookmarkList.get(n).setFolder(newName); item.setFolder(newName);
} else if (mBookmarkList.get(n).isFolder() && mBookmarkList.get(n).getTitle().equals(oldName)) { } else if (item.isFolder() && item.getTitle().equals(oldName)) {
mBookmarkList.get(n).setTitle(newName); item.setTitle(newName);
mBookmarkList.get(n).setUrl(Constants.FOLDER + newName); item.setUrl(Constants.FOLDER + newName);
} }
} }
overwriteBookmarks(mBookmarkList); mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
} }
/** /**
@ -165,16 +268,22 @@ public class BookmarkManager {
* @param name the name of the folder to be deleted * @param name the name of the folder to be deleted
*/ */
public synchronized void deleteFolder(@NonNull String name) { public synchronized void deleteFolder(@NonNull String name) {
Iterator<HistoryItem> iterator = mBookmarkList.iterator(); final Map<String, HistoryItem> bookmarks = new HashMap<>();
while (iterator.hasNext()) { for (HistoryItem item: mBookmarksMap.values()) {
HistoryItem item = iterator.next(); final String url = item.getUrl();
if (!item.isFolder() && item.getFolder().equals(name)) { if (item.isFolder()) {
item.setFolder(""); if (!item.getTitle().equals(name)) {
} else if (item.getTitle().equals(name)) { bookmarks.put(url, item);
iterator.remove(); }
} else {
if (item.getFolder().equals(name)) {
item.setFolder("");
}
bookmarks.put(url, item);
} }
} }
overwriteBookmarks(mBookmarkList); mBookmarksMap = bookmarks;
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values())));
} }
/** /**
@ -187,21 +296,21 @@ public class BookmarkManager {
if (oldItem == null || newItem == null || oldItem.isFolder()) { if (oldItem == null || newItem == null || oldItem.isFolder()) {
return; return;
} }
mBookmarkList.remove(oldItem);
mBookmarkList.add(newItem);
if (!oldItem.getUrl().equals(newItem.getUrl())) {
// Update the BookmarkMap if the URL has been changed
mBookmarkSearchSet.remove(oldItem.getUrl());
mBookmarkSearchSet.add(newItem.getUrl());
}
if (newItem.getUrl().isEmpty()) { if (newItem.getUrl().isEmpty()) {
deleteBookmark(oldItem); deleteBookmark(oldItem);
return; return;
} }
if (newItem.getTitle().isEmpty()) { if (newItem.getTitle().isEmpty()) {
newItem.setTitle(mContext.getString(R.string.untitled)); newItem.setTitle(DEFAULT_BOOKMARK_TITLE);
} }
overwriteBookmarks(mBookmarkList); 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())));
} }
/** /**
@ -248,30 +357,12 @@ public class BookmarkManager {
* This is a disk-bound operation and should not be * This is a disk-bound operation and should not be
* done very frequently. * done very frequently.
* *
* @param sort force to sort the returned bookmarkList
*
* @return returns a list of bookmarks that can be sorted * @return returns a list of bookmarks that can be sorted
*/ */
public synchronized List<HistoryItem> getAllBookmarks(boolean sort) { public synchronized List<HistoryItem> getAllBookmarks(boolean sort) {
List<HistoryItem> bookmarks = new ArrayList<>(); final List<HistoryItem> bookmarks = new ArrayList<>(mBookmarksMap.values());
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
BufferedReader bookmarksReader = null;
try {
bookmarksReader = new BufferedReader(new FileReader(bookmarksFile));
String line;
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));
item.setImageId(R.drawable.ic_bookmark);
bookmarks.add(item);
}
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarksReader);
}
if (sort) { if (sort) {
Collections.sort(bookmarks, new SortIgnoreCase()); Collections.sort(bookmarks, new SortIgnoreCase());
} }
@ -294,9 +385,9 @@ public class BookmarkManager {
folder = ""; folder = "";
} }
mCurrentFolder = folder; mCurrentFolder = folder;
for (int n = 0; n < mBookmarkList.size(); n++) { for (HistoryItem item: mBookmarksMap.values()) {
if (mBookmarkList.get(n).getFolder().equals(folder)) if (item.getFolder().equals(folder))
bookmarks.add(mBookmarkList.get(n)); bookmarks.add(item);
} }
if (sort) { if (sort) {
Collections.sort(bookmarks, new SortIgnoreCase()); Collections.sort(bookmarks, new SortIgnoreCase());
@ -320,9 +411,9 @@ public class BookmarkManager {
*/ */
private Set<String> getBookmarkUrls(List<HistoryItem> list) { private Set<String> getBookmarkUrls(List<HistoryItem> list) {
Set<String> set = new HashSet<>(); Set<String> set = new HashSet<>();
for (int n = 0; n < list.size(); n++) { for (HistoryItem item: mBookmarksMap.values()) {
if (!mBookmarkList.get(n).isFolder()) if (!item.isFolder())
set.add(mBookmarkList.get(n).getUrl()); set.add(item.getUrl());
} }
return set; return set;
} }
@ -335,34 +426,23 @@ public class BookmarkManager {
* @return a list of all folders * @return a list of all folders
*/ */
public synchronized List<HistoryItem> getFolders(boolean sort) { public synchronized List<HistoryItem> getFolders(boolean sort) {
List<HistoryItem> folders = new ArrayList<>(); final HashMap<String, HistoryItem> folders = new HashMap<>();
Set<String> folderMap = new HashSet<>(); for (HistoryItem item: mBookmarksMap.values()) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS); final String folderName = item.getFolder();
BufferedReader bookmarksReader = null; if (folderName != null && !folderName.isEmpty() && !folders.containsKey(folderName)) {
try { final HistoryItem folder = new HistoryItem();
bookmarksReader = new BufferedReader(new FileReader(bookmarksFile)); folder.setIsFolder(true);
String line; folder.setTitle(folderName);
while ((line = bookmarksReader.readLine()) != null) { folder.setImageId(R.drawable.ic_folder);
JSONObject object = new JSONObject(line); folder.setUrl(Constants.FOLDER + folderName);
String folderName = object.getString(FOLDER); folders.put(folderName, folder);
if (!folderName.isEmpty() && !folderMap.contains(folderName)) {
HistoryItem item = new HistoryItem();
item.setTitle(folderName);
item.setUrl(Constants.FOLDER + folderName);
item.setIsFolder(true);
folderMap.add(folderName);
folders.add(item);
}
} }
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarksReader);
} }
final List<HistoryItem> result = new ArrayList<>(folders.values());
if (sort) { if (sort) {
Collections.sort(folders, new SortIgnoreCase()); Collections.sort(result, new SortIgnoreCase());
} }
return folders; return result;
} }
/** /**
@ -372,27 +452,14 @@ public class BookmarkManager {
* @return a list of folder title strings * @return a list of folder title strings
*/ */
public synchronized List<String> getFolderTitles() { public synchronized List<String> getFolderTitles() {
List<String> folders = new ArrayList<>(); final Set<String> folders = new HashSet<>();
Set<String> folderMap = new HashSet<>(); for (HistoryItem item: mBookmarksMap.values()) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS); final String folderName = item.getFolder();
BufferedReader bookmarksReader = null; if (folderName != null && !folderName.isEmpty()) {
try { folders.add(folderName);
bookmarksReader = new BufferedReader(new FileReader(bookmarksFile));
String line;
while ((line = bookmarksReader.readLine()) != null) {
JSONObject object = new JSONObject(line);
String folderName = object.getString(FOLDER);
if (!folderName.isEmpty() && !folderMap.contains(folderName)) {
folders.add(folderName);
folderMap.add(folderName);
}
} }
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarksReader);
} }
return folders; return new ArrayList<>(folders);
} }
/** /**
@ -432,37 +499,6 @@ public class BookmarkManager {
} }
} }
/**
* This method overwrites the entire bookmark file with the list of
* bookmarks. This is useful when an edit has been made to one or more
* bookmarks in the list
*
* @param list the list of bookmarks to overwrite the old ones with
*/
private synchronized void overwriteBookmarks(List<HistoryItem> list) {
File bookmarksFile = new File(mContext.getFilesDir(), FILE_BOOKMARKS);
BufferedWriter bookmarkWriter = null;
try {
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksFile, false));
JSONObject object = new JSONObject();
for (int n = 0; n < list.size(); n++) {
HistoryItem item = list.get(n);
if (!item.isFolder()) {
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();
}
}
} catch (IOException | JSONException e) {
e.printStackTrace();
} finally {
Utils.close(bookmarkWriter);
}
}
/** /**
* find the index of a bookmark in a list using only its URL * find the index of a bookmark in a list using only its URL
* *
@ -498,17 +534,4 @@ public class BookmarkManager {
} }
} }
private static final String[] DEV = {"https://twitter.com/RestainoAnthony", "The Developer"};
private static final String[] FACEBOOK = {"https://www.facebook.com/", "Facebook"};
private static final String[] TWITTER = {"https://twitter.com", "Twitter"};
private static final String[] GOOGLE = {"https://www.google.com/", "Google"};
private static final String[] YAHOO = {"https://www.yahoo.com/", "Yahoo"};
public static final String[][] DEFAULT_BOOKMARKS = {
DEV,
FACEBOOK,
TWITTER,
GOOGLE,
YAHOO
};
} }

201
app/src/main/java/acr/browser/lightning/dialog/BookmarksDialogBuilder.java

@ -0,0 +1,201 @@
package acr.browser.lightning.dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.LinearLayout;
import com.squareup.otto.Bus;
import java.util.List;
import javax.inject.Inject;
import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.bus.BookmarkEvents;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.Utils;
/**
* Created by Stefano Pacifici on 02/09/15, based on Anthony C. Restaino's code.
*/
public class BookmarksDialogBuilder {
@Inject
BookmarkManager bookmarkManager;
@Inject
Bus eventBus;
@Inject
public BookmarksDialogBuilder() {
BrowserApp.getAppComponent().inject(this);
}
/**
* Show the appropriated dialog for the long pressed link. It means that we try to understand
* if the link is relative to a bookmark or is just a folder.
* @param context used to show the dialog
* @param url the long pressed url
*/
public void showLongPressedDialogForUrl(final Context context, final String url) {
final HistoryItem item;
if (url.startsWith(Constants.FILE) && url.endsWith(Constants.BOOKMARKS_FILENAME)) {
// TODO this in so many ways is wrong, probably we need to redesign the folder mechanism
final Uri uri = Uri.parse(url);
final String filename = uri.getLastPathSegment();
final String folderTitle = filename.substring(0, filename.length() - Constants.BOOKMARKS_FILENAME.length() - 1);
item = new HistoryItem();
item.setIsFolder(true);
item.setTitle(folderTitle);
item.setImageId(R.drawable.ic_folder);
item.setUrl(Constants.FOLDER + folderTitle);
} else {
item = bookmarkManager.findBookmarkForUrl(url);
}
if (item != null) {
if (item.isFolder()) {
showBookmarkFolderLongPressedDialog(context, item);
} else {
showLongPressedDialogForUrl(context, item);
}
}
}
public void showLongPressedDialogForUrl(final Context context, final HistoryItem item) {
final DialogInterface.OnClickListener dialogClickListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
eventBus.post(new BookmarkEvents.AsNewTab(item));
break;
case DialogInterface.BUTTON_NEGATIVE:
if (bookmarkManager.deleteBookmark(item)) {
eventBus.post(new BookmarkEvents.Deleted(item));
}
break;
case DialogInterface.BUTTON_NEUTRAL:
showEditBookmarkDialog(context, item);
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.action_bookmarks)
.setMessage(R.string.dialog_bookmark)
.setCancelable(true)
.setPositiveButton(R.string.action_new_tab, dialogClickListener)
.setNegativeButton(R.string.action_delete, dialogClickListener)
.setNeutralButton(R.string.action_edit, dialogClickListener)
.show();
}
public void showEditBookmarkDialog(final Context context, final HistoryItem item) {
final AlertDialog.Builder editBookmarkDialog = new AlertDialog.Builder(context);
editBookmarkDialog.setTitle(R.string.title_edit_bookmark);
final View dialogLayout = View.inflate(context, R.layout.dialog_edit_bookmark, null);
final EditText getTitle = (EditText) dialogLayout.findViewById(R.id.bookmark_title);
getTitle.setText(item.getTitle());
final EditText getUrl = (EditText) dialogLayout.findViewById(R.id.bookmark_url);
getUrl.setText(item.getUrl());
final AutoCompleteTextView getFolder =
(AutoCompleteTextView) dialogLayout.findViewById(R.id.bookmark_folder);
getFolder.setHint(R.string.folder);
getFolder.setText(item.getFolder());
final List<String> folders = bookmarkManager.getFolderTitles();
final ArrayAdapter<String> suggestionsAdapter = new ArrayAdapter<>(context,
android.R.layout.simple_dropdown_item_1line, folders);
getFolder.setThreshold(1);
getFolder.setAdapter(suggestionsAdapter);
editBookmarkDialog.setView(dialogLayout);
editBookmarkDialog.setPositiveButton(context.getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
HistoryItem editedItem = new HistoryItem();
String currentFolder = item.getFolder();
editedItem.setTitle(getTitle.getText().toString());
editedItem.setUrl(getUrl.getText().toString());
editedItem.setUrl(getUrl.getText().toString());
editedItem.setFolder(getFolder.getText().toString());
bookmarkManager.editBookmark(item, editedItem);
eventBus.post(new BookmarkEvents.BookmarkChanged(item, editedItem));
}
});
editBookmarkDialog.show();
}
public void showBookmarkFolderLongPressedDialog(final Context context, final HistoryItem item) {
assert item.isFolder();
final DialogInterface.OnClickListener dialogClickListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
showRenameFolderDialog(context, item);
break;
case DialogInterface.BUTTON_NEGATIVE:
bookmarkManager.deleteFolder(item.getTitle());
// setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
eventBus.post(new BookmarkEvents.Deleted(item));
break;
}
}
};
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.action_folder)
.setMessage(R.string.dialog_folder)
.setCancelable(true)
.setPositiveButton(R.string.action_rename, dialogClickListener)
.setNegativeButton(R.string.action_delete, dialogClickListener)
.show();
}
public void showRenameFolderDialog(final Context context, final HistoryItem item) {
assert item.isFolder();
final AlertDialog.Builder editFolderDialog = new AlertDialog.Builder(context);
editFolderDialog.setTitle(R.string.title_rename_folder);
final EditText getTitle = new EditText(context);
getTitle.setHint(R.string.hint_title);
getTitle.setText(item.getTitle());
getTitle.setSingleLine();
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
int padding = Utils.dpToPx(10);
layout.setPadding(padding, padding, padding, padding);
layout.addView(getTitle);
editFolderDialog.setView(layout);
editFolderDialog.setPositiveButton(context.getString(R.string.action_ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final String oldTitle = item.getTitle();
final String newTitle = getTitle.getText().toString();
final HistoryItem editedItem = new HistoryItem();
editedItem.setTitle(newTitle);
editedItem.setUrl(Constants.FOLDER + newTitle);
editedItem.setFolder(item.getFolder());
editedItem.setIsFolder(true);
bookmarkManager.renameFolder(oldTitle, newTitle);
eventBus.post(new BookmarkEvents.BookmarkChanged(item, editedItem));
}
});
editFolderDialog.show();
}
}

8
app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java

@ -17,7 +17,10 @@ import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.utils.PermissionsManager; import acr.browser.lightning.utils.PermissionsManager;
@ -27,7 +30,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
private static final String SETTINGS_IMPORT = "import_bookmark"; private static final String SETTINGS_IMPORT = "import_bookmark";
private Activity mActivity; private Activity mActivity;
private BookmarkManager mBookmarkManager; @Inject BookmarkManager mBookmarkManager;
private File[] mFileList; private File[] mFileList;
private String[] mFileNameList; private String[] mFileNameList;
private PermissionsManager mPermissionsManager; private PermissionsManager mPermissionsManager;
@ -40,13 +43,12 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
BrowserApp.getAppComponent().inject(this);
// Load the preferences from an XML resource // Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_bookmarks); addPreferencesFromResource(R.xml.preference_bookmarks);
mActivity = getActivity(); mActivity = getActivity();
mBookmarkManager = BookmarkManager.getInstance(mActivity.getApplicationContext());
initPrefs(); initPrefs();
mPermissionsManager = PermissionsManager.getInstance(); mPermissionsManager = PermissionsManager.getInstance();

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

@ -0,0 +1,361 @@
package acr.browser.lightning.fragment;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import acr.browser.lightning.R;
import acr.browser.lightning.activity.BrowserActivity;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.bus.BookmarkEvents;
import acr.browser.lightning.bus.BrowserEvents;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.dialog.BookmarksDialogBuilder;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.DownloadImageTask;
import acr.browser.lightning.utils.ThemeUtils;
/**
* Created by Stefano Pacifici on 25/08/15. Based on Anthony C. Restaino's code.
*/
public class BookmarksFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener {
// Managers
@Inject
BookmarkManager mBookmarkManager;
// Event bus
@Inject
Bus eventBus;
// Dialog builder
@Inject
BookmarksDialogBuilder bookmarksDialogBuilder;
// Adapter
private BookmarkViewAdapter mBookmarkAdapter;
// Preloaded images
private Bitmap mWebpageBitmap, mFolderBitmap;
// Bookmarks
private List<HistoryItem> mBookmarks = new ArrayList<>();
// Views
private ListView mBookmarksListView;
private ImageView mBookmarkTitleImage, mBookmarkImage;
// Colors
private int mIconColor;
// Init asynchronously the bookmark manager
private final Runnable initBookmarkManager = new Runnable() {
@Override
public void run() {
final Context context = getContext();
mBookmarkAdapter = new BookmarkViewAdapter(context, mBookmarks);
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), false);
mBookmarksListView.setAdapter(mBookmarkAdapter);
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BrowserApp.getAppComponent().inject(this);
}
// Handle bookmark click
private final OnItemClickListener itemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final HistoryItem item = mBookmarks.get(position);
if (item.isFolder()) {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(item.getTitle(), true),
true);
} else {
eventBus.post(new BookmarkEvents.Clicked(item));
}
}
};
private final OnItemLongClickListener itemLongClickListener = new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
final HistoryItem item = mBookmarks.get(position);
handleLongPress(item, position);
return true;
}
};
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.bookmark_drawer, container, false);
mBookmarksListView = (ListView) view.findViewById(R.id.right_drawer_list);
mBookmarksListView.setOnItemClickListener(itemClickListener);
mBookmarksListView.setOnItemLongClickListener(itemLongClickListener);
mBookmarkTitleImage = (ImageView) view.findViewById(R.id.starIcon);
mBookmarkImage = (ImageView) view.findViewById(R.id.icon_star);
final View backView = view.findViewById(R.id.bookmark_back_button);
backView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mBookmarkManager == null)
return;
if (!mBookmarkManager.isRootFolder()) {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true);
}
}
});
// Must be called here, only here we have a reference to the ListView
new Thread(initBookmarkManager).run();
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
// TODO this code depend way too much on BrowserActivity
super.onActivityCreated(savedInstanceState);
final BrowserActivity activity = (BrowserActivity) getActivity();
final PreferenceManager preferenceManager =PreferenceManager.getInstance();
boolean darkTheme = preferenceManager.getUseTheme() != 0 || activity.isIncognito();
mWebpageBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_webpage, darkTheme);
mFolderBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_folder, darkTheme);
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(activity) :
ThemeUtils.getIconLightThemeColor(activity);
setupFrameLayoutButton(getView(), R.id.action_add_bookmark, R.id.icon_star);
mBookmarkTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
}
@Override
public void onStart() {
super.onStart();
eventBus.register(this);
}
@Override
public void onStop() {
super.onStop();
eventBus.unregister(this);
}
@Subscribe
public void addBookmark(final BrowserEvents.AddBookmark event) {
final HistoryItem item = new HistoryItem(event.url, event.title);
if (mBookmarkManager.addBookmark(item)) {
mBookmarks.add(item);
Collections.sort(mBookmarks, new BookmarkManager.SortIgnoreCase());
mBookmarkAdapter.notifyDataSetChanged();
eventBus
.post(new BookmarkEvents.Added(item));
updateBookmarkIndicator(event.url);
}
}
@Subscribe
public void currentPageInfo(final BrowserEvents.CurrentPageUrl event) {
updateBookmarkIndicator(event.url);
}
@Subscribe
public void bookmarkChanged(BookmarkEvents.BookmarkChanged event) {
final int size = mBookmarks.size();
mBookmarks.remove(event.oldBookmark);
assert mBookmarks.size() < size;
mBookmarks.add(event.newBookmark);
mBookmarkAdapter.notifyDataSetChanged();
Collections.sort(mBookmarks, new BookmarkManager.SortIgnoreCase());
}
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);
}
}
@Subscribe
public void userPressedBack(final BrowserEvents.UserPressedBack event) {
if (mBookmarkManager.isRootFolder()) {
eventBus
.post(new BookmarkEvents.CloseBookmarks());
} else {
setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(null, true), true);
}
}
@Subscribe
public void bookmarkDeleted(final BookmarkEvents.Deleted event) {
final HistoryItem item = event.item;
final int size = mBookmarks.size();
mBookmarks.remove(event.item);
assert mBookmarks.size() < size;
mBookmarkAdapter.notifyDataSetChanged();
}
private void setBookmarkDataSet(List<HistoryItem> items, boolean animate) {
mBookmarks.clear();
mBookmarks.addAll(items);
mBookmarkAdapter.notifyDataSetChanged();
final int resource;
if (mBookmarkManager.isRootFolder())
resource = R.drawable.ic_action_star;
else
resource = R.drawable.ic_action_back;
final Animation startRotation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mBookmarkTitleImage.setRotationY(90 * interpolatedTime);
}
};
final Animation finishRotation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mBookmarkTitleImage.setRotationY((-90) + (90 * interpolatedTime));
}
};
startRotation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mBookmarkTitleImage.setImageResource(resource);
mBookmarkTitleImage.startAnimation(finishRotation);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
startRotation.setInterpolator(new AccelerateInterpolator());
finishRotation.setInterpolator(new DecelerateInterpolator());
startRotation.setDuration(250);
finishRotation.setDuration(250);
if (animate) {
mBookmarkTitleImage.startAnimation(startRotation);
} else {
mBookmarkTitleImage.setImageResource(resource);
}
}
// TODO this is basically a copy/paste from BrowserActivity, should be changed
private void setupFrameLayoutButton(@NonNull View view, @IdRes int buttonId, @IdRes int imageId) {
FrameLayout frameButton = (FrameLayout) view.findViewById(buttonId);
frameButton.setOnClickListener(this);
frameButton.setOnLongClickListener(this);
ImageView buttonImage = (ImageView) view.findViewById(imageId);
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
}
private void handleLongPress(final HistoryItem item, final int position) {
if (item.isFolder()) {
bookmarksDialogBuilder.showBookmarkFolderLongPressedDialog(getContext(), item);
} else {
bookmarksDialogBuilder.showLongPressedDialogForUrl(getContext(), item);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.action_add_bookmark:
eventBus.post(new BookmarkEvents.WantToBookmarkCurrentPage());
break;
default:
break;
}
}
@Override
public boolean onLongClick(View v) {
return false;
}
private class BookmarkViewAdapter extends ArrayAdapter<HistoryItem> {
final Context context;
public BookmarkViewAdapter(Context context, List<HistoryItem> data) {
super(context, R.layout.bookmark_list_item, data);
this.context = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
BookmarkViewHolder holder;
if (row == null) {
LayoutInflater inflater = LayoutInflater.from(context);
row = inflater.inflate(R.layout.bookmark_list_item, parent, false);
holder = new BookmarkViewHolder();
holder.txtTitle = (TextView) row.findViewById(R.id.textBookmark);
holder.favicon = (ImageView) row.findViewById(R.id.faviconBookmark);
row.setTag(holder);
} else {
holder = (BookmarkViewHolder) row.getTag();
}
ViewCompat.jumpDrawablesToCurrentState(row);
HistoryItem web = mBookmarks.get(position);
holder.txtTitle.setText(web.getTitle());
holder.favicon.setImageBitmap(mWebpageBitmap);
if (web.isFolder()) {
holder.favicon.setImageBitmap(mFolderBitmap);
} else if (web.getBitmap() == null) {
new DownloadImageTask(holder.favicon, web, mWebpageBitmap).execute();
} else {
holder.favicon.setImageBitmap(web.getBitmap());
}
return row;
}
class BookmarkViewHolder {
TextView txtTitle;
ImageView favicon;
}
}
}

7
app/src/main/java/acr/browser/lightning/object/SearchAdapter.java

@ -36,7 +36,10 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import javax.inject.Inject;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryDatabase; import acr.browser.lightning.database.HistoryDatabase;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
@ -58,7 +61,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
private boolean mIsExecuting = false; private boolean mIsExecuting = false;
private final boolean mDarkTheme; private final boolean mDarkTheme;
private final boolean mIncognito; private final boolean mIncognito;
private final BookmarkManager mBookmarkManager; @Inject BookmarkManager mBookmarkManager;
private static final String CACHE_FILE_TYPE = ".sgg"; private static final String CACHE_FILE_TYPE = ".sgg";
private static final String ENCODING = "ISO-8859-1"; private static final String ENCODING = "ISO-8859-1";
private static final long INTERVAL_DAY = 86400000; private static final long INTERVAL_DAY = 86400000;
@ -71,8 +74,8 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
private final Drawable mBookmarkDrawable; private final Drawable mBookmarkDrawable;
public SearchAdapter(Context context, boolean dark, boolean incognito) { public SearchAdapter(Context context, boolean dark, boolean incognito) {
BrowserApp.getAppComponent().inject(this);
mDatabaseHandler = HistoryDatabase.getInstance(context.getApplicationContext()); mDatabaseHandler = HistoryDatabase.getInstance(context.getApplicationContext());
mBookmarkManager = BookmarkManager.getInstance(context.getApplicationContext());
mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true)); mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true));
mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled(); mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled();
mContext = context; mContext = context;

11
app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java

@ -3,7 +3,7 @@ package acr.browser.lightning.preference;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Environment; import android.os.Environment;
import acr.browser.lightning.activity.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
public class PreferenceManager { public class PreferenceManager {
@ -44,7 +44,6 @@ public class PreferenceManager {
public static final String INVERT_COLORS = "invertColors"; public static final String INVERT_COLORS = "invertColors";
public static final String READING_TEXT_SIZE = "readingTextSize"; public static final String READING_TEXT_SIZE = "readingTextSize";
public static final String THEME = "Theme"; public static final String THEME = "Theme";
public static final String DEFAULT_BOOKMARKS = "defaultBookmarks";
public static final String TEXT_ENCODING = "textEncoding"; public static final String TEXT_ENCODING = "textEncoding";
public static final String CLEAR_WEBSTORAGE_EXIT = "clearWebStorageExit"; public static final String CLEAR_WEBSTORAGE_EXIT = "clearWebStorageExit";
public static final String SHOW_TABS_IN_DRAWER = "showTabsInDrawer"; public static final String SHOW_TABS_IN_DRAWER = "showTabsInDrawer";
@ -117,10 +116,6 @@ public class PreferenceManager {
return mPrefs.getBoolean(Name.COOKIES, true); return mPrefs.getBoolean(Name.COOKIES, true);
} }
public boolean getDefaultBookmarks() {
return mPrefs.getBoolean(Name.DEFAULT_BOOKMARKS, true);
}
public String getDownloadDirectory() { public String getDownloadDirectory() {
return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, Environment.DIRECTORY_DOWNLOADS); return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, Environment.DIRECTORY_DOWNLOADS);
} }
@ -317,10 +312,6 @@ public class PreferenceManager {
putBoolean(Name.COOKIES, enable); putBoolean(Name.COOKIES, enable);
} }
public void setDefaultBookmarks(boolean show) {
putBoolean(Name.DEFAULT_BOOKMARKS, show);
}
public void setDownloadDirectory(String directory) { public void setDownloadDirectory(String directory) {
putString(Name.DOWNLOAD_DIRECTORY, directory); putString(Name.DOWNLOAD_DIRECTORY, directory);
} }

127
app/src/main/java/acr/browser/lightning/utils/DownloadImageTask.java

@ -0,0 +1,127 @@
package acr.browser.lightning.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.ImageView;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.database.HistoryItem;
/**
* Created by Stefano Pacifici on 25/08/15.
*/
public class DownloadImageTask extends AsyncTask<Void, Void, Bitmap> {
final ImageView bmImage;
final HistoryItem mWeb;
final File mCacheDir;
final String mUrl;
final Bitmap mDefaultBitmap;
public DownloadImageTask(@NonNull ImageView bmImage, @NonNull HistoryItem web,
@NonNull Bitmap defaultBitmap) {
assert bmImage != null;
assert web != null;
assert defaultBitmap != null;
this.bmImage = bmImage;
this.mWeb = web;
this.mCacheDir = bmImage.getContext().getCacheDir();
this.mUrl = web.getUrl();
this.mDefaultBitmap = defaultBitmap;
}
protected Bitmap doInBackground(Void... params) {
Bitmap mIcon = null;
// unique path for each url that is bookmarked.
final Uri uri = Uri.parse(mUrl);
final String hash = "" + uri.getHost().hashCode();
final File image = new File(mCacheDir, hash + ".png");
final Uri urldisplay = Uri.fromParts(uri.getScheme(), uri.getHost(), "favicon.ico");
// checks to see if the image exists
if (!image.exists()) {
FileOutputStream fos = null;
InputStream in = null;
try {
// if not, download it...
final URL urlDownload = new URL(urldisplay.toString());
final HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection();
connection.setDoInput(true);
connection.connect();
in = connection.getInputStream();
if (in != null) {
mIcon = BitmapFactory.decodeStream(in);
}
// ...and cache it
if (mIcon != null) {
fos = new FileOutputStream(image);
mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
Log.d(Constants.TAG, "Downloaded: " + urldisplay);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Utils.close(in);
Utils.close(fos);
}
} else {
// if it exists, retrieve it from the cache
mIcon = BitmapFactory.decodeFile(image.getPath());
}
if (mIcon == null) {
InputStream in = null;
FileOutputStream fos = null;
try {
// if not, download it...
final URL urlDownload = new URL("https://www.google.com/s2/favicons?domain_url="
+ uri.toString());
final HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection();
connection.setDoInput(true);
connection.connect();
in = connection.getInputStream();
if (in != null) {
mIcon = BitmapFactory.decodeStream(in);
}
// ...and cache it
if (mIcon != null) {
fos = new FileOutputStream(image);
mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Utils.close(in);
Utils.close(fos);
}
}
if (mIcon == null) {
return mDefaultBitmap;
} else {
return mIcon;
}
}
protected void onPostExecute(Bitmap result) {
final Bitmap fav = Utils.padFavicon(result);
bmImage.setImageBitmap(fav);
mWeb.setBitmap(fav);
// notifyBookmarkDataSetChanged();
}
}

4
app/src/main/java/acr/browser/lightning/utils/Utils.java

@ -34,8 +34,11 @@ import android.webkit.URLUtil;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
@ -308,5 +311,4 @@ public final class Utils {
canvas.drawPath(wallpath, paint); canvas.drawPath(wallpath, paint);
} }
} }

9
app/src/main/java/acr/browser/lightning/view/LightningView.java

@ -98,6 +98,7 @@ public class LightningView {
private PermissionsManager mPermissionsManager; private PermissionsManager mPermissionsManager;
private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
@SuppressLint("NewApi")
public LightningView(Activity activity, String url, boolean darkTheme, boolean isIncognito) { public LightningView(Activity activity, String url, boolean darkTheme, boolean isIncognito) {
mActivity = activity; mActivity = activity;
@ -175,6 +176,7 @@ public class LightningView {
* if you don't have a reference to them * if you don't have a reference to them
* @param context the context in which the WebView was created * @param context the context in which the WebView was created
*/ */
@SuppressLint("NewApi")
public synchronized void initializePreferences(@Nullable WebSettings settings, Context context) { public synchronized void initializePreferences(@Nullable WebSettings settings, Context context) {
if (settings == null && mWebView == null) { if (settings == null && mWebView == null) {
return; return;
@ -293,6 +295,7 @@ public class LightningView {
* @param settings the WebSettings object to use. * @param settings the WebSettings object to use.
* @param context the Context which was used to construct the WebView. * @param context the Context which was used to construct the WebView.
*/ */
@SuppressLint("NewApi")
private void initializeSettings(WebSettings settings, Context context) { private void initializeSettings(WebSettings settings, Context context) {
if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) { if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) {
settings.setAppCacheMaxSize(Long.MAX_VALUE); settings.setAppCacheMaxSize(Long.MAX_VALUE);
@ -347,6 +350,7 @@ public class LightningView {
mToggleDesktop = !mToggleDesktop; mToggleDesktop = !mToggleDesktop;
} }
@SuppressLint("NewApi")
public void setUserAgent(Context context, int choice) { public void setUserAgent(Context context, int choice) {
if (mWebView == null) return; if (mWebView == null) return;
WebSettings settings = mWebView.getSettings(); WebSettings settings = mWebView.getSettings();
@ -526,6 +530,7 @@ public class LightningView {
} }
} }
@SuppressLint("NewApi")
public synchronized void find(String text) { public synchronized void find(String text) {
if (mWebView != null) { if (mWebView != null) {
if (API >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (API >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
@ -645,11 +650,11 @@ public class LightningView {
return null; return null;
} }
@SuppressLint("NewApi")
@Override @Override
public void onPageFinished(WebView view, String url) { public void onPageFinished(WebView view, String url) {
if (view.isShown()) { if (view.isShown()) {
mBrowserController.updateUrl(url, true); mBrowserController.updateUrl(url, true);
mBrowserController.updateBookmarkIndicator(url);
view.postInvalidate(); view.postInvalidate();
} }
if (view.getTitle() == null || view.getTitle().isEmpty()) { if (view.getTitle() == null || view.getTitle().isEmpty()) {
@ -667,7 +672,6 @@ public class LightningView {
public void onPageStarted(WebView view, String url, Bitmap favicon) { public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (isShown()) { if (isShown()) {
mBrowserController.updateUrl(url, false); mBrowserController.updateUrl(url, false);
mBrowserController.updateBookmarkIndicator(url);
mBrowserController.showActionBar(); mBrowserController.showActionBar();
} }
mTitle.setFavicon(mWebpageBitmap); mTitle.setFavicon(mWebpageBitmap);
@ -723,6 +727,7 @@ public class LightningView {
private boolean mIsRunning = false; private boolean mIsRunning = false;
private float mZoomScale = 0.0f; private float mZoomScale = 0.0f;
@SuppressLint("NewApi")
@Override @Override
public void onScaleChanged(final WebView view, final float oldScale, final float newScale) { public void onScaleChanged(final WebView view, final float oldScale, final float newScale) {
if (view.isShown() && mTextReflow && API >= android.os.Build.VERSION_CODES.KITKAT) { if (view.isShown() && mTextReflow && API >= android.os.Build.VERSION_CODES.KITKAT) {

15
app/src/main/res/layout/activity_main.xml

@ -27,7 +27,20 @@
<include layout="@layout/tab_drawer" /> <include layout="@layout/tab_drawer" />
<include layout="@layout/bookmark_drawer" /> <FrameLayout
android:weightSum="1"
android:layout_gravity="end"
android:fitsSystemWindows="true"
android:id="@+id/right_drawer"
android:background="?attr/drawerBackground"
android:layout_width="@dimen/navigation_width"
android:layout_height="match_parent">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
class="acr.browser.lightning.fragment.BookmarksFragment" />
</FrameLayout>
<!-- include layout="@layout/bookmark_drawer" / -->
</android.support.v4.widget.DrawerLayout> </android.support.v4.widget.DrawerLayout>
</LinearLayout> </LinearLayout>

7
app/src/main/res/layout/bookmark_drawer.xml

@ -1,13 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/right_drawer" android:layout_width="match_parent"
android:layout_width="@dimen/navigation_width"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="end"
android:background="?attr/drawerBackground"
android:clickable="true" android:clickable="true"
android:weightSum="1"
android:fitsSystemWindows="true"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout

29
app/src/main/res/layout/dialog_edit_bookmark.xml

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bookmark_title"
android:singleLine="true"
android:hint="@string/hint_title"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bookmark_url"
android:hint="@string/hint_url"
android:singleLine="true"
android:inputType="textUri"/>
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bookmark_folder"
android:hint="@string/folder"
android:singleLine="true"/>
</LinearLayout>

5
app/src/main/res/raw/default_bookmarks.dat

@ -0,0 +1,5 @@
{"url": "https://twitter.com/RestainoAnthony", "title": "The Developer", "folder": "", "order": 0}
{"url": "https://www.facebook.com/", "title": "Facebook", "folder": "", "order": 2}
{"url": "https://twitter.com", "title": "Twitter", "folder": "", "order": 3}
{"url": "https://www.google.com/", "title": "Google", "folder": "", "order": 4}
{"url": "https://www.yahoo.com/", "title": "Yahoo", "folder": "", "order": 5}

1
build.gradle

@ -5,6 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:1.3.1' classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.7'
} }
} }

2
external/netcipher vendored

@ -1 +1 @@
Subproject commit be233148c1c644b77b4f90089c82d85a50940c57 Subproject commit 7727f0402ce43489e5ebf4ee42dcacddad37c491
Loading…
Cancel
Save