Customizable library tab order

This commit is contained in:
Christopher Eby 2012-03-02 02:15:05 -06:00
parent 6c6754cff0
commit dabacbbd49
19 changed files with 864 additions and 170 deletions

View File

@ -125,6 +125,9 @@ THE SOFTWARE.
<activity
android:name="PreferencesActivity"
android:theme="@style/BackActionBar" />
<activity
android:name="TabOrderActivity"
android:theme="@style/BackActionBar" />
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIH6Xcxa4hn6sHN1m4jMpi4MFFFMP5sv3XhFuWeA" />

View File

@ -22,9 +22,8 @@ THE SOFTWARE.
-->
<org.kreed.vanilla.DragListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/playlist"
android:id="@+id/list"
android:fastScrollEnabled="true"
android:divider="@null"
android:dividerHeight="1dip"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

View File

@ -23,7 +23,6 @@ THE SOFTWARE.
<org.kreed.vanilla.DragTextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="3dip"
android:minHeight="44dip"
android:singleLine="true"
android:textColor="#ffff"
android:gravity="left|center_vertical" />

57
res/layout/tab_order.xml Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:divider="?android:attr/dividerHorizontal"
android:showDividers="middle"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<org.kreed.vanilla.DragListView
android:id="@+id/list"
android:divider="@null"
android:layout_height="0px"
android:layout_width="fill_parent"
android:layout_weight="1"
android:choiceMode="multipleChoice" />
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
style="?android:attr/buttonBarButtonStyle"
android:id="@+id/restore_default"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/restore_default" />
<Button
style="?android:attr/buttonBarButtonStyle"
android:id="@+id/done"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/done" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<org.kreed.vanilla.DragTextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="3dip"
android:singleLine="true"
android:textColor="#ffff"
android:drawableLeft="@drawable/grabber"
android:drawableRight="?android:attr/listChoiceIndicatorMultiple"
android:gravity="left|center_vertical" />

View File

@ -185,6 +185,10 @@ THE SOFTWARE.
<string name="scan_in_progress">Scan in progress…</string>
<string name="finished_scanning">Finished scanning. Tap to scan again.</string>
<string name="tabs">Tab Order</string>
<string name="customize_tab_order">Adjust the order and visibility of library tabs</string>
<string name="restore_default">Restore default</string>
<!-- The following are for the list preferences -->
<string name="last_used_action">Last used action</string>

View File

@ -113,6 +113,7 @@ THE SOFTWARE.
android:title="@string/controls_in_selector_title"
android:summary="@string/controls_in_selector_summary"
android:defaultValue="false" />
<org.kreed.vanilla.TabOrderPreference />
<org.kreed.vanilla.ListPreferenceSummary
android:key="default_action_int"
android:title="@string/default_action_title"

View File

