/*
* Tint Browser for Android
*
* Copyright (C) 2012 - to infinity and beyond J. Devauchelle and contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 3 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package com.seal.ui.fragments;
import java.util.ArrayList;
import java.util.List;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentBreadCrumbs;
import android.app.LoaderManager;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.seal.R;
import com.seal.addons.AddonMenuItem;
import com.seal.controllers.Controller;
import com.seal.model.BookmarkHistoryItem;
import com.seal.model.BookmarksAdapter;
import com.seal.providers.BookmarksProvider;
import com.seal.providers.BookmarksWrapper;
import com.seal.ui.activities.EditBookmarkActivity;
import com.seal.ui.managers.UIFactory;
import com.seal.ui.managers.UIManager;
import com.seal.utils.ApplicationUtils;
import com.seal.utils.Constants;
public class BookmarksFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String EXTRA_FOLDER_STACK = "EXTRA_FOLDER_STACK";
private static final String STACK_SEPARATOR = "//;//";
private static final int CONTEXT_MENU_OPEN_IN_TAB = Menu.FIRST;
private static final int CONTEXT_MENU_EDIT_BOOKMARK = Menu.FIRST + 1;
private static final int CONTEXT_MENU_COPY_URL = Menu.FIRST + 2;
private static final int CONTEXT_MENU_SHARE_URL = Menu.FIRST + 3;
private static final int CONTEXT_MENU_DELETE_BOOKMARK = Menu.FIRST + 4;
private static final int CONTEXT_MENU_DELETE_FOLDER = Menu.FIRST + 5;
private View mContainer = null;
private UIManager mUIManager;
private GridView mBookmarksGrid;
private ProgressBar mProgress;
private ViewGroup mBreadCrumbGroup;
private FragmentBreadCrumbs mFoldersBreadCrumb;
private ImageView mBackBreadCrumb;
private BookmarksAdapter mAdapter;
private List<NavigationItem> mNavigationList;
private boolean mIsTablet;
private boolean mIsListShown = true;
private ProgressDialog mProgressDialog;
private OnSharedPreferenceChangeListener mPreferenceChangeListener;
public BookmarksFragment() {
mUIManager = Controller.getInstance().getUIManager();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (Constants.PREFERENCE_BOOKMARKS_SORT_MODE.equals(key)) {
getLoaderManager().restartLoader(0, null, BookmarksFragment.this);
}
}
};
PreferenceManager.getDefaultSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(mPreferenceChangeListener);
}
@Override
public void onDestroy() {
super.onDestroy();
PreferenceManager.getDefaultSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(mPreferenceChangeListener);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mIsTablet = UIFactory.isTablet(getActivity());
if (mContainer == null) {
mContainer = inflater.inflate(R.layout.bookmarks_fragment, container, false);
mBreadCrumbGroup = (ViewGroup) mContainer.findViewById(R.id.BookmarksBreadCrumbGroup);
mFoldersBreadCrumb = (FragmentBreadCrumbs) mContainer.findViewById(R.id.BookmarksBreadCrumb);
mFoldersBreadCrumb.setMaxVisible(2);
mFoldersBreadCrumb.setActivity(getActivity());
mFoldersBreadCrumb.setParentTitle(getString(R.string.Bookmarks), null, new OnClickListener() {
@Override
public void onClick(View v) {
popNavigation();
}
});
mBackBreadCrumb = (ImageView) mContainer.findViewById(R.id.BookmarksBreadCrumbBackHierarchy);
mBackBreadCrumb.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
popNavigation();
}
});
mBookmarksGrid = (GridView) mContainer.findViewById(R.id.BookmarksGridView);
mProgress = (ProgressBar) mContainer.findViewById(R.id.BookmarksProgressBar);
String[] from = new String[] { BookmarksProvider.Columns.TITLE, BookmarksProvider.Columns.URL };
int[] to = new int[] { R.id.BookmarkRow_Title, R.id.BookmarkRow_Url };
mAdapter = new BookmarksAdapter(getActivity(), R.layout.bookmark_row, null, from, to, 0, R.drawable.browser_thumbnail);
mBookmarksGrid.setAdapter(mAdapter);
mBookmarksGrid.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
BookmarkHistoryItem item = BookmarksWrapper.getBookmarkById(getActivity().getContentResolver(), id);
if (item != null) {
if (item.isFolder()) {
mNavigationList.add(new NavigationItem(item.getId(), item.getTitle()));
updateFolderId();
} else {
Intent result = new Intent();
result.putExtra(Constants.EXTRA_URL, item.getUrl());
getActivity().setResult(Activity.RESULT_OK, result);
getActivity().finish();
}
}
}
});
registerForContextMenu(mBookmarksGrid);
if (!mIsTablet) {
mBreadCrumbGroup.setVisibility(View.GONE);
mBreadCrumbGroup.setTranslationY(-mBreadCrumbGroup.getHeight());
}
mNavigationList = new ArrayList<NavigationItem>();
if ((savedInstanceState != null) && (savedInstanceState.containsKey(EXTRA_FOLDER_STACK))) {
String folderStack = savedInstanceState.getString(EXTRA_FOLDER_STACK);
String[] stack = folderStack.split(STACK_SEPARATOR);
for (int i = 0; i < stack.length; i++) {
mNavigationList.add(new NavigationItem(stack[i]));
}
} else {
mNavigationList.add(new NavigationItem(-1, null));
}
setListShown(false);
updateFolderId();
}
return mContainer;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
StringBuilder sb = new StringBuilder();
for (NavigationItem item : mNavigationList) {
sb.append(item.toString() + STACK_SEPARATOR);
}
outState.putString(EXTRA_FOLDER_STACK, sb.toString());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
long id = ((AdapterContextMenuInfo) menuInfo).id;
if (id != -1) {
BookmarkHistoryItem selectedItem = BookmarksWrapper.getBookmarkById(getActivity().getContentResolver(), id);
if (selectedItem != null) {
menu.setHeaderTitle(selectedItem.getTitle());
if (!selectedItem.isFolder()) {
BitmapDrawable icon = ApplicationUtils.getApplicationButtonImage(getActivity(), selectedItem.getFavicon());
if (icon != null) {
menu.setHeaderIcon(icon);
}
menu.add(0, CONTEXT_MENU_OPEN_IN_TAB, 0, R.string.OpenInTab);
menu.add(0, CONTEXT_MENU_EDIT_BOOKMARK, 0, R.string.EditBookmark);
menu.add(0, CONTEXT_MENU_COPY_URL, 0, R.string.CopyUrl);
menu.add(0, CONTEXT_MENU_SHARE_URL, 0, R.string.ContextMenuShareUrl);
menu.add(0, CONTEXT_MENU_DELETE_BOOKMARK, 0, R.string.DeleteBookmark);
} else {
menu.add(0, CONTEXT_MENU_DELETE_FOLDER, 0, R.string.DeleteFolder);
}
List<AddonMenuItem> addonsContributions = Controller.getInstance().getAddonManager()
.getContributedBookmarkContextMenuItems(mUIManager.getCurrentWebView());
for (AddonMenuItem item : addonsContributions) {
menu.add(0, item.getAddon().getMenuId(), 0, item.getMenuItem());
}
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
BookmarkHistoryItem selectedItem = BookmarksWrapper.getBookmarkById(getActivity().getContentResolver(), info.id);
Intent i;
switch (item.getItemId()) {
case CONTEXT_MENU_OPEN_IN_TAB:
if (item != null) {
Intent result = new Intent();
result.putExtra(Constants.EXTRA_NEW_TAB, true);
result.putExtra(Constants.EXTRA_URL, selectedItem.getUrl());
getActivity().setResult(Activity.RESULT_OK, result);
getActivity().finish();
}
return true;
case CONTEXT_MENU_EDIT_BOOKMARK:
if (selectedItem != null) {
i = new Intent(getActivity(), EditBookmarkActivity.class);
i.putExtra(Constants.EXTRA_ID, info.id);
i.putExtra(Constants.EXTRA_FOLDER_ID, selectedItem.getFolderId());
i.putExtra(Constants.EXTRA_LABEL, selectedItem.getTitle());
i.putExtra(Constants.EXTRA_URL, selectedItem.getUrl());
startActivity(i);
}
return true;
case CONTEXT_MENU_COPY_URL:
if (selectedItem != null) {
ApplicationUtils
.copyTextToClipboard(getActivity(), selectedItem.getUrl(), getActivity().getResources().getString(R.string.UrlCopyToastMessage));
}
return true;
case CONTEXT_MENU_SHARE_URL:
if (selectedItem != null) {
ApplicationUtils.sharePage(getActivity(), null, selectedItem.getUrl());
}
return true;
case CONTEXT_MENU_DELETE_BOOKMARK:
BookmarksWrapper.deleteBookmark(getActivity().getContentResolver(), info.id);
return true;
case CONTEXT_MENU_DELETE_FOLDER:
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setCancelable(true);
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setTitle(R.string.DeleteFolder);
builder.setMessage(R.string.ConfirmDeleteFolderMessage);
builder.setPositiveButton(R.string.Yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doDeleteFolder(info.id);
}
});
builder.setNegativeButton(R.string.No, null);
builder.create().show();
return true;
default:
if (Controller
.getInstance()
.getAddonManager()
.onContributedBookmarkContextMenuItemSelected(getActivity(), item.getItemId(), selectedItem.getTitle(), selectedItem.getUrl(),
mUIManager.getCurrentWebView())) {
return true;
} else {
return super.onContextItemSelected(item);
}
}
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setListShown(false);
return BookmarksWrapper.getCursorLoaderForBookmarks(getActivity(), mNavigationList.get(mNavigationList.size() - 1).getId());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
setListShown(true);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
private void setListShown(boolean shown) {
if (mIsListShown == shown) {
return;
}
mIsListShown = shown;
if (shown) {
mProgress.setVisibility(View.GONE);
mBookmarksGrid.setVisibility(View.VISIBLE);
} else {
mProgress.setVisibility(View.VISIBLE);
mBookmarksGrid.setVisibility(View.GONE);
}
}
private void updateFolderId() {
if (mAdapter != null) {
mAdapter.swapCursor(null);
}
NavigationItem current = mNavigationList.get(mNavigationList.size() - 1);
if (current.getId() == -1) {
if (!mIsTablet) {
// Dirty workaround for the first time the BreadCrumb is shown.
// At this time, its size has not been computed, so its height is 0
// and does not show with an animation.
int height = mBreadCrumbGroup.getHeight();
if (height == 0) {
height = 80;
}
AnimatorSet animator = new AnimatorSet();
animator.play(ObjectAnimator.ofFloat(mBreadCrumbGroup, "translationY", -height));
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBreadCrumbGroup.setVisibility(View.GONE);
getLoaderManager().restartLoader(0, null, BookmarksFragment.this);
}
});
animator.start();
} else {
mBackBreadCrumb.setVisibility(View.GONE);
getLoaderManager().restartLoader(0, null, this);
}
mFoldersBreadCrumb.setParentTitle(getString(R.string.Bookmarks), null, new OnClickListener() {
@Override
public void onClick(View arg0) {
popNavigation();
}
});
} else {
if (!mIsTablet) {
mBreadCrumbGroup.setVisibility(View.VISIBLE);
AnimatorSet animator = new AnimatorSet();
animator.play(ObjectAnimator.ofFloat(mBreadCrumbGroup, "translationY", 0));
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBreadCrumbGroup.requestLayout();
getLoaderManager().restartLoader(0, null, BookmarksFragment.this);
}
});
animator.start();
} else {
mBackBreadCrumb.setVisibility(View.VISIBLE);
getLoaderManager().restartLoader(0, null, this);
}
if (mNavigationList.size() > 2) {
NavigationItem previous = mNavigationList.get(mNavigationList.size() - 2);
mFoldersBreadCrumb.setParentTitle(previous.getTitle(), null, new OnClickListener() {
@Override
public void onClick(View arg0) {
popNavigation();
}
});
} else {
mFoldersBreadCrumb.setParentTitle(getString(R.string.Bookmarks), null, new OnClickListener() {
@Override
public void onClick(View arg0) {
popNavigation();
}
});
}
}
mFoldersBreadCrumb.setTitle(current.getTitle(), current.getTitle());
}
private void popNavigation() {
mNavigationList.remove(mNavigationList.size() - 1);
updateFolderId();
}
private void doDeleteFolder(long folderId) {
mProgressDialog = ProgressDialog.show(getActivity(), getString(R.string.DeleteFolderTitle), getString(R.string.DeleteFolderMessage));
new Thread(new DeleteFolderRunnable(folderId)).start();
}
@SuppressLint("HandlerLeak")
private class DeleteFolderRunnable implements Runnable {
private long mFolderId;
public DeleteFolderRunnable(long folderId) {
mFolderId = folderId;
}
@Override
public void run() {
BookmarksWrapper.deleteFolder(getActivity().getContentResolver(), mFolderId);
mHandler.sendEmptyMessage(0);
}
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
mProgressDialog.dismiss();
getLoaderManager().restartLoader(0, null, BookmarksFragment.this);
}
};
}
private class NavigationItem {
private long mId;
private String mTitle;
public NavigationItem(long id, String title) {
mId = id;
mTitle = title;
}
public NavigationItem(String builder) {
if ((builder.startsWith("{")) && (builder.endsWith("}"))) {
try {
builder = builder.substring(1, builder.length() - 1);
String[] parts = builder.split(",");
mId = Long.parseLong(parts[0]);
if (mId == -1) {
mTitle = null;
} else {
mTitle = parts[1];
}
} catch (Exception e) {
mId = -1;
mTitle = null;
}
} else {
mId = -1;
mTitle = null;
}
}
public long getId() {
return mId;
}
public String getTitle() {
return mTitle;
}
@Override
public String toString() {
return String.format("{%s,%s}", mId, mTitle);
}
}
}