You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
21 KiB
559 lines
21 KiB
package org.purplei2p.lightning.browser.fragment; |
|
|
|
import android.app.Activity; |
|
import android.content.Context; |
|
import android.content.Intent; |
|
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.support.v7.util.DiffUtil; |
|
import android.support.v7.widget.LinearLayoutManager; |
|
import android.support.v7.widget.RecyclerView; |
|
import android.view.LayoutInflater; |
|
import android.view.View; |
|
import android.view.ViewGroup; |
|
import android.view.animation.Animation; |
|
import android.widget.FrameLayout; |
|
import android.widget.ImageView; |
|
import android.widget.TextView; |
|
|
|
import com.anthonycr.bonsai.Schedulers; |
|
import com.anthonycr.bonsai.SingleOnSubscribe; |
|
import com.anthonycr.bonsai.Subscription; |
|
|
|
import java.util.ArrayList; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.concurrent.ConcurrentHashMap; |
|
|
|
import javax.inject.Inject; |
|
|
|
import org.purplei2p.lightning.R; |
|
import org.purplei2p.lightning.browser.bookmark.BookmarkUiModel; |
|
import org.purplei2p.lightning.browser.TabsManager; |
|
import org.purplei2p.lightning.animation.AnimationUtils; |
|
import org.purplei2p.lightning.BrowserApp; |
|
import org.purplei2p.lightning.browser.BookmarksView; |
|
import org.purplei2p.lightning.constant.Constants; |
|
import org.purplei2p.lightning.controller.UIController; |
|
import org.purplei2p.lightning.database.HistoryItem; |
|
import org.purplei2p.lightning.database.bookmark.BookmarkModel; |
|
import org.purplei2p.lightning.dialog.LightningDialogBuilder; |
|
import org.purplei2p.lightning.favicon.FaviconModel; |
|
import org.purplei2p.lightning.preference.PreferenceManager; |
|
import org.purplei2p.lightning.utils.Preconditions; |
|
import org.purplei2p.lightning.utils.SubscriptionUtils; |
|
import org.purplei2p.lightning.utils.ThemeUtils; |
|
import org.purplei2p.lightning.view.LightningView; |
|
import butterknife.BindView; |
|
import butterknife.ButterKnife; |
|
import butterknife.Unbinder; |
|
|
|
public class BookmarksFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener, BookmarksView { |
|
|
|
@NonNull |
|
public static BookmarksFragment createFragment(boolean isIncognito) { |
|
BookmarksFragment bookmarksFragment = new BookmarksFragment(); |
|
final Bundle bookmarksFragmentArguments = new Bundle(); |
|
bookmarksFragmentArguments.putBoolean(BookmarksFragment.INCOGNITO_MODE, isIncognito); |
|
bookmarksFragment.setArguments(bookmarksFragmentArguments); |
|
|
|
return bookmarksFragment; |
|
} |
|
|
|
private static final String TAG = "BookmarksFragment"; |
|
|
|
private final static String INCOGNITO_MODE = TAG + ".INCOGNITO_MODE"; |
|
|
|
// Managers |
|
@Inject BookmarkModel mBookmarkManager; |
|
|
|
// Dialog builder |
|
@Inject LightningDialogBuilder mBookmarksDialogBuilder; |
|
|
|
@Inject PreferenceManager mPreferenceManager; |
|
|
|
@Inject FaviconModel mFaviconModel; |
|
|
|
private TabsManager mTabsManager; |
|
|
|
private UIController mUiController; |
|
|
|
// Adapter |
|
private BookmarkListAdapter mBookmarkAdapter; |
|
|
|
// Preloaded images |
|
private Bitmap mWebpageBitmap, mFolderBitmap; |
|
|
|
// Views |
|
@BindView(R.id.right_drawer_list) RecyclerView mBookmarksListView; |
|
@BindView(R.id.starIcon) ImageView mBookmarkTitleImage; |
|
@BindView(R.id.icon_star) ImageView mBookmarkImage; |
|
|
|
@Nullable private Unbinder mUnbinder; |
|
|
|
// Colors |
|
private int mIconColor, mScrollIndex; |
|
|
|
private boolean mIsIncognito; |
|
|
|
@Nullable private Subscription mBookmarksSubscription; |
|
@Nullable private Subscription mFoldersSubscription; |
|
@Nullable private Subscription mBookmarkUpdateSubscription; |
|
|
|
@NonNull private final BookmarkUiModel mUiModel = new BookmarkUiModel(); |
|
|
|
@Override |
|
public void onCreate(@Nullable Bundle savedInstanceState) { |
|
super.onCreate(savedInstanceState); |
|
BrowserApp.getAppComponent().inject(this); |
|
final Bundle arguments = getArguments(); |
|
final Context context = getContext(); |
|
mUiController = (UIController) context; |
|
mTabsManager = mUiController.getTabModel(); |
|
mIsIncognito = arguments.getBoolean(INCOGNITO_MODE, false); |
|
boolean darkTheme = mPreferenceManager.getUseTheme() != 0 || mIsIncognito; |
|
mWebpageBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); |
|
mFolderBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_folder, darkTheme); |
|
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(context) : |
|
ThemeUtils.getIconLightThemeColor(context); |
|
} |
|
|
|
private TabsManager getTabsManager() { |
|
if (mTabsManager == null) { |
|
mTabsManager = mUiController.getTabModel(); |
|
} |
|
return mTabsManager; |
|
} |
|
|
|
// Handle bookmark click |
|
private final OnItemClickListener mItemClickListener = new OnItemClickListener() { |
|
@Override |
|
public void onItemClick(@NonNull HistoryItem item) { |
|
if (item.isFolder()) { |
|
mScrollIndex = ((LinearLayoutManager) mBookmarksListView.getLayoutManager()).findFirstVisibleItemPosition(); |
|
setBookmarksShown(item.getTitle(), true); |
|
} else { |
|
mUiController.bookmarkItemClicked(item); |
|
} |
|
} |
|
}; |
|
|
|
private final OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() { |
|
@Override |
|
public boolean onItemLongClick(@NonNull HistoryItem item) { |
|
handleLongPress(item); |
|
return true; |
|
} |
|
}; |
|
|
|
@Override |
|
public void onResume() { |
|
super.onResume(); |
|
if (mBookmarkAdapter != null) { |
|
setBookmarksShown(null, false); |
|
} |
|
} |
|
|
|
@Nullable |
|
@Override |
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
|
final View view = inflater.inflate(R.layout.bookmark_drawer, container, false); |
|
mUnbinder = ButterKnife.bind(this, view); |
|
mBookmarkTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); |
|
final View backView = view.findViewById(R.id.bookmark_back_button); |
|
backView.setOnClickListener(new View.OnClickListener() { |
|
@Override |
|
public void onClick(View v) { |
|
if (!mUiModel.isRootFolder()) { |
|
setBookmarksShown(null, true); |
|
mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex); |
|
} |
|
} |
|
}); |
|
setupNavigationButton(view, R.id.action_add_bookmark, R.id.icon_star); |
|
setupNavigationButton(view, R.id.action_toggle_desktop, R.id.icon_desktop); |
|
|
|
mBookmarkAdapter = new BookmarkListAdapter(mFaviconModel, mFolderBitmap, mWebpageBitmap); |
|
mBookmarkAdapter.setOnItemClickListener(mItemClickListener); |
|
mBookmarkAdapter.setOnItemLongClickListener(mItemLongClickListener); |
|
mBookmarksListView.setLayoutManager(new LinearLayoutManager(getContext())); |
|
mBookmarksListView.setAdapter(mBookmarkAdapter); |
|
|
|
setBookmarksShown(null, true); |
|
|
|
return view; |
|
} |
|
|
|
@Override |
|
public void onDestroyView() { |
|
super.onDestroyView(); |
|
|
|
SubscriptionUtils.safeUnsubscribe(mBookmarksSubscription); |
|
SubscriptionUtils.safeUnsubscribe(mFoldersSubscription); |
|
SubscriptionUtils.safeUnsubscribe(mBookmarkUpdateSubscription); |
|
|
|
if (mBookmarkAdapter != null) { |
|
mBookmarkAdapter.cleanupSubscriptions(); |
|
} |
|
|
|
if (mUnbinder != null) { |
|
mUnbinder.unbind(); |
|
mUnbinder = null; |
|
} |
|
} |
|
|
|
@Override |
|
public void onDestroy() { |
|
super.onDestroy(); |
|
|
|
SubscriptionUtils.safeUnsubscribe(mBookmarksSubscription); |
|
SubscriptionUtils.safeUnsubscribe(mFoldersSubscription); |
|
SubscriptionUtils.safeUnsubscribe(mBookmarkUpdateSubscription); |
|
|
|
if (mBookmarkAdapter != null) { |
|
mBookmarkAdapter.cleanupSubscriptions(); |
|
} |
|
} |
|
|
|
public void reinitializePreferences() { |
|
Activity activity = getActivity(); |
|
if (activity == null) { |
|
return; |
|
} |
|
boolean darkTheme = mPreferenceManager.getUseTheme() != 0 || mIsIncognito; |
|
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); |
|
} |
|
|
|
private void updateBookmarkIndicator(final String url) { |
|
SubscriptionUtils.safeUnsubscribe(mBookmarkUpdateSubscription); |
|
mBookmarkUpdateSubscription = mBookmarkManager.isBookmark(url) |
|
.subscribeOn(Schedulers.io()) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<Boolean>() { |
|
@Override |
|
public void onItem(@Nullable Boolean item) { |
|
mBookmarkUpdateSubscription = null; |
|
Preconditions.checkNonNull(item); |
|
Activity activity = getActivity(); |
|
if (mBookmarkImage == null || activity == null) { |
|
return; |
|
} |
|
if (!item) { |
|
mBookmarkImage.setImageResource(R.drawable.ic_action_star); |
|
mBookmarkImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); |
|
} else { |
|
mBookmarkImage.setImageResource(R.drawable.ic_bookmark); |
|
mBookmarkImage.setColorFilter(ThemeUtils.getAccentColor(activity), PorterDuff.Mode.SRC_IN); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
@Override |
|
public void handleBookmarkDeleted(@NonNull HistoryItem item) { |
|
if (item.isFolder()) { |
|
setBookmarksShown(null, false); |
|
} else { |
|
mBookmarkAdapter.deleteItem(item); |
|
} |
|
} |
|
|
|
private void setBookmarksShown(@Nullable final String folder, final boolean animate) { |
|
SubscriptionUtils.safeUnsubscribe(mBookmarksSubscription); |
|
mBookmarksSubscription = mBookmarkManager.getBookmarksFromFolderSorted(folder) |
|
.subscribeOn(Schedulers.io()) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { |
|
@Override |
|
public void onItem(@Nullable final List<HistoryItem> item) { |
|
mBookmarksSubscription = null; |
|
Preconditions.checkNonNull(item); |
|
|
|
mUiModel.setCurrentFolder(folder); |
|
if (folder == null) { |
|
SubscriptionUtils.safeUnsubscribe(mFoldersSubscription); |
|
mFoldersSubscription = mBookmarkManager.getFoldersSorted() |
|
.subscribeOn(Schedulers.io()) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { |
|
@Override |
|
public void onItem(@Nullable List<HistoryItem> folders) { |
|
mFoldersSubscription = null; |
|
Preconditions.checkNonNull(folders); |
|
item.addAll(folders); |
|
setBookmarkDataSet(item, animate); |
|
} |
|
}); |
|
} else { |
|
setBookmarkDataSet(item, animate); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
private void setBookmarkDataSet(@NonNull List<HistoryItem> items, boolean animate) { |
|
mBookmarkAdapter.updateItems(items); |
|
final int resource; |
|
if (mUiModel.isRootFolder()) { |
|
resource = R.drawable.ic_action_star; |
|
} else { |
|
resource = R.drawable.ic_action_back; |
|
} |
|
|
|
if (animate) { |
|
Animation transition = AnimationUtils.createRotationTransitionAnimation(mBookmarkTitleImage, resource); |
|
mBookmarkTitleImage.startAnimation(transition); |
|
} else { |
|
mBookmarkTitleImage.setImageResource(resource); |
|
} |
|
} |
|
|
|
private void setupNavigationButton(@NonNull View view, @IdRes int buttonId, @IdRes int imageId) { |
|
FrameLayout frameButton = view.findViewById(buttonId); |
|
frameButton.setOnClickListener(this); |
|
frameButton.setOnLongClickListener(this); |
|
ImageView buttonImage = view.findViewById(imageId); |
|
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); |
|
} |
|
|
|
private void handleLongPress(@NonNull final HistoryItem item) { |
|
if (item.isFolder()) { |
|
mBookmarksDialogBuilder.showBookmarkFolderLongPressedDialog(getActivity(), mUiController, item); |
|
} else { |
|
mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(getActivity(), mUiController, item); |
|
} |
|
} |
|
|
|
@Override |
|
public void onClick(@NonNull View v) { |
|
switch (v.getId()) { |
|
case R.id.action_add_bookmark: |
|
mUiController.bookmarkButtonClicked(); |
|
break; |
|
case R.id.action_toggle_desktop: |
|
LightningView current = getTabsManager().getCurrentTab(); |
|
if (current != null) { |
|
current.toggleDesktopUA(getActivity()); |
|
current.reload(); |
|
// TODO add back drawer closing |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
@Override |
|
public boolean onLongClick(View v) { |
|
return false; |
|
} |
|
|
|
@Override |
|
public void navigateBack() { |
|
if (mUiModel.isRootFolder()) { |
|
mUiController.closeBookmarksDrawer(); |
|
} else { |
|
setBookmarksShown(null, true); |
|
mBookmarksListView.getLayoutManager().scrollToPosition(mScrollIndex); |
|
} |
|
} |
|
|
|
@Override |
|
public void handleUpdatedUrl(@NonNull String url) { |
|
updateBookmarkIndicator(url); |
|
String folder = mUiModel.getCurrentFolder(); |
|
setBookmarksShown(folder, false); |
|
} |
|
|
|
static class BookmarkViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { |
|
@BindView(R.id.textBookmark) TextView txtTitle; |
|
@BindView(R.id.faviconBookmark) ImageView favicon; |
|
|
|
@NonNull private final BookmarkListAdapter adapter; |
|
|
|
@Nullable private final OnItemLongClickListener onItemLongClickListener; |
|
@Nullable private final OnItemClickListener onItemClickListener; |
|
|
|
BookmarkViewHolder(@NonNull View itemView, |
|
@NonNull BookmarkListAdapter adapter, |
|
@Nullable OnItemLongClickListener onItemLongClickListener, |
|
@Nullable OnItemClickListener onItemClickListener) { |
|
super(itemView); |
|
ButterKnife.bind(this, itemView); |
|
|
|
this.adapter = adapter; |
|
|
|
this.onItemClickListener = onItemClickListener; |
|
this.onItemLongClickListener = onItemLongClickListener; |
|
|
|
itemView.setOnLongClickListener(this); |
|
itemView.setOnClickListener(this); |
|
} |
|
|
|
@Override |
|
public void onClick(View v) { |
|
int index = getAdapterPosition(); |
|
if (onItemClickListener != null && index != RecyclerView.NO_ID) { |
|
onItemClickListener.onItemClick(adapter.itemAt(index)); |
|
} |
|
} |
|
|
|
@Override |
|
public boolean onLongClick(View v) { |
|
int index = getAdapterPosition(); |
|
return index != RecyclerView.NO_POSITION && onItemLongClickListener != null && |
|
onItemLongClickListener.onItemLongClick(adapter.itemAt(index)); |
|
} |
|
} |
|
|
|
interface OnItemLongClickListener { |
|
boolean onItemLongClick(@NonNull HistoryItem item); |
|
} |
|
|
|
interface OnItemClickListener { |
|
void onItemClick(@NonNull HistoryItem item); |
|
} |
|
|
|
private static class BookmarkListAdapter extends RecyclerView.Adapter<BookmarkViewHolder> { |
|
|
|
@NonNull private List<HistoryItem> mBookmarks = new ArrayList<>(); |
|
@NonNull private final FaviconModel mFaviconModel; |
|
@NonNull private final Bitmap mFolderBitmap; |
|
@NonNull private final Bitmap mWebpageBitmap; |
|
@NonNull private final Map<String, Subscription> mFaviconFetchSubscriptions = new ConcurrentHashMap<>(); |
|
|
|
@Nullable private OnItemLongClickListener mOnItemLongCLickListener; |
|
@Nullable private OnItemClickListener mOnItemClickListener; |
|
|
|
BookmarkListAdapter(@NonNull FaviconModel faviconModel, |
|
@NonNull Bitmap folderBitmap, |
|
@NonNull Bitmap webpageBitmap) { |
|
mFaviconModel = faviconModel; |
|
mFolderBitmap = folderBitmap; |
|
mWebpageBitmap = webpageBitmap; |
|
} |
|
|
|
void setOnItemLongClickListener(@Nullable OnItemLongClickListener listener) { |
|
mOnItemLongCLickListener = listener; |
|
} |
|
|
|
void setOnItemClickListener(@Nullable OnItemClickListener listener) { |
|
mOnItemClickListener = listener; |
|
} |
|
|
|
@NonNull |
|
HistoryItem itemAt(int position) { |
|
return mBookmarks.get(position); |
|
} |
|
|
|
void deleteItem(@NonNull HistoryItem item) { |
|
List<HistoryItem> newList = new ArrayList<>(mBookmarks); |
|
newList.remove(item); |
|
updateItems(newList); |
|
} |
|
|
|
void updateItems(@NonNull List<HistoryItem> newList) { |
|
final List<HistoryItem> oldList = mBookmarks; |
|
mBookmarks = newList; |
|
|
|
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { |
|
@Override |
|
public int getOldListSize() { |
|
return oldList.size(); |
|
} |
|
|
|
@Override |
|
public int getNewListSize() { |
|
return mBookmarks.size(); |
|
} |
|
|
|
@Override |
|
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { |
|
return oldList.get(oldItemPosition).equals(mBookmarks.get(newItemPosition)); |
|
} |
|
|
|
@Override |
|
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { |
|
return oldList.get(oldItemPosition).equals(mBookmarks.get(newItemPosition)); |
|
} |
|
}); |
|
|
|
diffResult.dispatchUpdatesTo(this); |
|
} |
|
|
|
void cleanupSubscriptions() { |
|
for (Subscription subscription : mFaviconFetchSubscriptions.values()) { |
|
subscription.unsubscribe(); |
|
} |
|
mFaviconFetchSubscriptions.clear(); |
|
} |
|
|
|
@Override |
|
public BookmarkViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
|
LayoutInflater inflater = LayoutInflater.from(parent.getContext()); |
|
View itemView = inflater.inflate(R.layout.bookmark_list_item, parent, false); |
|
|
|
return new BookmarkViewHolder(itemView, this, mOnItemLongCLickListener, mOnItemClickListener); |
|
} |
|
|
|
@Override |
|
public void onViewRecycled(BookmarkViewHolder holder) { |
|
super.onViewRecycled(holder); |
|
} |
|
|
|
@Override |
|
public void onBindViewHolder(final BookmarkViewHolder holder, int position) { |
|
ViewCompat.jumpDrawablesToCurrentState(holder.itemView); |
|
|
|
final HistoryItem web = mBookmarks.get(position); |
|
holder.txtTitle.setText(web.getTitle()); |
|
if (web.isFolder()) { |
|
holder.favicon.setImageBitmap(mFolderBitmap); |
|
} else if (web.getBitmap() == null) { |
|
holder.favicon.setImageBitmap(mWebpageBitmap); |
|
holder.favicon.setTag(web.getUrl().hashCode()); |
|
|
|
final String url = web.getUrl(); |
|
|
|
Subscription oldSubscription = mFaviconFetchSubscriptions.get(url); |
|
SubscriptionUtils.safeUnsubscribe(oldSubscription); |
|
|
|
final Subscription faviconSubscription = mFaviconModel.faviconForUrl(url, web.getTitle()) |
|
.subscribeOn(Schedulers.io()) |
|
.observeOn(Schedulers.main()) |
|
.subscribe(new SingleOnSubscribe<Bitmap>() { |
|
@Override |
|
public void onItem(@Nullable Bitmap item) { |
|
mFaviconFetchSubscriptions.remove(url); |
|
Object tag = holder.favicon.getTag(); |
|
if (tag != null && tag.equals(url.hashCode())) { |
|
holder.favicon.setImageBitmap(item); |
|
} |
|
|
|
web.setBitmap(item); |
|
} |
|
}); |
|
|
|
mFaviconFetchSubscriptions.put(url, faviconSubscription); |
|
} else { |
|
holder.favicon.setImageBitmap(web.getBitmap()); |
|
} |
|
|
|
} |
|
|
|
@Override |
|
public int getItemCount() { |
|
return mBookmarks.size(); |
|
} |
|
} |
|
|
|
}
|
|
|