@ -65,25 +65,13 @@ public class CompatHoneycomb {
};
ActionBar ab = activity.getActionBar();
ab.addTab(ab.newTab()
.setText(R.string.artists)
.setTabListener(listener));
ab.addTab(ab.newTab()
.setText(R.string.albums)
.setTabListener(listener));
ab.addTab(ab.newTab()
.setText(R.string.songs)
.setTabListener(listener));
ab.addTab(ab.newTab()
.setText(R.string.playlists)
.setTabListener(listener));
ab.addTab(ab.newTab()
.setText(R.string.genres)
.setTabListener(listener));
ab.addTab(ab.newTab()
.setText(R.string.files)
.setTabListener(listener));
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ab.removeAllTabs();
int[] order = activity.mPagerAdapter.mTabOrder;
int[] titles = LibraryPagerAdapter.TITLES;
for (int i = 0, n = activity.mPagerAdapter.getCount(); i != n; ++i) {
ab.addTab(ab.newTab().setText(titles[order[i]]).setTabListener(listener));
}
}
/**
@ -120,7 +108,9 @@ public class CompatHoneycomb {
public static void selectTab(Activity activity, int position)
{
ActionBar ab = activity.getActionBar();
ab.selectTab(ab.getTabAt(position));
if (position < ab.getTabCount()) {
ab.selectTab(ab.getTabAt(position));
}
}
/**

View File

@ -28,6 +28,7 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
/**
@ -36,7 +37,7 @@ import android.widget.ListView;
* This implementation has some restrictions:
* Footers are unsupported
* All non-header views must have the same height
* The adapter must be a PlaylistAdapter
* The adapter must implement DragAdapter
*
* Dragging disabled by default. Enable it with
* {@link DragListView#setEditable(boolean)}.
@ -45,6 +46,20 @@ import android.widget.ListView;
* HACKY. : /
*/
public class DragListView extends ListView implements Handler.Callback {
/**
* Adapter that implements move and remove operations.
*/
public interface DragAdapter extends ListAdapter {
/**
* Remove the element at position from and insert it at position to.
*/
public void move(int from, int to);
/**
* Remove the element at the given position.
*/
public void remove(int position);
}
/**
* Sent to scroll the list up or down when the dragged view is near the
* top or bottom of the list.
@ -53,11 +68,11 @@ public class DragListView extends ListView implements Handler.Callback {
/**
* Height of each row in dip.
*/
private static final int ROW_HEIGHT = 44;
public static final int ROW_HEIGHT = 44;
/**
* Padding for each row in dip.
*/
private static final int PADDING = 3;
public static final int PADDING = 3;
/**
* Background color of row while it is being dragged.
*/
@ -73,7 +88,7 @@ public class DragListView extends ListView implements Handler.Callback {
/**
* The adapter that will be called to move/remove rows.
*/
private PlaylistAdapter mAdapter;
private DragAdapter mAdapter;
/**
* True to allow dragging; false otherwise.
*/
@ -135,13 +150,13 @@ public class DragListView extends ListView implements Handler.Callback {
/**
* This should be called instead of
* {@link ListView#setAdapter(android.widget.ListAdapter)}.
* DragListView requires a PlaylistAdapter to handle move/remove callbacks
* DragListView requires a DragAdapter to handle move/remove callbacks
* from dragging.
*
* @param adapter The adapter to use. Will be passed to
* {@link ListView#setAdapter(android.widget.ListAdapter)}.
*/
public void setAdapter(PlaylistAdapter adapter)
public void setAdapter(DragAdapter adapter)
{
super.setAdapter(adapter);
// Keep track of adapter here since getAdapter() will return a wrapper
@ -217,13 +232,13 @@ public class DragListView extends ListView implements Handler.Callback {
*/
private void unExpandViews()
{
int padding = mPadding;
for (int i = 0, count = getChildCount(); i != count; ++i) {
View view = getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = 0;
view.setLayoutParams(params);
view.setVisibility(View.VISIBLE);
int padding = mPadding;
view.setPadding(padding, padding, padding, padding);
}
}

View File

@ -25,7 +25,9 @@ package org.kreed.vanilla;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.Checkable;
import android.widget.TextView;
/**
@ -33,16 +35,42 @@ import android.widget.TextView;
*
* We draw the divider here rather than with ListView.setDivider() so we don't
* have duplicate dividers when hiding a row for a drag.
*
* This also implements the Checkable interface to provide checking for
* TabOrderActivity. CheckedTextView also provides this, but unfortunately its
* check-mark ignores padding so it can't be used with DragListView's expansion
* code.
*/
public class DragTextView extends TextView {
public class DragTextView extends TextView implements Checkable {
private final Paint mPaint;
private boolean mChecked;
/**
* Check mark drawable to update with checked state. This drawable is set
* as the TextView's right compound drawable, so TextView will handle the
* drawing.
*/
private final Drawable mCheckMarkDrawable;
/**
* The preferred height of the view in pixels. Set to DragListView.ROW_HEIGHT.
*/
private final int mHeight;
private static final int[] CHECKED_STATE_SET = {
android.R.attr.state_checked
};
public DragTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
Paint paint = new Paint();
paint.setColor(0xff444444);
mPaint = paint;
Drawable[] drawables = getCompoundDrawables();
mCheckMarkDrawable = drawables[2];
float density = context.getResources().getDisplayMetrics().density;
mHeight = (int)(DragListView.ROW_HEIGHT * density);
}
@Override
@ -52,10 +80,57 @@ public class DragTextView extends TextView {
if (height <= 1)
return;
super.onDraw(canvas);
if (getDrawingCacheBackgroundColor() != DragListView.DRAG_COLOR) {
if (getDrawingCacheBackgroundColor() != DragListView.DRAG_COLOR && getPaddingBottom() < getHeight() / 2) {
// only draw divider when not dragging
float h = height - 1;
canvas.drawLine(0, h, getWidth(), h, mPaint);
}
}
@Override
public boolean isChecked()
{
return mChecked;
}
@Override
public void setChecked(boolean checked)
{
mChecked = checked;
refreshDrawableState();
}
@Override
public void toggle()
{
setChecked(!mChecked);
}
@Override
protected int[] onCreateDrawableState(int extraSpace)
{
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (mChecked) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
@Override
protected void drawableStateChanged()
{
super.drawableStateChanged();
if (mCheckMarkDrawable != null) {
int[] myDrawableState = getDrawableState();
mCheckMarkDrawable.setState(myDrawableState);
invalidate();
}
}
@Override
public void onMeasure(int widthSpec, int heightSpec)
{
setMeasuredDimension(MeasureSpec.getSize(widthSpec), resolveSize(mHeight, heightSpec));
}
}

View File

@ -110,6 +110,7 @@ public class LibraryActivity
SongTimeline.MODE_PLAY_ID_FIRST, SongTimeline.MODE_ENQUEUE_ID_FIRST };
public ViewPager mViewPager;
private TabPageIndicator mTabs;
private View mSearchBox;
private boolean mSearchBoxVisible;
@ -183,7 +184,6 @@ public class LibraryActivity
mViewPager = pager;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
CompatHoneycomb.addActionBarTabs(this);
pager.setOnPageChangeListener(pagerAdapter);
View controls = getLayoutInflater().inflate(R.layout.actionbar_controls, null);
@ -196,6 +196,7 @@ public class LibraryActivity
TabPageIndicator tabs = new TabPageIndicator(this);
tabs.setViewPager(pager);
tabs.setOnPageChangeListener(pagerAdapter);
mTabs = tabs;
LinearLayout content = (LinearLayout)findViewById(R.id.content);
content.addView(tabs, 0, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
@ -239,11 +240,17 @@ public class LibraryActivity
super.onStart();
SharedPreferences settings = PlaybackService.getSettings(this);
if (settings.getBoolean("controls_in_selector", false) != (mControls != null)) {
finish();
startActivity(new Intent(this, LibraryActivity.class));
}
if (mPagerAdapter.loadTabOrder()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
CompatHoneycomb.addActionBarTabs(this);
} else {
mTabs.notifyDataSetChanged();
}
}
mDefaultAction = Integer.parseInt(settings.getString("default_action_int", "0"));
mLastActedId = LibraryAdapter.INVALID_ID;
updateHeaders();
@ -273,7 +280,7 @@ public class LibraryActivity
String data = String.format("album_id=%d", albumId);
Limiter limiter = new Limiter(MediaUtils.TYPE_ALBUM, fields, data);
int tab = mPagerAdapter.setLimiter(limiter);
if (tab == mViewPager.getCurrentItem())
if (tab == -1 || tab == mViewPager.getCurrentItem())
updateLimiterViews();
else
mViewPager.setCurrentItem(tab);
@ -386,7 +393,7 @@ public class LibraryActivity
mode = modeForAction[mode];
QueryTask query = buildQueryFromIntent(intent, false, all);
PlaybackService.get(this).addSongs(mode, query, intent.getIntExtra("type", -1));
PlaybackService.get(this).addSongs(mode, query, intent.getIntExtra("type", MediaUtils.TYPE_INVALID));
mLastActedId = id;
@ -405,10 +412,10 @@ public class LibraryActivity
*/
private void expand(Intent intent)
{
int type = intent.getIntExtra("type", 1);
int type = intent.getIntExtra("type", MediaUtils.TYPE_INVALID);
long id = intent.getLongExtra("id", LibraryAdapter.INVALID_ID);
int tab = mPagerAdapter.setLimiter(mPagerAdapter.mAdapters[type - 1].buildLimiter(id));
if (tab == mViewPager.getCurrentItem())
int tab = mPagerAdapter.setLimiter(mPagerAdapter.mAdapters[type].buildLimiter(id));
if (tab == -1 || tab == mViewPager.getCurrentItem())
updateLimiterViews();
else
mViewPager.setCurrentItem(tab);
@ -455,7 +462,7 @@ public class LibraryActivity
*/
public void onItemExpanded(Intent rowData)
{
int type = rowData.getIntExtra(LibraryAdapter.DATA_TYPE, -1);
int type = rowData.getIntExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_INVALID);
if (type == MediaUtils.TYPE_PLAYLIST)
editPlaylist(rowData);
else
@ -575,7 +582,7 @@ public class LibraryActivity
*/
private QueryTask buildQueryFromIntent(Intent intent, boolean empty, boolean all)
{
int type = intent.getIntExtra("type", 1);
int type = intent.getIntExtra("type", MediaUtils.TYPE_INVALID);
String[] projection;
if (type == MediaUtils.TYPE_PLAYLIST)
@ -588,7 +595,7 @@ public class LibraryActivity
if (type == MediaUtils.TYPE_FILE) {
query = MediaUtils.buildFileQuery(intent.getStringExtra("file"), projection);
} else if (all || id == LibraryAdapter.HEADER_ID) {
query = ((MediaAdapter)mPagerAdapter.mAdapters[type - 1]).buildSongQuery(projection);
query = ((MediaAdapter)mPagerAdapter.mAdapters[type]).buildSongQuery(projection);
query.setExtra(id);
} else {
query = MediaUtils.buildQuery(type, id, projection, null);
@ -622,7 +629,7 @@ public class LibraryActivity
menu.add(0, MENU_ENQUEUE_ALL, 0, R.string.enqueue_all).setIntent(rowData);
menu.addSubMenu(0, MENU_ADD_TO_PLAYLIST, 0, R.string.add_to_playlist).getItem().setIntent(rowData);
} else {
int type = rowData.getIntExtra(LibraryAdapter.DATA_TYPE, -1);
int type = rowData.getIntExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_INVALID);
boolean isAllAdapter = type <= MediaUtils.TYPE_SONG;
menu.setHeaderTitle(rowData.getStringExtra(LibraryAdapter.DATA_TITLE));
@ -680,8 +687,8 @@ public class LibraryActivity
*/
private void delete(Intent intent)
{
int type = intent.getIntExtra("type", 1);
long id = intent.getLongExtra("id", -1);
int type = intent.getIntExtra("type", MediaUtils.TYPE_INVALID);
long id = intent.getLongExtra("id", LibraryAdapter.INVALID_ID);
String message = null;
Resources res = getResources();
@ -1005,17 +1012,18 @@ public class LibraryActivity
}
/**
* Called when a new adapter has been made visible.
* Called when a new page becomes visible.
*
* @param position The position of the new page.
* @param adapter The new visible adapter.
*/
public void onAdapterSelected(LibraryAdapter adapter)
public void onPageChanged(int position, LibraryAdapter adapter)
{
mCurrentAdapter = adapter;
mLastActedId = LibraryAdapter.INVALID_ID;
updateLimiterViews();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
CompatHoneycomb.selectTab(this, mViewPager.getCurrentItem());
CompatHoneycomb.selectTab(this, position);
}
}

View File

@ -42,6 +42,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.Arrays;
/**
* PagerAdapter that manages the library media ListViews.
@ -54,41 +55,64 @@ public class LibraryPagerAdapter
, AdapterView.OnItemClickListener
{
/**
* The number of adapters/lists (determines array sizes).
* The number of unique list types. The number of visible lists may be
* smaller.
*/
private static final int ADAPTER_COUNT = 6;
public static final int MAX_ADAPTER_COUNT = 6;
/**
* The human-readable title for each page.
* The human-readable title for each list. The positions correspond to the
* MediaUtils ids, so e.g. TITLES[MediaUtils.TYPE_SONG] = R.string.songs
*/
private static final int[] TITLES = { R.string.artists, R.string.albums, R.string.songs,
R.string.playlists, R.string.genres, R.string.files };
public static final int[] TITLES = { R.string.artists, R.string.albums, R.string.songs,
R.string.playlists, R.string.genres, R.string.files };
/**
* The ListView for each adapter, in the same order as MediaUtils.TYPE_*.
* Default tab order.
*/
private final ListView[] mLists = new ListView[ADAPTER_COUNT];
public static final int[] DEFAULT_ORDER = { MediaUtils.TYPE_ARTIST, MediaUtils.TYPE_ALBUM, MediaUtils.TYPE_SONG,
MediaUtils.TYPE_PLAYLIST, MediaUtils.TYPE_GENRE, MediaUtils.TYPE_FILE };
/**
* The user-chosen tab order.
*/
int[] mTabOrder;
/**
* The number of visible tabs.
*/
private int mTabCount;
/**
* The ListView for each adapter. Each index corresponds to that list's
* MediaUtils id.
*/
private final ListView[] mLists = new ListView[MAX_ADAPTER_COUNT];
/**
* The adapters. Each index corresponds to that adapter's MediaUtils id.
*/
public LibraryAdapter[] mAdapters = new LibraryAdapter[MAX_ADAPTER_COUNT];
/**
* Whether the adapter corresponding to each index has stale data.
*/
private final boolean[] mRequeryNeeded = new boolean[ADAPTER_COUNT];
private final boolean[] mRequeryNeeded = new boolean[MAX_ADAPTER_COUNT];
/**
* Each adapter, in the same order as MediaUtils.TYPE_*.
* The artist adapter instance, also stored at mAdapters[MediaUtils.TYPE_ARTIST].
*/
public final LibraryAdapter[] mAdapters = new LibraryAdapter[ADAPTER_COUNT];
private MediaAdapter mArtistAdapter;
/**
* The album adapter instance, also stored at mAdapters[1].
* The album adapter instance, also stored at mAdapters[MediaUtils.TYPE_ALBUM].
*/
private MediaAdapter mAlbumAdapter;
/**
* The song adapter instance, also stored at mAdapters[2].
* The song adapter instance, also stored at mAdapters[MediaUtils.TYPE_SONG].
*/
private MediaAdapter mSongAdapter;
/**
* The playlist adapter instance, also stored at mAdapters[3].
* The playlist adapter instance, also stored at mAdapters[MediaUtils.TYPE_PLAYLIST].
*/
MediaAdapter mPlaylistAdapter;
/**
* The file adapter instance, also stored at mAdapters[5].
* The genre adapter instance, also stored at mAdapters[MediaUtils.TYPE_GENRE].
*/
private MediaAdapter mGenreAdapter;
/**
* The file adapter instance, also stored at mAdapters[MediaUtils.TYPE_FILE].
*/
private FileSystemAdapter mFilesAdapter;
/**
@ -125,7 +149,7 @@ public class LibraryPagerAdapter
*/
private final Handler mUiHandler;
/**
* A Handler runing on a worker thread.
* A Handler running on a worker thread.
*/
private final Handler mWorkerHandler;
/**
@ -140,6 +164,14 @@ public class LibraryPagerAdapter
* The current filter text, or null if none.
*/
private String mFilter;
/**
* The position of the songs page, or -1 if it is hidden.
*/
private int mSongsPosition = -1;
/**
* The position of the albums page, or -1 if it is hidden.
*/
private int mAlbumsPosition = -1;
private final ContentObserver mPlaylistObserver = new ContentObserver(null) {
@Override
@ -167,10 +199,85 @@ public class LibraryPagerAdapter
activity.getContentResolver().registerContentObserver(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mPlaylistObserver);
}
/**
* Load the tab order from SharedPreferences.
*
* @return True if order has changed.
*/
public boolean loadTabOrder()
{
String in = PlaybackService.getSettings(mActivity).getString("tab_order", null);
int[] order;
int count;
if (in == null || in.length() != MAX_ADAPTER_COUNT) {
order = DEFAULT_ORDER;
count = MAX_ADAPTER_COUNT;
} else {
char[] chars = in.toCharArray();
order = new int[MAX_ADAPTER_COUNT];
count = 0;
for (int i = 0; i != MAX_ADAPTER_COUNT; ++i) {
char v = chars[i];
if (v >= 128) {
v -= 128;
if (v >= MediaUtils.TYPE_COUNT) {
// invalid media type; use default order
order = DEFAULT_ORDER;
count = MAX_ADAPTER_COUNT;
break;
}
order[count++] = v;
}
}
}
if (count != mTabCount || !Arrays.equals(order, mTabOrder)) {
mTabOrder = order;
mTabCount = count;
notifyDataSetChanged();
computeExpansions();
return true;
}
return false;
}
/**
* Determines whether adapters should be expandable from the visibility of
* the adapters each expands to. Also updates mSongsPosition/mAlbumsPositions.
*/
public void computeExpansions()
{
int[] order = mTabOrder;
int songsPosition = -1;
int albumsPosition = -1;
for (int i = mTabCount; --i != -1; ) {
switch (order[i]) {
case MediaUtils.TYPE_ALBUM:
albumsPosition = i;
break;
case MediaUtils.TYPE_SONG:
songsPosition = i;
break;
}
}
if (mArtistAdapter != null)
mArtistAdapter.setExpandable(songsPosition != -1 || albumsPosition != -1);
if (mAlbumAdapter != null)
mAlbumAdapter.setExpandable(songsPosition != -1);
if (mGenreAdapter != null)
mGenreAdapter.setExpandable(songsPosition != -1);
mSongsPosition = songsPosition;
mAlbumsPosition = albumsPosition;
}
@Override
public Object instantiateItem(ViewGroup container, int position)
{
ListView view = mLists[position];
int type = mTabOrder[position];
ListView view = mLists[type];
if (view == null) {
LibraryActivity activity = mActivity;
@ -178,75 +285,91 @@ public class LibraryPagerAdapter
LibraryAdapter adapter;
TextView header = null;
switch (position) {
case 0:
adapter = new MediaAdapter(activity, MediaUtils.TYPE_ARTIST, null);
switch (type) {
case MediaUtils.TYPE_ARTIST:
adapter = mArtistAdapter = new MediaAdapter(activity, MediaUtils.TYPE_ARTIST, null);
mArtistAdapter.setExpandable(mSongsPosition != -1 || mAlbumsPosition != -1);
mArtistHeader = header = (TextView)inflater.inflate(R.layout.library_row, null);
break;
case 1:
case MediaUtils.TYPE_ALBUM:
adapter = mAlbumAdapter = new MediaAdapter(activity, MediaUtils.TYPE_ALBUM, mPendingAlbumLimiter);
mAlbumAdapter.setExpandable(mSongsPosition != -1);
mPendingAlbumLimiter = null;
mAlbumHeader = header = (TextView)inflater.inflate(R.layout.library_row, null);
break;
case 2:
case MediaUtils.TYPE_SONG:
adapter = mSongAdapter = new MediaAdapter(activity, MediaUtils.TYPE_SONG, mPendingSongLimiter);
mPendingSongLimiter = null;
mSongHeader = header = (TextView)inflater.inflate(R.layout.library_row, null);
break;
case 3:
case MediaUtils.TYPE_PLAYLIST:
adapter = mPlaylistAdapter = new MediaAdapter(activity, MediaUtils.TYPE_PLAYLIST, null);
break;
case 4:
adapter = new MediaAdapter(activity, MediaUtils.TYPE_GENRE, null);
case MediaUtils.TYPE_GENRE:
adapter = mGenreAdapter = new MediaAdapter(activity, MediaUtils.TYPE_GENRE, null);
mGenreAdapter.setExpandable(mSongsPosition != -1);
break;
case 5:
case MediaUtils.TYPE_FILE:
adapter = mFilesAdapter = new FileSystemAdapter(activity, mPendingFileLimiter);
mPendingFileLimiter = null;
break;
default:
throw new IllegalArgumentException("Invalid position: " + position);
throw new IllegalArgumentException("Invalid media type: " + type);
}
view = (ListView)inflater.inflate(R.layout.listview, null);
view.setOnCreateContextMenuListener(this);
view.setOnItemClickListener(this);
view.setTag(type);
if (header != null) {
header.setText(mHeaderText);
header.setTag(position + 1);
header.setTag(type);
view.addHeaderView(header);
}
view.setAdapter(adapter);
if (position != 5)
if (type != MediaUtils.TYPE_FILE)
loadSortOrder((MediaAdapter)adapter);
enableFastScroll(view);
adapter.setFilter(mFilter);
mAdapters[position] = adapter;
mLists[position] = view;
mRequeryNeeded[position] = true;
mAdapters[type] = adapter;
mLists[type] = view;
mRequeryNeeded[type] = true;
}
requeryIfNeeded(position);
requeryIfNeeded(type);
container.addView(view);
return view;
}
@Override
public int getItemPosition(Object item)
{
int type = (Integer)((ListView)item).getTag();
int[] order = mTabOrder;
for (int i = mTabCount; --i != -1; ) {
if (order[i] == type)
return i;
}
return POSITION_NONE;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object)
{
container.removeView(mLists[position]);
container.removeView((View)object);
}
@Override
public CharSequence getPageTitle(int position)
{
return mActivity.getResources().getText(TITLES[position]);
return mActivity.getResources().getText(TITLES[mTabOrder[position]]);
}
@Override
public int getCount()
{
return ADAPTER_COUNT;
return mTabCount;
}
@Override
@ -258,12 +381,13 @@ public class LibraryPagerAdapter
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object)
{
LibraryAdapter adapter = mAdapters[position];
if (adapter != mCurrentAdapter) {
requeryIfNeeded(position);
int type = mTabOrder[position];
LibraryAdapter adapter = mAdapters[type];
if (position != mCurrentPage || adapter != mCurrentAdapter) {
requeryIfNeeded(type);
mCurrentAdapter = adapter;
mCurrentPage = position;
mActivity.onAdapterSelected(adapter);
mActivity.onPageChanged(position, adapter);
}
}
@ -288,9 +412,9 @@ public class LibraryPagerAdapter
if (mFilesAdapter != null)
out.putSerializable("limiter_files", mFilesAdapter.getLimiter());
int[] savedPositions = new int[ADAPTER_COUNT];
int[] savedPositions = new int[MAX_ADAPTER_COUNT];
ListView[] lists = mLists;
for (int i = ADAPTER_COUNT; --i != -1; ) {
for (int i = MAX_ADAPTER_COUNT; --i != -1; ) {
if (lists[i] != null) {
savedPositions[i] = lists[i].getFirstVisiblePosition();
}
@ -350,7 +474,7 @@ public class LibraryPagerAdapter
* Update the adapters with the given limiter.
*
* @param limiter The limiter to set.
* @return The tab appropriate for expanding a row.
* @return The tab type that should be switched to to expand the row.
*/
public int setLimiter(Limiter limiter)
{
@ -365,7 +489,7 @@ public class LibraryPagerAdapter
loadSortOrder(mSongAdapter);
requestRequery(mSongAdapter);
}
tab = 2;
tab = mSongsPosition;
break;
case MediaUtils.TYPE_ARTIST:
if (mAlbumAdapter == null) {
@ -382,7 +506,9 @@ public class LibraryPagerAdapter
loadSortOrder(mSongAdapter);
requestRequery(mSongAdapter);
}
tab = 1;
tab = mAlbumsPosition;
if (tab == -1)
tab = mSongsPosition;
break;
case MediaUtils.TYPE_GENRE:
if (mAlbumAdapter == null) {
@ -399,7 +525,7 @@ public class LibraryPagerAdapter
loadSortOrder(mSongAdapter);
requestRequery(mSongAdapter);
}
tab = 2;
tab = mSongsPosition;
break;
case MediaUtils.TYPE_FILE:
if (mFilesAdapter == null) {
@ -408,7 +534,7 @@ public class LibraryPagerAdapter
mFilesAdapter.setLimiter(limiter);
requestRequery(mFilesAdapter);
}
tab = 5;
tab = -1;
break;
default:
throw new IllegalArgumentException("Unsupported limiter type: " + limiter.type);
@ -461,7 +587,7 @@ public class LibraryPagerAdapter
switch (message.what) {
case MSG_RUN_QUERY: {
LibraryAdapter adapter = (LibraryAdapter)message.obj;
int index = adapter.getMediaType() - 1;
int index = adapter.getMediaType();
Handler handler = mUiHandler;
handler.sendMessage(handler.obtainMessage(MSG_COMMIT_QUERY, index, 0, adapter.query()));
break;
@ -508,7 +634,7 @@ public class LibraryPagerAdapter
if (adapter == mCurrentAdapter) {
postRunQuery(adapter);
} else {
mRequeryNeeded[adapter.getMediaType() - 1] = true;
mRequeryNeeded[adapter.getMediaType()] = true;
// Clear the data for non-visible adapters (so we don't show the old
// data briefly when we later switch to that adapter)
adapter.clear();
@ -534,21 +660,21 @@ public class LibraryPagerAdapter
*/
private void postRunQuery(LibraryAdapter adapter)
{
mRequeryNeeded[adapter.getMediaType() - 1] = false;
mRequeryNeeded[adapter.getMediaType()] = false;
Handler handler = mWorkerHandler;
handler.removeMessages(MSG_RUN_QUERY, adapter);
handler.sendMessage(handler.obtainMessage(MSG_RUN_QUERY, adapter));
}
/**
* Requery the adapter at the given position if it exists and needs a requery.
* Requery the adapter of the given type if it exists and needs a requery.
*
* @param position An index in mAdapters.
* @param type One of MediaUtils.TYPE_*
*/
private void requeryIfNeeded(int position)
private void requeryIfNeeded(int type)
{
LibraryAdapter adapter = mAdapters[position];
if (adapter != null && mRequeryNeeded[position]) {
LibraryAdapter adapter = mAdapters[type];
if (adapter != null && mRequeryNeeded[type]) {
postRunQuery(adapter);
}
}
@ -598,7 +724,7 @@ public class LibraryPagerAdapter
// Force a new FastScroller to be created so the scroll sections
// are updated.
ListView view = mLists[mCurrentPage];
ListView view = mLists[mTabOrder[mCurrentPage]];
view.setFastScrollEnabled(false);
enableFastScroll(view);

View File

@ -36,8 +36,9 @@ import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import android.widget.TextView;
import java.util.regex.Pattern;
@ -54,7 +55,7 @@ import java.util.regex.Pattern;
* See getLimiter and setLimiter for details.
*/
public class MediaAdapter
extends CursorAdapter
extends BaseAdapter
implements SectionIndexer
, LibraryAdapter
, View.OnClickListener
@ -69,6 +70,10 @@ public class MediaAdapter
* A LayoutInflater to use.
*/
private final LayoutInflater mInflater;
/**
* The current data.
*/
private Cursor mCursor;
/**
* The type of media represented by this adapter. Must be one of the
* MediaUtils.FIELD_* constants. Determines which content provider to query for
@ -132,9 +137,9 @@ public class MediaAdapter
*/
private int mSortMode;
/**
* The layout used for each row.
* If true, show the expander button on each row.
*/
private int mLayout;
private boolean mExpandable;
/**
* Construct a MediaAdapter representing the given <code>type</code> of
@ -148,8 +153,6 @@ public class MediaAdapter
*/
public MediaAdapter(LibraryActivity activity, int type, Limiter limiter)
{
super(activity, null, false);
mActivity = activity;
mType = type;
mLimiter = limiter;
@ -164,7 +167,6 @@ public class MediaAdapter
mSongSort = MediaUtils.DEFAULT_SORT;
mSortEntries = new int[] { R.string.name, R.string.number_of_tracks };
mSortValues = new String[] { "artist_key %1$s", "number_of_tracks %1$s,artist_key %1$s" };
mLayout = R.layout.library_row_expandable;
break;
case MediaUtils.TYPE_ALBUM:
mStore = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
@ -174,7 +176,6 @@ public class MediaAdapter
mSongSort = "album_key,track";
mSortEntries = new int[] { R.string.name, R.string.artist_album, R.string.year, R.string.number_of_tracks };
mSortValues = new String[] { "album_key %1$s", "artist_key %1$s,album_key %1$s", "minyear %1$s,album_key %1$s", "numsongs %1$s,album_key %1$s" };
mLayout = R.layout.library_row_expandable;
break;
case MediaUtils.TYPE_SONG:
mStore = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
@ -182,7 +183,6 @@ public class MediaAdapter
mFieldKeys = new String[] { MediaStore.Audio.Media.ARTIST_KEY, MediaStore.Audio.Media.ALBUM_KEY, MediaStore.Audio.Media.TITLE_KEY };
mSortEntries = new int[] { R.string.name, R.string.artist_album_track, R.string.artist_album_title, R.string.artist_year, R.string.year };
mSortValues = new String[] { "title_key %1$s", "artist_key %1$s,album_key %1$s,track %1$s", "artist_key %1$s,album_key %1$s,title_key %1$s", "artist_key %1$s,year %1$s,track %1$s", "year %1$s,title_key %1$s" };
mLayout = R.layout.library_row;
break;
case MediaUtils.TYPE_PLAYLIST:
mStore = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
@ -190,7 +190,7 @@ public class MediaAdapter
mFieldKeys = null;
mSortEntries = new int[] { R.string.name, R.string.date_added };
mSortValues = new String[] { "name %1$s", "date_added %1$s" };
mLayout = R.layout.library_row_expandable;
mExpandable = true;
break;
case MediaUtils.TYPE_GENRE:
mStore = MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI;
@ -198,7 +198,6 @@ public class MediaAdapter
mFieldKeys = null;
mSortEntries = new int[] { R.string.name };
mSortValues = new String[] { "name %1$s" };
mLayout = R.layout.library_row_expandable;
break;
default:
throw new IllegalArgumentException("Invalid value for type: " + type);
@ -210,6 +209,20 @@ public class MediaAdapter
mProjection = new String[] { BaseColumns._ID, mFields[mFields.length - 1], mFields[0] };
}
/**
* Set whether or not the expander button should be shown in each row.
* Defaults to true for playlist adapter and false for all others.
*
* @param expandable True to show expander, false to hide.
*/
public void setExpandable(boolean expandable)
{
if (expandable != mExpandable) {
mExpandable = expandable;
notifyDataSetChanged();
}
}
@Override
public void setFilter(String filter)
{
@ -359,7 +372,7 @@ public class MediaAdapter
String[] fields;
Object data;
Cursor cursor = getCursor();
Cursor cursor = mCursor;
if (cursor == null)
return null;
for (int i = 0, count = cursor.getCount(); i != count; ++i) {
@ -388,11 +401,24 @@ public class MediaAdapter
return new Limiter(mType, fields, data);
}
@Override
/**
* Set a new cursor for this adapter. The old cursor will be closed.
*
* @param cursor The new cursor.
*/
public void changeCursor(Cursor cursor)
{
super.changeCursor(cursor);
Cursor old = mCursor;
mCursor = cursor;
if (cursor == null) {
notifyDataSetInvalidated();
} else {
notifyDataSetChanged();
}
mIndexer.setCursor(cursor);
if (old != null) {
old.close();
}
}
@Override
@ -432,13 +458,36 @@ public class MediaAdapter
public ImageView arrow;
}
/**
* Update the values in the given view.
*/
@Override
public void bindView(View view, Context context, Cursor cursor)
public View getView(int position, View view, ViewGroup parent)
{
ViewHolder holder = (ViewHolder)view.getTag();
ViewHolder holder;
if (view == null || mExpandable != view instanceof LinearLayout) {
// We must create a new view if we're not given a recycle view or
// if the recycle view has the wrong layout.
int layout = mExpandable ? R.layout.library_row_expandable : R.layout.library_row;
view = mInflater.inflate(layout, null);
holder = new ViewHolder();
view.setTag(holder);
if (mExpandable) {
holder.text = (TextView)view.findViewById(R.id.text);
holder.arrow = (ImageView)view.findViewById(R.id.arrow);
holder.arrow.setOnClickListener(this);
} else {
holder.text = (TextView)view;
view.setLongClickable(true);
}
holder.text.setOnClickListener(this);
} else {
holder = (ViewHolder)view.getTag();
}
Cursor cursor = mCursor;
cursor.moveToPosition(position);
holder.id = cursor.getLong(0);
if (mFields.length > 1) {
String line1 = cursor.getString(1);
@ -454,29 +503,7 @@ public class MediaAdapter
holder.text.setText(title);
holder.title = title;
}
}
/**
* Generate a new view.
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
int layout = mLayout;
View view = mInflater.inflate(layout, null);
ViewHolder holder = new ViewHolder();
view.setTag(holder);
if (layout == R.layout.library_row_expandable) {
holder.text = (TextView)view.findViewById(R.id.text);
holder.arrow = (ImageView)view.findViewById(R.id.arrow);
holder.arrow.setOnClickListener(this);
} else {
holder.text = (TextView)view;
view.setLongClickable(true);
}
holder.text.setOnClickListener(this);
return view;
}
@ -546,7 +573,7 @@ public class MediaAdapter
intent.putExtra(LibraryAdapter.DATA_TYPE, mType);
intent.putExtra(LibraryAdapter.DATA_ID, holder.id);
intent.putExtra(LibraryAdapter.DATA_TITLE, holder.title);
intent.putExtra(LibraryAdapter.DATA_EXPANDABLE, mType != MediaUtils.TYPE_SONG);
intent.putExtra(LibraryAdapter.DATA_EXPANDABLE, mExpandable);
return intent;
}
@ -554,7 +581,7 @@ public class MediaAdapter
public void onClick(View view)
{
int id = view.getId();
if (mLayout == R.layout.library_row_expandable)
if (mExpandable)
view = (View)view.getParent();
Intent intent = createData(view);
if (id == R.id.arrow) {
@ -563,4 +590,35 @@ public class MediaAdapter
mActivity.onItemClicked(intent);
}
}
@Override
public int getCount()
{
Cursor cursor = mCursor;
if (cursor == null)
return 0;
return cursor.getCount();
}
@Override
public Object getItem(int position)
{
return null;
}
@Override
public long getItemId(int position)
{
Cursor cursor = mCursor;
if (cursor == null)
return 0;
cursor.moveToPosition(position);
return cursor.getLong(0);
}
@Override
public boolean hasStableIds()
{
return true;
}
}

View File

@ -42,32 +42,36 @@ public class MediaUtils {
/**
* A special invalid media type.
*/
public static final int TYPE_INVALID = 0;
public static final int TYPE_INVALID = -1;
/**
* Type indicating an id represents an artist.
*/
public static final int TYPE_ARTIST = 1;
public static final int TYPE_ARTIST = 0;
/**
* Type indicating an id represents an album.
*/
public static final int TYPE_ALBUM = 2;
public static final int TYPE_ALBUM = 1;
/**
* Type indicating an id represents a song.
*/
public static final int TYPE_SONG = 3;
public static final int TYPE_SONG = 2;
/**
* Type indicating an id represents a playlist.
*/
public static final int TYPE_PLAYLIST = 4;
public static final int TYPE_PLAYLIST = 3;
/**
* Type indicating ids represent genres.
*/
public static final int TYPE_GENRE = 5;
public static final int TYPE_GENRE = 4;
/**
* Special type for files and folders. Most methods do not accept this type
* since files have no MediaStore id and require special handling.
*/
public static final int TYPE_FILE = 6;
public static final int TYPE_FILE = 5;
/**
* The number of different valid media types.
*/
public static final int TYPE_COUNT = 6;
/**
* The default sort order for media queries. First artist, then album, then

View File

@ -97,7 +97,7 @@ public class PlaylistActivity extends Activity
setContentView(R.layout.playlist_activity);
DragListView view = (DragListView)findViewById(R.id.playlist);
DragListView view = (DragListView)findViewById(R.id.list);
view.setOnItemClickListener(this);
view.setOnCreateContextMenuListener(this);
mListView = view;
@ -204,11 +204,12 @@ public class PlaylistActivity extends Activity
{
int itemId = item.getItemId();
Intent intent = item.getIntent();
int pos = intent.getIntExtra("position", -1);
if (itemId == MENU_REMOVE) {
mAdapter.remove(intent.getLongExtra("id", -1));
mAdapter.remove(pos);
} else {
performAction(itemId, intent.getIntExtra("position", -1), intent.getLongExtra("audioId", -1));
performAction(itemId, pos, intent.getLongExtra("audioId", -1));
}
return true;

View File

@ -42,7 +42,7 @@ import android.widget.TextView;
/**
* CursorAdapter backed by MediaStore playlists.
*/
public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
public class PlaylistAdapter extends CursorAdapter implements Handler.Callback, DragListView.DragAdapter {
private static final String[] PROJECTION = new String[] {
MediaStore.Audio.Playlists.Members._ID,
MediaStore.Audio.Playlists.Members.TITLE,
@ -162,12 +162,7 @@ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
return query.runQuery(resolver);
}
/**
* Move a song to a new position.
*
* @param from The old position.
* @param to The new position.
*/
@Override
public void move(int from, int to)
{
if (from == to)
@ -221,16 +216,12 @@ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
changeCursor(runQuery(resolver));
}
/**
* Remove the song with the given id.
*
* @param id The MediaStore id of the row to remove.
*/
public void remove(long id)
@Override
public void remove(int position)
{
ContentResolver resolver = mContext.getContentResolver();
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", mPlaylistId);
resolver.delete(ContentUris.withAppendedId(uri, id), null, null);
resolver.delete(ContentUris.withAppendedId(uri, getItemId(position)), null, null);
changeCursor(runQuery(resolver));
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kreed.vanilla;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
/**
* The preferences activity in which one can change application preferences.
*/
public class TabOrderActivity extends Activity implements View.OnClickListener, OnItemClickListener {
private TabOrderAdapter mAdapter;
private DragListView mList;
/**
* Initialize the activity, loading the preference specifications.
*/
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setTitle(R.string.tabs);
setContentView(R.layout.tab_order);
mAdapter = new TabOrderAdapter(this);
DragListView list = (DragListView)findViewById(R.id.list);
list.setAdapter(mAdapter);
list.setEditable(true);
list.setOnItemClickListener(this);
mList = list;
load();
findViewById(R.id.done).setOnClickListener(this);
findViewById(R.id.restore_default).setOnClickListener(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
public void onClick(View view)
{
switch (view.getId()) {
case R.id.done:
finish();
break;
case R.id.restore_default:
restoreDefault();
break;
}
}
/**
* Restore the default tab order and visibility.
*/
public void restoreDefault()
{
android.util.Log.i("VanillaMusic", "restore default");
mAdapter.setTabIds(LibraryPagerAdapter.DEFAULT_ORDER.clone());
DragListView list = mList;
for (int i = 0; i != LibraryPagerAdapter.MAX_ADAPTER_COUNT; ++i) {
list.setItemChecked(i, true);
}
save();
}
/**
* Save tab order and visibility to SharedPreferences as a string.
*/
public void save()
{
int[] ids = mAdapter.getTabIds();
DragListView list = mList;
char[] out = new char[LibraryPagerAdapter.MAX_ADAPTER_COUNT];
for (int i = 0; i != LibraryPagerAdapter.MAX_ADAPTER_COUNT; ++i) {
out[i] = (char)(list.isItemChecked(i) ? 128 + ids[i] : 127 - ids[i]);
}
SharedPreferences.Editor editor = PlaybackService.getSettings(this).edit();
editor.putString("tab_order", new String(out));
editor.commit();
}
/**
* Load tab order settings from SharedPreferences and apply it to the
* activity.
*/
public void load()
{
String in = PlaybackService.getSettings(this).getString("tab_order", null);
if (in != null && in.length() == LibraryPagerAdapter.MAX_ADAPTER_COUNT) {
char[] chars = in.toCharArray();
int[] ids = new int[LibraryPagerAdapter.MAX_ADAPTER_COUNT];
for (int i = 0; i != LibraryPagerAdapter.MAX_ADAPTER_COUNT; ++i) {
int v = chars[i];
v = v < 128 ? -(v - 127) : v - 128;
if (v >= MediaUtils.TYPE_COUNT) {
ids = null;
break;
}
ids[i] = v;
}
if (ids != null) {
mAdapter.setTabIds(ids);
DragListView list = mList;
for (int i = 0; i != LibraryPagerAdapter.MAX_ADAPTER_COUNT; ++i) {
list.setItemChecked(i, chars[i] >= 128);
}
}
return;
}
restoreDefault();
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
{
save();
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (C) 2011 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kreed.vanilla;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/**
* CursorAdapter backed by MediaStore playlists.
*/
public class TabOrderAdapter extends BaseAdapter implements DragListView.DragAdapter {
private final TabOrderActivity mActivity;
private final LayoutInflater mInflater;
private int[] mTabIds;
/**
* Create a tab order adapter.
*
* @param activity The activity that will own this adapter. The activity
* will be notified when items have been moved.
*/
public TabOrderAdapter(TabOrderActivity activity)
{
mActivity = activity;
mInflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Set the array containing the order of tab ids.
*/
public void setTabIds(int[] ids)
{
mTabIds = ids;
notifyDataSetChanged();
}
/**
* Returns the array containing the order of tab ids. Do not modify the array.
*/
public int[] getTabIds()
{
return mTabIds;
}
@Override
public void move(int from, int to)
{
if (from == to)
return;
int[] ids = mTabIds;
int tempId = ids[from];
if (from > to) {
System.arraycopy(ids, to, ids, to + 1, from - to);
} else {
System.arraycopy(ids, from + 1, ids, from, to - from);
}
ids[to] = tempId;
notifyDataSetChanged();
mActivity.save();
}
@Override
public void remove(int position)
{
// not implemented
}
@Override
public int getCount()
{
return LibraryPagerAdapter.MAX_ADAPTER_COUNT;
}
@Override
public Object getItem(int position)
{
return null;
}
@Override
public long getItemId(int position)
{
return mTabIds[position];
}
@Override
public View getView(int position, View convert, ViewGroup parent)
{
DragTextView text;
if (convert == null) {
text = (DragTextView)mInflater.inflate(R.layout.tab_order_row, null);
} else {
text = (DragTextView)convert;
}
text.setText(LibraryPagerAdapter.TITLES[mTabIds[position]]);
return text;
}
@Override
public boolean hasStableIds()
{
return true;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kreed.vanilla;
import android.content.Context;
import android.content.Intent;
import android.preference.Preference;
import android.util.AttributeSet;
/**
* A preference that opens the tab order selector.
*/
public class TabOrderPreference extends Preference {
public TabOrderPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
setTitle(R.string.tabs);
setSummary(R.string.customize_tab_order);
}
@Override
public void onClick()
{
Context context = getContext();
context.startActivity(new Intent(context, TabOrderActivity.class));
}
}