/**
* **************************************************************************
* BaseBrowserFragment.java
* ****************************************************************************
* Copyright © 2015 VLC authors and VideoLAN
* Author: Geoffrey Métais
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* ***************************************************************************
*/
package org.videolan.vlc.gui.browser;
import android.annotation.TargetApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.MediaBrowser;
import org.videolan.vlc.MediaWrapper;
import org.videolan.vlc.R;
import org.videolan.vlc.VLCApplication;
import org.videolan.vlc.gui.DividerItemDecoration;
import org.videolan.vlc.gui.MainActivity;
import org.videolan.vlc.gui.SecondaryActivity;
import org.videolan.vlc.gui.dialogs.CommonDialogs;
import org.videolan.vlc.gui.video.VideoPlayerActivity;
import org.videolan.vlc.interfaces.IRefreshable;
import org.videolan.vlc.util.Util;
import org.videolan.vlc.util.VLCInstance;
import org.videolan.vlc.util.VLCRunnable;
import org.videolan.vlc.util.WeakHandler;
import org.videolan.vlc.widget.ContextMenuRecyclerView;
import org.videolan.vlc.widget.SwipeRefreshLayout;
import java.util.ArrayList;
public abstract class BaseBrowserFragment extends MediaBrowserFragment implements IRefreshable, MediaBrowser.EventListener, SwipeRefreshLayout.OnRefreshListener {
protected static final String TAG = "VLC/BaseBrowserFragment";
public static String ROOT = "smb";
public static final String KEY_MRL = "key_mrl";
public static final String KEY_MEDIA = "key_media";
public static final String KEY_MEDIA_LIST = "key_media_list";
public static final String KEY_POSITION = "key_list";
protected BrowserFragmentHandler mHandler;
protected MediaBrowser mMediaBrowser;
protected ContextMenuRecyclerView mRecyclerView;
protected BaseBrowserAdapter mAdapter;
protected LinearLayoutManager mLayoutManager;
protected TextView mEmptyView;
public String mMrl;
protected MediaWrapper mCurrentMedia;
protected int mSavedPosition = -1, mFavorites = 0;
public boolean mRoot;
private SparseArray<ArrayList<MediaWrapper>> mMediaLists = new SparseArray<ArrayList<MediaWrapper>>();
private ArrayList<MediaWrapper> mediaList;
public int mCurrentParsedPosition = 0;
protected abstract Fragment createFragment();
protected abstract void browseRoot();
protected abstract String getCategoryTitle();
public BaseBrowserFragment(){
mHandler = new BrowserFragmentHandler(this);
mAdapter = new BaseBrowserAdapter(this);
}
public void onCreate(Bundle bundle){
super.onCreate(bundle);
if (bundle == null)
bundle = getArguments();
if (bundle != null){
mediaList = bundle.getParcelableArrayList(KEY_MEDIA_LIST);
if (mediaList != null)
mAdapter.addAll(mediaList);
mCurrentMedia = bundle.getParcelable(KEY_MEDIA);
if (mCurrentMedia != null)
mMrl = mCurrentMedia.getLocation();
else
mMrl = bundle.getString(KEY_MRL);
mSavedPosition = bundle.getInt(KEY_POSITION);
} else if (getActivity().getIntent() != null){
mMrl = getActivity().getIntent().getDataString();
getActivity().setIntent(null);
}
}
protected int getLayoutId(){
return R.layout.directory_browser;
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View v = inflater.inflate(getLayoutId(), container, false);
mRecyclerView = (ContextMenuRecyclerView) v.findViewById(R.id.network_list);
mEmptyView = (TextView) v.findViewById(android.R.id.empty);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.addItemDecoration(new DividerItemDecoration(VLCApplication.getAppContext(), DividerItemDecoration.VERTICAL_LIST));
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addOnScrollListener(mScrollListener);
registerForContextMenu(mRecyclerView);
mSwipeRefreshLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipeLayout);
mSwipeRefreshLayout.setColorSchemeResources(R.color.orange700);
mSwipeRefreshLayout.setOnRefreshListener(this);
return v;
}
@Override
public void onStart() {
super.onStart();
mMediaBrowser = new MediaBrowser(VLCInstance.get(), this);
}
public void onStop(){
super.onStop();
releaseBrowser();
}
private void releaseBrowser() {
if (mMediaBrowser != null) {
mMediaBrowser.release();
mMediaBrowser = null;
}
}
public void onSaveInstanceState(Bundle outState){
outState.putString(KEY_MRL, mMrl);
outState.putParcelable(KEY_MEDIA, mCurrentMedia);
outState.putParcelableArrayList(KEY_MEDIA_LIST, mediaList);
if (mRecyclerView != null) {
outState.putInt(KEY_POSITION, mLayoutManager.findFirstCompletelyVisibleItemPosition());
}
super.onSaveInstanceState(outState);
}
public boolean isRootDirectory(){
return mRoot;
}
public String getTitle(){
if (mRoot)
return getCategoryTitle();
else
return mCurrentMedia != null ? mCurrentMedia.getTitle() : mMrl;
}
@Override
protected void display() {
if (!mReadyToDisplay) {
mReadyToDisplay = true;
update();
return;
}
updateDisplay();
}
public void goBack(){
if (!mRoot)
getActivity().getSupportFragmentManager().popBackStack();
else
getActivity().finish();
}
public void browse (MediaWrapper media, int position, boolean save){
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
Fragment next = createFragment();
Bundle args = new Bundle();
ArrayList<MediaWrapper> list = mMediaLists != null ? mMediaLists.get(position) : null;
if(list != null && !list.isEmpty())
args.putParcelableArrayList(KEY_MEDIA_LIST, list);
args.putParcelable(KEY_MEDIA, media);
next.setArguments(args);
ft.replace(R.id.fragment_placeholder, next, media.getLocation());
if (save)
ft.addToBackStack(mMrl);
ft.commit();
}
@Override
public void onMediaAdded(int index, Media media) {
mAdapter.addItem(media, mReadyToDisplay && mRoot, mRoot);
if (mReadyToDisplay)
updateEmptyView();
if (mRoot)
mHandler.sendEmptyMessage(BrowserFragmentHandler.MSG_HIDE_LOADING);
}
@Override
public void onMediaRemoved(int index, Media media) {
mAdapter.removeItem(index, mReadyToDisplay);
}
@Override
public void onBrowseEnd() {
releaseBrowser();
mHandler.sendEmptyMessage(BrowserFragmentHandler.MSG_HIDE_LOADING);
if (mReadyToDisplay)
display();
}
@Override
public void onRefresh() {
mSavedPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition();
refresh();
}
RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int topRowVerticalPosition =
(recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
mSwipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);
}
};
/**
* Update views visibility and emptiness info
*/
protected void updateEmptyView(){
if (mAdapter.isEmpty()){
mEmptyView.setVisibility(View.VISIBLE);
mRecyclerView.setVisibility(View.GONE);
mSwipeRefreshLayout.setEnabled(false);
} else if (mEmptyView.getVisibility() == View.VISIBLE) {
mEmptyView.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.VISIBLE);
mSwipeRefreshLayout.setEnabled(true);
}
}
protected void update(){
update(false);
}
protected void update(boolean force){
if (mReadyToDisplay) {
updateEmptyView();
if (force || mAdapter.isEmpty()) {
refresh();
} else {
updateDisplay();
}
}
}
protected void updateDisplay() {
if (!mAdapter.isEmpty()) {
if (mSavedPosition > 0) {
mLayoutManager.scrollToPositionWithOffset(mSavedPosition, 0);
mSavedPosition = 0;
}
}
mAdapter.notifyDataSetChanged();
parseSubDirectories();
focusHelper();
}
@Override
public void refresh() {
mAdapter.clear();
if (mMediaBrowser == null)
mMediaBrowser = new MediaBrowser(VLCInstance.get(), this);
else
mMediaBrowser.changeEventListener(this);
mCurrentParsedPosition = 0;
if (mRoot)
browseRoot();
else
mMediaBrowser.browse(mCurrentMedia != null ? mCurrentMedia.getUri() : Uri.parse(mMrl));
mHandler.sendEmptyMessageDelayed(BrowserFragmentHandler.MSG_SHOW_LOADING, 300);
}
protected static class BrowserFragmentHandler extends WeakHandler<BaseBrowserFragment> {
public static final int MSG_SHOW_LOADING = 0;
public static final int MSG_HIDE_LOADING = 1;
public BrowserFragmentHandler(BaseBrowserFragment owner) {
super(owner);
}
@Override
public void handleMessage(Message msg) {
BaseBrowserFragment fragment = getOwner();
switch (msg.what){
case MSG_SHOW_LOADING:
fragment.mSwipeRefreshLayout.setRefreshing(true);
break;
case MSG_HIDE_LOADING:
removeMessages(MSG_SHOW_LOADING);
fragment.mSwipeRefreshLayout.setRefreshing(false);
break;
}
}
}
protected void focusHelper() {
if (getActivity() == null || !(getActivity() instanceof MainActivity))
return;
boolean isEmpty = mAdapter.isEmpty();
MainActivity main = (MainActivity) getActivity();
main.setMenuFocusDown(isEmpty, R.id.network_list);
main.setSearchAsFocusDown(isEmpty, getView(), R.id.network_list);
}
public void clear(){
mAdapter.clear();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
ContextMenuRecyclerView.RecyclerContextMenuInfo info = (ContextMenuRecyclerView
.RecyclerContextMenuInfo) menuInfo;
if (info != null)
setContextMenu(getActivity().getMenuInflater(), menu, info.position);
}
protected void setContextMenu(MenuInflater inflater, Menu menu, int position) {
MediaWrapper mw = (MediaWrapper) mAdapter.getItem(position);
boolean canWrite = Util.canWrite(mw.getLocation());
boolean isAudio = mw.getType() == MediaWrapper.TYPE_AUDIO;
boolean isVideo = mw.getType() == MediaWrapper.TYPE_VIDEO;
if (isAudio || isVideo) {
inflater.inflate(R.menu.directory_view_file, menu);
menu.findItem(R.id.directory_view_delete).setVisible(canWrite);
menu.findItem(R.id.directory_view_info).setVisible(isVideo);
} else if (mw.getType() == MediaWrapper.TYPE_DIR) {
boolean isEmpty = mMediaLists.get(position) == null || mMediaLists.get(position).isEmpty();
if (canWrite || !isEmpty) {
inflater.inflate(R.menu.directory_view_dir, menu);
// if (canWrite) {
// boolean nomedia = new File(mw.getLocation() + "/.nomedia").exists();
// menu.findItem(R.id.directory_view_hide_media).setVisible(!nomedia);
// menu.findItem(R.id.directory_view_show_media).setVisible(nomedia);
// } else {
// menu.findItem(R.id.directory_view_hide_media).setVisible(false);
// menu.findItem(R.id.directory_view_show_media).setVisible(false);
// }
menu.findItem(R.id.directory_view_play_folder).setVisible(!isEmpty);
menu.findItem(R.id.directory_view_delete).setVisible(canWrite);
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
ContextMenuRecyclerView.RecyclerContextMenuInfo info = (ContextMenuRecyclerView
.RecyclerContextMenuInfo) item.getMenuInfo();
if (info != null && handleContextItemSelected(item, info.position))
return true;
return super.onContextItemSelected(item);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onPopupMenu(View anchor, final int position) {
if (!AndroidUtil.isHoneycombOrLater()) {
// Call the "classic" context menu
anchor.performLongClick();
return;
}
PopupMenu popupMenu = new PopupMenu(getActivity(), anchor);
setContextMenu(popupMenu.getMenuInflater(), popupMenu.getMenu(), position);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return handleContextItemSelected(item, position);
}
});
popupMenu.show();
}
protected boolean handleContextItemSelected(MenuItem item, int position) {
int id = item.getItemId();
if (! (mAdapter.getItem(position) instanceof MediaWrapper))
return super.onContextItemSelected(item);
MediaWrapper mw = (MediaWrapper) mAdapter.getItem(position);
switch (id){
case R.id.directory_view_play:
mw.removeFlags(MediaWrapper.MEDIA_FORCE_AUDIO);
Util.openMedia(getActivity(), mw);
return true;
case R.id.directory_view_append: {
if (mService != null)
mService.append(mw);
return true;
}
case R.id.directory_view_delete:
AlertDialog alertDialog = CommonDialogs.deleteMedia(
mw.getType(), getActivity(), mw.getLocation(),
new VLCRunnable() {
@Override
public void run(Object o) {
refresh();
}
});
alertDialog.show();
return true;
case R.id.directory_view_info:
Intent i = new Intent(getActivity(), SecondaryActivity.class);
i.putExtra("fragment", "mediaInfo");
i.putExtra("param", mw.getUri().toString());
startActivity(i);
return true;
case R.id.directory_view_play_audio: {
if (mService != null) {
mw.addFlags(MediaWrapper.MEDIA_FORCE_AUDIO);
mService.load(mw);
}
return true;
}
case R.id.directory_view_play_video:
VideoPlayerActivity.start(getActivity(), mw.getUri());
return true;
case R.id.directory_view_play_folder:
ArrayList<MediaWrapper> mediaList = new ArrayList<MediaWrapper>();
for (MediaWrapper mediaItem : mMediaLists.get(position)){
if (mediaItem.getType() == MediaWrapper.TYPE_AUDIO || mediaItem.getType() == MediaWrapper.TYPE_VIDEO)
mediaList.add(mediaItem);
}
Util.openList(getActivity(), mediaList, 0);
return true;
// case R.id.directory_view_hide_media:
// try {
// if (new File(mw.getLocation()+"/.nomedia").createNewFile())
// updateLib();
// } catch (IOException e) {}
// return true;
// case R.id.directory_view_show_media:
// if (new File(mw.getLocation()+"/.nomedia").delete())
// updateLib();
// return true;
}
return false;
}
protected void parseSubDirectories() {
if (mCurrentParsedPosition == -1 || mAdapter.isEmpty())
return;
mMediaLists.clear();
if (mMediaBrowser == null)
mMediaBrowser = new MediaBrowser(VLCInstance.get(), mFoldersBrowserListener);
else
mMediaBrowser.changeEventListener(mFoldersBrowserListener);
mCurrentParsedPosition = 0;
Object item;
MediaWrapper mw;
while (mCurrentParsedPosition <mAdapter.getItemCount()){
item = mAdapter.getItem(mCurrentParsedPosition);
if (item instanceof BaseBrowserAdapter.Storage) {
mw = new MediaWrapper(((BaseBrowserAdapter.Storage) item).getUri());
mw.setType(MediaWrapper.TYPE_DIR);
} else if (item instanceof MediaWrapper){
mw = (MediaWrapper) item;
} else
mw = null;
if (mw != null){
if (mw.getType() == MediaWrapper.TYPE_DIR || mw.getType() == MediaWrapper.TYPE_PLAYLIST){
mMediaBrowser.browse(mw.getUri());
return;
}
}
++mCurrentParsedPosition;
}
}
private MediaBrowser.EventListener mFoldersBrowserListener = new MediaBrowser.EventListener(){
ArrayList<MediaWrapper> directories = new ArrayList<MediaWrapper>();
ArrayList<MediaWrapper> files = new ArrayList<MediaWrapper>();
@Override
public void onMediaAdded(int index, Media media) {
int type = media.getType();
if (type == Media.Type.Directory)
directories.add(new MediaWrapper(media));
else if (type == Media.Type.File)
files.add(new MediaWrapper(media));
}
@Override
public void onMediaRemoved(int index, Media media) {}
@Override
public void onBrowseEnd() {
if (mAdapter.isEmpty()) {
mCurrentParsedPosition = -1;
releaseBrowser();
return;
}
String holderText = getDescription(directories.size(), files.size());
MediaWrapper mw = null;
if (!TextUtils.equals(holderText, "")) {
mAdapter.setDescription(mCurrentParsedPosition, holderText);
directories.addAll(files);
mMediaLists.put(mCurrentParsedPosition, new ArrayList<MediaWrapper>(directories));
}
while (++mCurrentParsedPosition < mAdapter.getItemCount()){ //skip media that are not browsable
if (mAdapter.getItem(mCurrentParsedPosition) instanceof MediaWrapper) {
mw = (MediaWrapper) mAdapter.getItem(mCurrentParsedPosition);
if (mw.getType() == MediaWrapper.TYPE_DIR || mw.getType() == MediaWrapper.TYPE_PLAYLIST)
break;
} else if (mAdapter.getItem(mCurrentParsedPosition) instanceof BaseBrowserAdapter.Storage) {
mw = new MediaWrapper(((BaseBrowserAdapter.Storage) mAdapter.getItem(mCurrentParsedPosition)).getUri());
break;
} else
mw = null;
}
if (mw != null) {
if (mCurrentParsedPosition < mAdapter.getItemCount()) {
mMediaBrowser.browse(mw.getUri());
} else {
mCurrentParsedPosition = -1;
releaseBrowser();
}
} else
releaseBrowser();
directories .clear();
files.clear();
}
private String getDescription(int folderCount, int mediaFileCount) {
String holderText = "";
if (folderCount > 0) {
holderText += VLCApplication.getAppResources().getQuantityString(
R.plurals.subfolders_quantity, folderCount, folderCount
);
if (mediaFileCount > 0)
holderText += ", ";
}
if (mediaFileCount > 0)
holderText += VLCApplication.getAppResources().getQuantityString(
R.plurals.mediafiles_quantity, mediaFileCount,
mediaFileCount);
else if (folderCount == 0 && mediaFileCount == 0)
holderText = getString(R.string.directory_empty);
return holderText;
}
};
}