Commit f976d606 authored by Edward Wang's avatar Edward Wang

Add a directory browsing view

This allows the user to browse media by file system hierachy, returning some control to us power users.
parent 34a1aeb4
......@@ -49,6 +49,14 @@
android:layout_width="80dip"
android:layout_height="wrap_content"
android:gravity="center_horizontal" />
<TextView
android:text="@string/directories"
android:textSize="16dip"
android:textColor="#888888"
android:id="@+id/directory"
android:layout_width="80dip"
android:layout_height="wrap_content"
android:gravity="center_horizontal" />
</LinearLayout>
</HorizontalScrollView>
<View
......@@ -93,5 +101,12 @@
android:dividerHeight="1dp"
android:groupIndicator="@null"
android:fastScrollEnabled="true" />
<ListView
android:id="@+id/directory_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@color/item_common"
android:dividerHeight="1dp"
android:fastScrollEnabled="true" />
</org.videolan.vlc.widget.FlingViewGroup>
</LinearLayout>
......@@ -23,6 +23,7 @@ package org.videolan.vlc.gui.audio;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
......@@ -77,6 +78,7 @@ public class AudioBrowserFragment extends SherlockFragment implements ISortable
private AudioPlaylistAdapter mArtistsAdapter;
private AudioPlaylistAdapter mAlbumsAdapter;
private AudioPlaylistAdapter mGenresAdapter;
private AudioDirectoryAdapter mDirectoryAdapter;
public final static int SORT_BY_TITLE = 0;
public final static int SORT_BY_LENGTH = 1;
......@@ -87,6 +89,7 @@ public class AudioBrowserFragment extends SherlockFragment implements ISortable
public final static int MODE_ALBUM = 1;
public final static int MODE_SONG = 2;
public final static int MODE_GENRE = 3;
public final static int MODE_DIRECTORY = 4;
public final static int MENU_PLAY = Menu.FIRST;
public final static int MENU_APPEND = Menu.FIRST + 1;
......@@ -107,6 +110,7 @@ public class AudioBrowserFragment extends SherlockFragment implements ISortable
mArtistsAdapter = new AudioPlaylistAdapter(getActivity(), R.plurals.albums, R.plurals.songs);
mAlbumsAdapter = new AudioPlaylistAdapter(getActivity(), R.plurals.songs, R.plurals.songs);
mGenresAdapter = new AudioPlaylistAdapter(getActivity(), R.plurals.albums, R.plurals.songs);
mDirectoryAdapter = new AudioDirectoryAdapter(getActivity());
}
@Override
......@@ -123,21 +127,30 @@ public class AudioBrowserFragment extends SherlockFragment implements ISortable
ExpandableListView artistList = (ExpandableListView)v.findViewById(R.id.artists_list);
ExpandableListView albumList = (ExpandableListView)v.findViewById(R.id.albums_list);
ExpandableListView genreList = (ExpandableListView)v.findViewById(R.id.genres_list);
ListView directoryList = (ListView)v.findViewById(R.id.directory_list);
songsList.setAdapter(mSongsAdapter);
artistList.setAdapter(mArtistsAdapter);
albumList.setAdapter(mAlbumsAdapter);
genreList.setAdapter(mGenresAdapter);
directoryList.setAdapter(mDirectoryAdapter);
songsList.setOnItemClickListener(songListener);
artistList.setOnGroupClickListener(playlistListener);
albumList.setOnGroupClickListener(playlistListener);
genreList.setOnGroupClickListener(playlistListener);
directoryList.setOnItemClickListener(directoryListener);
artistList.setOnChildClickListener(playlistChildListener);
albumList.setOnChildClickListener(playlistChildListener);
genreList.setOnChildClickListener(playlistChildListener);
//directoryList.setOnChildClickListener(playlistChildListener);
songsList.setOnCreateContextMenuListener(contextMenuListener);
artistList.setOnCreateContextMenuListener(contextMenuListener);
albumList.setOnCreateContextMenuListener(contextMenuListener);
genreList.setOnCreateContextMenuListener(contextMenuListener);
directoryList.setOnCreateContextMenuListener(contextMenuListener);
return v;
}
......@@ -158,6 +171,21 @@ public class AudioBrowserFragment extends SherlockFragment implements ISortable
}
};
OnItemClickListener directoryListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View v, int p, long id) {
Boolean success = mDirectoryAdapter.browse(p);
if(!success) { /* Clicked on a media file */
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add(mDirectoryAdapter.getMediaLocation(p));
mAudioController.load(arrayList, 0);
Intent intent = new Intent(getActivity(), AudioPlayerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
}
};
OnGroupClickListener playlistListener = new OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView elv, View v, int groupPosition, long id) {
......@@ -389,6 +417,7 @@ public class AudioBrowserFragment extends SherlockFragment implements ISortable
mArtistsAdapter.clear();
mAlbumsAdapter.clear();
mGenresAdapter.clear();
mDirectoryAdapter.clear();
switch(mSortBy) {
case SORT_BY_LENGTH:
......@@ -429,6 +458,7 @@ public class AudioBrowserFragment extends SherlockFragment implements ISortable
mArtistsAdapter.notifyDataSetChanged();
mAlbumsAdapter.notifyDataSetChanged();
mGenresAdapter.notifyDataSetChanged();
mDirectoryAdapter.notifyDataSetChanged();
}
public void sortBy(int sortby) {
......
/*****************************************************************************
* AudioPlaylistAdapter.java
*****************************************************************************
* Copyright © 2012 VLC authors and VideoLAN
* Copyright © 2012 Edward Wang
*
* 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.audio;
import java.io.File;
import java.io.FileFilter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import org.videolan.vlc.LibVLC;
import org.videolan.vlc.Media;
import org.videolan.vlc.R;
import org.videolan.vlc.Util;
import android.content.Context;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class AudioDirectoryAdapter extends BaseAdapter {
public final static String TAG = "VLC/AudioDirectoryAdapter";
/**
* Filter: accept only media files and directories
*/
private class AudioDirectoryAdapterFilter implements FileFilter {
public boolean accept(File f) {
if(f.isHidden())
return false;
if(f.isDirectory() && !Media.FOLDER_BLACKLIST.contains(f.getPath().toLowerCase()))
return true;
for(String ext : Media.EXTENTIONS) {
if(f.getPath().toLowerCase().endsWith(ext))
return true;
}
return false;
}
}
/**
* Private helper class to implement the directory tree
*/
public class Node implements Comparable<AudioDirectoryAdapter.Node> {
public ArrayList<AudioDirectoryAdapter.Node> children;
String name;
public Boolean isFile;
public Node(String _name) {
this.name = _name;
this.children = new ArrayList<AudioDirectoryAdapter.Node>();
this.isFile = false;
}
public Boolean isFile() {
return this.isFile;
}
public void setIsFile() {
this.isFile = true;
}
public Boolean existsChild(String _n) {
for(AudioDirectoryAdapter.Node n : this.children) {
if(n.name == _n) return true;
}
return false;
}
public AudioDirectoryAdapter.Node ensureExists(String _n) {
for(AudioDirectoryAdapter.Node n : this.children) {
if(n.name == _n) return n;
}
AudioDirectoryAdapter.Node nn = new Node(_n);
this.children.add(nn);
return nn;
}
public int subfolderCount() {
int c = 0;
for(AudioDirectoryAdapter.Node n : this.children) {
if(n.isFile() == false && n.name != "..") c++;
}
return c;
}
public int subfilesCount() {
int c = 0;
for(AudioDirectoryAdapter.Node n : this.children) {
if(n.isFile() == true) c++;
}
return c;
}
@Override
public int compareTo(Node arg0) {
if(this.isFile && !(arg0.isFile))
return 1;
else if(!(this.isFile) && arg0.isFile)
return -1;
else
return this.name.compareTo(arg0.name);
}
}
static class DirectoryViewHolder {
View layout;
TextView title;
TextView text;
Boolean isFile;
}
public void populateNode(AudioDirectoryAdapter.Node n, String MRL) {
Log.v(TAG, "populating " + MRL);
File file = new File(MRL);
if(!file.exists() || !file.isDirectory())
return;
File[] files = file.listFiles(new AudioDirectoryAdapterFilter());
/* If no sub-directories or I/O error don't crash */
if(files == null || files.length < 1) {
//return
} else {
for(int i = 0; i < files.length; i++) {
AudioDirectoryAdapter.Node nss = new AudioDirectoryAdapter.Node(files[i].getName());
if(files[i].isFile())
nss.setIsFile();
String mCurrentDir_old = mCurrentDir;
mCurrentDir = MRL;
this.populateNode(nss, MRL + "/" + nss.name);
mCurrentDir = mCurrentDir_old;
n.children.add(nss);
}
Collections.sort(n.children);
}
/* Don't let the user escape into the wild by jumping above the root dir */
if(mCurrentDir.contains(mRootDir) && !mCurrentDir.equals(mRootDir)) {
AudioDirectoryAdapter.Node up = new AudioDirectoryAdapter.Node("..");
n.children.add(0, up);
}
}
private Context mContext;
private LayoutInflater mInflater;
private AudioDirectoryAdapter.Node mRootNode;
private AudioDirectoryAdapter.Node mCurrentNode;
private String mRootDir;
private String mCurrentDir;
public AudioDirectoryAdapter(Context context) {
AudioDirectoryAdapter_Core(context,
PreferenceManager.getDefaultSharedPreferences(context).getString("directories_root", "/")
);
}
public AudioDirectoryAdapter(Context context, String rootDir) {
AudioDirectoryAdapter_Core(context, rootDir);
}
private void AudioDirectoryAdapter_Core(Context context, String rootDir) {
rootDir = Util.stripTrailingSlash(rootDir);
Log.v(TAG, "rootMRL is " + rootDir);
mContext = context;
mInflater = LayoutInflater.from(context);
mRootNode = new AudioDirectoryAdapter.Node(rootDir);
mCurrentDir = rootDir;
mRootDir = rootDir;
this.populateNode(mRootNode, rootDir);
mCurrentNode = mRootNode;
}
@Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
@Override
public int getCount() {
return mCurrentNode.children.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AudioDirectoryAdapter.Node selectedNode = mCurrentNode.children.get(position);
DirectoryViewHolder holder;
View v = convertView;
/* If view not created or type changed */
if (v == null || ((DirectoryViewHolder)v.getTag()).isFile != selectedNode.isFile()) {
if(selectedNode.isFile())
v = mInflater.inflate(R.layout.audio_browser_playlist_child, parent, false);
else
v = mInflater.inflate(R.layout.audio_browser_playlist, parent, false);
holder = new DirectoryViewHolder();
holder.layout = (View) v.findViewById(R.id.layout_item);
holder.title = (TextView) v.findViewById(R.id.title);
holder.text = (TextView) v.findViewById(R.id.text);
holder.isFile = selectedNode.isFile();
v.setTag(holder);
} else
holder = (DirectoryViewHolder) v.getTag();
Util.setItemBackground(holder.layout, position);
holder.title.setText(selectedNode.name);
String holderText;
if(selectedNode.name == "..")
holderText = mContext.getString(R.string.parent_folder);
else if(!selectedNode.isFile()) {
int folderCount = selectedNode.subfolderCount();
int songCount = selectedNode.subfilesCount();
holderText = "";
if(folderCount > 0)
holderText += mContext.getResources().getQuantityString(
R.plurals.subfolders, folderCount, folderCount
);
if(folderCount > 0 && songCount > 0)
holderText += ", ";
if(songCount > 0)
holderText += mContext.getResources().getQuantityString(
R.plurals.songs, songCount, songCount
);
}
else holderText = "Media file here";
holder.text.setText(holderText);
return v;
}
public Boolean browse(int position) {
AudioDirectoryAdapter.Node selectedNode = mCurrentNode.children.get(position);
if(selectedNode.isFile()) return false;
try {
this.mCurrentDir = new URI(this.mCurrentDir + "/" + selectedNode.name).normalize().getPath();
this.mCurrentDir = Util.stripTrailingSlash(this.mCurrentDir);
} catch (URISyntaxException e) {
/* blah blah blah blah blah */
}
this.mCurrentNode.children.clear();
this.mCurrentNode = new AudioDirectoryAdapter.Node(mCurrentDir);
this.populateNode(mCurrentNode, mCurrentDir);
this.notifyDataSetChanged();
return true;
}
public String getMediaLocation(int position) {
return LibVLC.getExistingInstance().nativeToURI(
this.mCurrentDir + "/" + mCurrentNode.children.get(position).name
);
}
public void clear() {
this.mRootNode.children.clear();
this.mRootNode = new AudioDirectoryAdapter.Node(mRootDir);
this.populateNode(mRootNode, mRootDir);
this.mCurrentDir = mRootDir;
this.mCurrentNode = mRootNode;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment