Version 1.3
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.classpath
|
||||
.cproject
|
||||
.project
|
||||
assets/
|
||||
bin/
|
||||
gen/
|
||||
obj/
|
||||
tmp/
|
44
AndroidManifest.xml
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.annimon.playlisteditor"
|
||||
android:versionCode="4"
|
||||
android:versionName="1.3" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="5"
|
||||
android:targetSdkVersion="17" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
|
||||
<activity
|
||||
android:name=".SelectPlaylistActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".EditPlaylistActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@style/AppTheme.NoTitleBar" />
|
||||
|
||||
<activity
|
||||
android:name="com.google.ads.AdActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
ic_launcher-web.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
libs/GoogleAdMobAdsSdk-6.2.1.jar
Normal file
11
proguard-project.txt
Normal file
@ -0,0 +1,11 @@
|
||||
@E:\\SETUPS\\Disk\\Programming\\Java\\android.pro
|
||||
-obfuscationdictionary E:\\SETUPS\\Disk\\Programming\\Java\\compact.txt
|
||||
-optimizationpasses 9
|
||||
-allowaccessmodification
|
||||
-overloadaggressively
|
||||
#-keep public class android.support.**
|
||||
|
||||
# Ad Mob
|
||||
#-keep class com.google.ads.AdActivity { <init>(...); }
|
||||
#-keep class com.google.ads.AdView { <init>(...); }
|
||||
-dontwarn com.google.ads.**
|
14
project.properties
Normal file
@ -0,0 +1,14 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-17
|
9
res/anim/slide_left_in.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<translate
|
||||
android:duration="@integer/transition_duration"
|
||||
android:fromXDelta="100%p"
|
||||
android:toXDelta="0" />
|
||||
|
||||
</set>
|
9
res/anim/slide_left_out.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<translate
|
||||
android:duration="@integer/transition_duration"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="-100%p" />
|
||||
|
||||
</set>
|
9
res/anim/slide_right_in.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<translate
|
||||
android:duration="@integer/transition_duration"
|
||||
android:fromXDelta="-100%p"
|
||||
android:toXDelta="0" />
|
||||
|
||||
</set>
|
9
res/anim/slide_right_out.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<translate
|
||||
android:duration="@integer/transition_duration"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="100%p" />
|
||||
|
||||
</set>
|
BIN
res/drawable-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
res/drawable-hdpi/ic_new.png
Normal file
After Width: | Height: | Size: 204 B |
BIN
res/drawable-ldpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 582 B |
BIN
res/drawable-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 985 B |
BIN
res/drawable-mdpi/ic_new.png
Normal file
After Width: | Height: | Size: 177 B |
BIN
res/drawable-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-xhdpi/ic_new.png
Normal file
After Width: | Height: | Size: 250 B |
5
res/drawable/background.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:dither="true"
|
||||
android:src="@drawable/notes_bg_tile_white"
|
||||
android:tileMode="repeat" />
|
BIN
res/drawable/notes_bg_tile_white.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
22
res/layout/dialog_name.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialogPlaylistName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/enter_name"
|
||||
android:inputType="text"
|
||||
android:maxLength="@integer/maxPlaylistName"
|
||||
android:singleLine="true" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialogOk"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@android:string/ok" />
|
||||
|
||||
</LinearLayout>
|
27
res/layout/main.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<ListView
|
||||
android:id="@android:id/list"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.ads.AdView
|
||||
android:id="@+id/adView"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
ads:adSize="BANNER"
|
||||
ads:adUnitId="a1510ae00ecc03d"
|
||||
ads:loadAdOnCreate="true"
|
||||
ads:backgroundColor="#282828"
|
||||
ads:primaryTextColor="#FFFFFF"
|
||||
ads:secondaryTextColor="#D7D7D7" />
|
||||
|
||||
</RelativeLayout>
|
54
res/layout/playlist_editor.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="33dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sourceSpinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
<Button
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:id="@+id/saveButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="5dp"
|
||||
android:background="@null"
|
||||
android:drawableLeft="@android:drawable/ic_menu_save"
|
||||
android:text="@string/save_playlist" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:background="@drawable/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<ListView
|
||||
android:id="@+id/sourcelist"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:choiceMode="singleChoice" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/resultlist"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
13
res/menu/main.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item android:id="@+id/menu_new_playlist"
|
||||
android:showAsAction="always|withText"
|
||||
android:title="@string/new_playlist"
|
||||
android:icon="@drawable/ic_new" />
|
||||
|
||||
<item android:id="@+id/menu_restore_playlist"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/restore_playlist" />
|
||||
|
||||
</menu>
|
18
res/menu/main_context.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item android:id="@+id/menu_delete_playlist"
|
||||
android:showAsAction="ifRoom|withText"
|
||||
android:title="@string/delete"
|
||||
android:icon="@android:drawable/ic_menu_delete" />
|
||||
|
||||
<item android:id="@+id/menu_rename_playlist"
|
||||
android:showAsAction="ifRoom|withText"
|
||||
android:title="@string/rename"
|
||||
android:icon="@android:drawable/ic_menu_edit" />
|
||||
|
||||
<item android:id="@+id/menu_backup_playlist"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/backup_playlist" />
|
||||
|
||||
</menu>
|
20
res/values-ru/strings.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Playlist Editor</string>
|
||||
<string name="new_playlist">Создать плейлист</string>
|
||||
<string name="save_playlist">Сохранить плейлист</string>
|
||||
<string name="backup_playlist">Резервная копия плейлиста</string>
|
||||
<string name="restore_playlist">Восстановить плейлист</string>
|
||||
<string name="delete">Удалить</string>
|
||||
<string name="rename">Переименовать</string>
|
||||
<string name="enter_name">Введите имя</string>
|
||||
<string name="done">Готово!</string>
|
||||
<string name="all_tracks">Все треки</string>
|
||||
|
||||
<!-- Errors, messages etc -->
|
||||
<string name="error_while_restore_backup">Ошибка при восстановлении резервной копии</string>
|
||||
<string name="unable_to_store_backup">Невозможно сохранить резервную копию</string>
|
||||
<string name="empty_backup_list">Пустой список резервных копий</string>
|
||||
|
||||
</resources>
|
12
res/values-v11/styles.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Application and activity styles -->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light" />
|
||||
|
||||
<style name="AppTheme.NoTitleBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
5
res/values/integers.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="transition_duration">400</integer>
|
||||
<integer name="maxPlaylistName">50</integer>
|
||||
</resources>
|
20
res/values/strings.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Playlist Editor</string>
|
||||
<string name="new_playlist">New playlist</string>
|
||||
<string name="save_playlist">Save playlist</string>
|
||||
<string name="backup_playlist">Backup playlist</string>
|
||||
<string name="restore_playlist">Restore playlist</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="rename">Rename</string>
|
||||
<string name="enter_name">Enter name</string>
|
||||
<string name="done">Done!</string>
|
||||
<string name="all_tracks">All tracks</string>
|
||||
|
||||
<!-- Errors, messages etc -->
|
||||
<string name="error_while_restore_backup">Error while restore backup</string>
|
||||
<string name="unable_to_store_backup">Unable to store backup</string>
|
||||
<string name="empty_backup_list">Empty backup list</string>
|
||||
|
||||
</resources>
|
15
res/values/styles.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Application and activity styles -->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Light" />
|
||||
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoTitleBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
189
src/com/annimon/playlisteditor/EditPlaylistActivity.java
Normal file
@ -0,0 +1,189 @@
|
||||
package com.annimon.playlisteditor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import com.annimon.playlisteditor.data.Playlist;
|
||||
import com.annimon.playlisteditor.data.Track;
|
||||
|
||||
/**
|
||||
* Edit playlist window.
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public class EditPlaylistActivity extends Activity {
|
||||
|
||||
public static final String PLAYLIST_ID = "playlist_id";
|
||||
public static final String PLAYLIST_NAME = "playlist_name";
|
||||
|
||||
private Track bufferItem;
|
||||
private TracksAdapter sourceAdapter, resultAdapter;
|
||||
private ArrayAdapter<Playlist> srcPlaylistAdapter;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.playlist_editor);
|
||||
|
||||
initPlaylistSpinner();
|
||||
|
||||
((Button) findViewById(R.id.saveButton)).setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
savePlaylist();
|
||||
}
|
||||
});
|
||||
|
||||
bufferItem = null;
|
||||
|
||||
int playlistId = getIntent().getIntExtra(PLAYLIST_ID, -1);
|
||||
Track[] tracks = PlaylistDatabase.getTracks(this, playlistId);
|
||||
int resource = android.R.layout.simple_list_item_1;
|
||||
resultAdapter = new TracksAdapter(this, resource,
|
||||
(playlistId == -1) ? new Track[0] : tracks);
|
||||
ListView listResult = (ListView) findViewById(R.id.resultlist);
|
||||
listResult.setAdapter(resultAdapter);
|
||||
listResult.setOnItemClickListener(destItemClick);
|
||||
listResult.setOnItemLongClickListener(destLongItemClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
setSlidingTransition();
|
||||
}
|
||||
|
||||
private void initPlaylistSpinner() {
|
||||
Playlist[] playlists = PlaylistDatabase.getPlaylists(this);
|
||||
List<Playlist> list = new ArrayList<Playlist>();
|
||||
list.add(new Playlist(-1, getString(R.string.all_tracks)));
|
||||
list.addAll(Arrays.asList(playlists));
|
||||
|
||||
Spinner sourceSpinner = (Spinner) findViewById(R.id.sourceSpinner);
|
||||
srcPlaylistAdapter = new ArrayAdapter<Playlist>(this,
|
||||
android.R.layout.simple_spinner_item, list);
|
||||
srcPlaylistAdapter.setDropDownViewResource(
|
||||
android.R.layout.simple_spinner_dropdown_item);
|
||||
sourceSpinner.setAdapter(srcPlaylistAdapter);
|
||||
|
||||
sourceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> p, View v, int pos, long id) {
|
||||
int playlistId = srcPlaylistAdapter.getItem(pos).getId();
|
||||
addTracksToSourceList(playlistId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> arg0) {
|
||||
int playlistId = getIntent().getIntExtra(PLAYLIST_ID, -1);
|
||||
addTracksToSourceList(playlistId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addTracksToSourceList(int playlistId) {
|
||||
Track[] tracks = PlaylistDatabase.getTracks(this, playlistId);
|
||||
int resource = android.R.layout.simple_list_item_single_choice;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
resource = android.R.layout.simple_list_item_activated_1;
|
||||
}
|
||||
sourceAdapter = new TracksAdapter(this, resource, tracks);
|
||||
ListView listSource = (ListView) findViewById(R.id.sourcelist);
|
||||
listSource.setAdapter(sourceAdapter);
|
||||
listSource.setOnItemClickListener(srcItemClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow user to enter name of saving playlist.
|
||||
*/
|
||||
private void savePlaylist() {
|
||||
PlaylistNameDialog dialog = new PlaylistNameDialog(this) {
|
||||
|
||||
@Override
|
||||
protected void onClick(String playlistName) {
|
||||
savePlaylist(playlistName);
|
||||
// Exit window.
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
setSlidingTransition();
|
||||
}
|
||||
};
|
||||
dialog.setTitle(R.string.save_playlist);
|
||||
if (getIntent().getIntExtra(PLAYLIST_ID, -1) != -1) {
|
||||
String playlistName = getIntent().getStringExtra(PLAYLIST_NAME) + "_pe";
|
||||
dialog.setName(playlistName);
|
||||
}
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves track order into new playlist.
|
||||
*/
|
||||
private void savePlaylist(String playlistName) {
|
||||
int[] trackIds = new int[resultAdapter.getCount()];
|
||||
for (int i = 0; i < trackIds.length; i++) {
|
||||
trackIds[i] = resultAdapter.getItem(i).getAudioId();
|
||||
}
|
||||
PlaylistDatabase.createPlaylist(this, playlistName, trackIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide out layout when close screen.
|
||||
*/
|
||||
private void setSlidingTransition() {
|
||||
overridePendingTransition(R.anim.slide_right_in, R.anim.slide_right_out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select item for copy into destination list.
|
||||
* Because it's no way to add item to empty destination list,
|
||||
* we need automatically add it.
|
||||
* */
|
||||
private OnItemClickListener srcItemClick = new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> p, View v, int pos, long id) {
|
||||
bufferItem = sourceAdapter.getItem(pos);
|
||||
if (resultAdapter.isEmpty()) {
|
||||
resultAdapter.addTrack(1, bufferItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Add item from source list by clicking on destination list. */
|
||||
private OnItemClickListener destItemClick = new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> p, View v, int pos, long id) {
|
||||
if (bufferItem != null) {
|
||||
resultAdapter.addTrack(pos, bufferItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Remove item on long click in destination list. */
|
||||
private OnItemLongClickListener destLongItemClick = new OnItemLongClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> p, View v, int pos, long id) {
|
||||
resultAdapter.removeTrack(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
61
src/com/annimon/playlisteditor/ExceptionHandler.java
Normal file
@ -0,0 +1,61 @@
|
||||
package com.annimon.playlisteditor;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Handling exceptions.
|
||||
* If error is critical or must be show to user, then
|
||||
* call <b>alert</b> method.
|
||||
* In other situations call <b>log</b> method.
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public class ExceptionHandler {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "ExceptionHandler";
|
||||
|
||||
public static void alert(Context context, Exception ex) {
|
||||
alert(context, getErrorMessage(ex));
|
||||
}
|
||||
|
||||
public static void alert(Context context, int resourceId) {
|
||||
alert(context, context.getString(resourceId));
|
||||
}
|
||||
|
||||
public static void alert(Context context, String message) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(android.R.string.dialog_alert_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
}).show();
|
||||
}
|
||||
|
||||
public static void log(Exception ex) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, getErrorMessage(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public static void log(String message) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, message);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getErrorMessage(Exception ex) {
|
||||
return ex.getMessage();
|
||||
}
|
||||
|
||||
}
|
75
src/com/annimon/playlisteditor/PlaylistBackup.java
Normal file
@ -0,0 +1,75 @@
|
||||
package com.annimon.playlisteditor;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.annimon.playlisteditor.data.BackupFile;
|
||||
import com.annimon.playlisteditor.data.Playlist;
|
||||
import android.content.Context;
|
||||
|
||||
|
||||
public class PlaylistBackup {
|
||||
|
||||
private static final String BACKUP_EXT = ".bpe";
|
||||
|
||||
private Context context;
|
||||
|
||||
public PlaylistBackup(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void backup(Playlist playlist) throws IOException {
|
||||
String playlistName = playlist.getName();
|
||||
int playlistId = playlist.getId();
|
||||
|
||||
String filename = String.valueOf(playlistName.hashCode()) + BACKUP_EXT;
|
||||
FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
|
||||
DataOutputStream dos = new DataOutputStream(fos);
|
||||
PlaylistDatabase.backupPlaylist(context, playlistName, playlistId, dos);
|
||||
dos.flush();
|
||||
dos.close();
|
||||
}
|
||||
|
||||
public void restore(BackupFile backup) throws IOException {
|
||||
FileInputStream fis = context.openFileInput(backup.getFilename());
|
||||
DataInputStream dis = new DataInputStream(fis);
|
||||
PlaylistDatabase.restorePlaylist(context, dis);
|
||||
dis.close();
|
||||
}
|
||||
|
||||
public List<BackupFile> list() throws IOException {
|
||||
String[] filelist = context.fileList();
|
||||
int length = filelist.length;
|
||||
List<BackupFile> backups = new ArrayList<BackupFile>(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (filelist[i].endsWith(BACKUP_EXT)) {
|
||||
String playlistName = getPlaylistName(filelist[i]);
|
||||
backups.add( new BackupFile(filelist[i], playlistName) );
|
||||
}
|
||||
}
|
||||
|
||||
return backups;
|
||||
}
|
||||
|
||||
public void delete(String filename) {
|
||||
File file = context.getFileStreamPath(filename);
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private String getPlaylistName(String filename) throws IOException {
|
||||
FileInputStream fis = context.openFileInput(filename);
|
||||
DataInputStream dis = new DataInputStream(fis);
|
||||
String name = dis.readUTF();
|
||||
dis.close();
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
194
src/com/annimon/playlisteditor/PlaylistDatabase.java
Normal file
@ -0,0 +1,194 @@
|
||||
package com.annimon.playlisteditor;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import com.annimon.playlisteditor.data.Playlist;
|
||||
import com.annimon.playlisteditor.data.Track;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
|
||||
|
||||
public class PlaylistDatabase {
|
||||
|
||||
public static void createPlaylist(Context context, String name, int[] trackIds) {
|
||||
if (TextUtils.isEmpty(name)) return;
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(MediaStore.Audio.Playlists.NAME, name);
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Uri uri = resolver.insert(MediaStore.Audio.Playlists.getContentUri("external"), cv);
|
||||
|
||||
int size = trackIds.length;
|
||||
ContentValues[] values = new ContentValues[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
values[i] = new ContentValues();
|
||||
values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i);
|
||||
values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, trackIds[i]);
|
||||
}
|
||||
|
||||
resolver.bulkInsert(uri, values);
|
||||
}
|
||||
|
||||
public static void deletePlaylist(Context context, int playlistId) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
resolver.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
|
||||
MediaStore.Audio.Playlists._ID +" = "+ playlistId, null);
|
||||
}
|
||||
|
||||
public static void renamePlaylist(Context context, int id, String name) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
ContentValues value = new ContentValues();
|
||||
value.put(MediaStore.Audio.Playlists.NAME, name);
|
||||
|
||||
resolver.update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
|
||||
value, MediaStore.Audio.Playlists._ID +" = "+ id, null);
|
||||
}
|
||||
|
||||
public static void backupPlaylist(Context context,
|
||||
String playlistName, int playlistId,
|
||||
DataOutputStream dos) throws IOException {
|
||||
|
||||
dos.writeUTF(playlistName);
|
||||
|
||||
String[] projection = {
|
||||
MediaStore.Audio.Playlists.Members.AUDIO_ID
|
||||
};
|
||||
|
||||
Cursor tracksCursor = query(context,
|
||||
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
|
||||
projection, null);
|
||||
|
||||
int size = tracksCursor.getCount();
|
||||
dos.writeInt(size);
|
||||
|
||||
tracksCursor.moveToFirst();
|
||||
for (int i = 0; i < size; i++) {
|
||||
int audioId = tracksCursor.getInt(0);
|
||||
dos.writeInt(audioId);
|
||||
|
||||
tracksCursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
public static void restorePlaylist(Context context,
|
||||
DataInputStream dis) throws IOException {
|
||||
|
||||
String playlistName = dis.readUTF();
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(MediaStore.Audio.Playlists.NAME, playlistName);
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Uri uri = resolver.insert(MediaStore.Audio.Playlists.getContentUri("external"), cv);
|
||||
|
||||
int size = dis.readInt();
|
||||
ContentValues[] values = new ContentValues[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
values[i] = new ContentValues();
|
||||
values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i);
|
||||
values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, dis.readInt());
|
||||
}
|
||||
|
||||
resolver.bulkInsert(uri, values);
|
||||
}
|
||||
|
||||
public static Playlist[] getPlaylists(Context context) {
|
||||
String[] projection = {
|
||||
MediaStore.Audio.Playlists._ID,
|
||||
MediaStore.Audio.Playlists.NAME
|
||||
};
|
||||
|
||||
Cursor playlistSDCardCursor = query(context,
|
||||
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
|
||||
projection, projection[1]);
|
||||
|
||||
int size = playlistSDCardCursor.getCount();
|
||||
Playlist[] playlists = new Playlist[size];
|
||||
|
||||
playlistSDCardCursor.moveToFirst();
|
||||
for (int i = 0; i < size; i++) {
|
||||
int id = playlistSDCardCursor.getInt(0);
|
||||
String name = playlistSDCardCursor.getString(1);
|
||||
playlists[i] = new Playlist(id, name);
|
||||
|
||||
playlistSDCardCursor.moveToNext();
|
||||
}
|
||||
|
||||
return playlists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tracks of playlist. If playlistId equals -1 - return all tracks in system.
|
||||
* @param context
|
||||
* @param playlistId id of playlist, or -1.
|
||||
* @return tracks array.
|
||||
*/
|
||||
public static Track[] getTracks(Context context, long playlistId) {
|
||||
String[] projection = {
|
||||
(playlistId == -1) ?
|
||||
MediaStore.Audio.Media._ID :
|
||||
MediaStore.Audio.Playlists.Members.AUDIO_ID,
|
||||
|
||||
MediaStore.Audio.Media.ARTIST,
|
||||
MediaStore.Audio.Media.TITLE
|
||||
};
|
||||
|
||||
Cursor tracksCursor;
|
||||
if (playlistId == -1) {
|
||||
// Get all tracks.
|
||||
String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0 ";
|
||||
tracksCursor = query(context,
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, selection, null, null, 0);
|
||||
} else {
|
||||
// Get tracks of playlist.
|
||||
tracksCursor = query(context,
|
||||
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
|
||||
projection, null);
|
||||
}
|
||||
|
||||
int size = tracksCursor.getCount();
|
||||
Track[] tracks = new Track[size];
|
||||
|
||||
tracksCursor.moveToFirst();
|
||||
for (int i = 0; i < size; i++) {
|
||||
int audioId = tracksCursor.getInt(0);
|
||||
String artist = tracksCursor.getString(1);
|
||||
String title = tracksCursor.getString(2);
|
||||
tracks[i] = new Track(audioId, artist, title);
|
||||
|
||||
tracksCursor.moveToNext();
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
private static Cursor query(Context context,
|
||||
Uri uri, String[] projection, String sortOrder) {
|
||||
return query(context, uri, projection, null, null, sortOrder, 0);
|
||||
}
|
||||
|
||||
private static Cursor query(Context context, Uri uri, String[] projection,
|
||||
String selection, String[] selectionArgs, String sortOrder, int limit) {
|
||||
try {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
if (resolver == null) {
|
||||
return null;
|
||||
}
|
||||
if (limit > 0) {
|
||||
uri = uri.buildUpon().appendQueryParameter("limit", String.valueOf(limit)).build();
|
||||
}
|
||||
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
ExceptionHandler.log(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
74
src/com/annimon/playlisteditor/PlaylistNameDialog.java
Normal file
@ -0,0 +1,74 @@
|
||||
package com.annimon.playlisteditor;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
/**
|
||||
* Custom dialog for enter playlist name.
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public abstract class PlaylistNameDialog implements View.OnClickListener {
|
||||
|
||||
private Dialog dialog;
|
||||
private EditText nameEditText;
|
||||
private Button okButton;
|
||||
|
||||
public PlaylistNameDialog(Context context) {
|
||||
dialog = new Dialog(context);
|
||||
dialog.setCancelable(true);
|
||||
dialog.setContentView(R.layout.dialog_name);
|
||||
|
||||
nameEditText = (EditText) dialog.findViewById(R.id.dialogPlaylistName);
|
||||
nameEditText.addTextChangedListener(editTextWatcher);
|
||||
okButton = (Button) dialog.findViewById(R.id.dialogOk);
|
||||
okButton.setOnClickListener(this);
|
||||
okButton.setEnabled(false);
|
||||
}
|
||||
|
||||
public void setTitle(int resource) {
|
||||
dialog.setTitle(resource);
|
||||
}
|
||||
|
||||
public void setName(String text) {
|
||||
nameEditText.setText(text);
|
||||
disableButtonIfTextEmpty();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String name = nameEditText.getText().toString();
|
||||
onClick(name);
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
protected abstract void onClick(String name);
|
||||
|
||||
private void disableButtonIfTextEmpty() {
|
||||
|
||||
boolean textEmpty = TextUtils.isEmpty(nameEditText.getText().toString());
|
||||
okButton.setEnabled(!textEmpty);
|
||||
}
|
||||
|
||||
/** To avoid applying empty text we needs disable button on incorrect situation */
|
||||
private TextWatcher editTextWatcher = new TextWatcher() {
|
||||
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
|
||||
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) { }
|
||||
|
||||
public void afterTextChanged(Editable editable) {
|
||||
disableButtonIfTextEmpty();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
325
src/com/annimon/playlisteditor/SelectPlaylistActivity.java
Normal file
@ -0,0 +1,325 @@
|
||||
package com.annimon.playlisteditor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
import com.annimon.playlisteditor.data.BackupFile;
|
||||
import com.annimon.playlisteditor.data.Playlist;
|
||||
|
||||
/**
|
||||
* Select playlist for edit.
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public class SelectPlaylistActivity extends ListActivity {
|
||||
|
||||
private Object actionMode;
|
||||
private ArrayAdapter<Playlist> adapter;
|
||||
|
||||
private int selectedIndex;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
selectedIndex = -1;
|
||||
|
||||
ListView listView = getListView();
|
||||
listView.setBackgroundResource(R.drawable.background);
|
||||
|
||||
getPlaylists();
|
||||
listView.setOnItemClickListener(itemClickListener);
|
||||
if (isNewApi()) {
|
||||
listView.setOnItemLongClickListener(itemLongClickListener);
|
||||
} else {
|
||||
registerForContextMenu(listView);
|
||||
}
|
||||
}
|
||||
|
||||
private final Context getContext() {
|
||||
return SelectPlaylistActivity.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch(item.getItemId()) {
|
||||
case R.id.menu_new_playlist:
|
||||
editPlaylist(-1, "");
|
||||
break;
|
||||
case R.id.menu_restore_playlist:
|
||||
restorePlaylist();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
if (result == RESULT_OK) {
|
||||
getPlaylists();
|
||||
}
|
||||
}
|
||||
|
||||
private void getPlaylists() {
|
||||
Playlist[] playlists = PlaylistDatabase.getPlaylists(this);
|
||||
List<Playlist> list = new ArrayList<Playlist>();
|
||||
list.addAll(Arrays.asList(playlists));
|
||||
|
||||
adapter = new ArrayAdapter<Playlist>(this,
|
||||
android.R.layout.simple_list_item_1, list);
|
||||
|
||||
setListAdapter(adapter);
|
||||
}
|
||||
|
||||
private void editPlaylist(int playlistId, String playlistName) {
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(getContext(), EditPlaylistActivity.class);
|
||||
intent.putExtra(EditPlaylistActivity.PLAYLIST_ID, playlistId);
|
||||
intent.putExtra(EditPlaylistActivity.PLAYLIST_NAME, playlistName);
|
||||
startActivityForResult(intent, 1);
|
||||
|
||||
overridePendingTransition(R.anim.slide_left_in, R.anim.slide_left_out);
|
||||
}
|
||||
|
||||
private void renamePlaylist(final int playlistId) {
|
||||
PlaylistNameDialog dialog = new PlaylistNameDialog(this) {
|
||||
|
||||
@Override
|
||||
protected void onClick(String newPlaylistName) {
|
||||
PlaylistDatabase.renamePlaylist(getContext(),
|
||||
playlistId, newPlaylistName);
|
||||
// Update list of playlists.
|
||||
getPlaylists();
|
||||
}
|
||||
};
|
||||
dialog.setTitle(R.string.rename);
|
||||
String playlistName = adapter.getItem(selectedIndex).getName();
|
||||
dialog.setName(playlistName);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void deletePlaylist(int playlistId) {
|
||||
PlaylistDatabase.deletePlaylist(getContext(), playlistId);
|
||||
// Update list of playlists.
|
||||
getPlaylists();
|
||||
}
|
||||
|
||||
private void backupPlaylist(Playlist playlist) {
|
||||
PlaylistBackup backup = new PlaylistBackup(getContext());
|
||||
try {
|
||||
backup.backup(playlist);
|
||||
Toast.makeText(getContext(), getString(R.string.done),
|
||||
Toast.LENGTH_LONG).show();
|
||||
} catch (IOException ex) {
|
||||
ExceptionHandler.alert(getContext(),
|
||||
R.string.unable_to_store_backup);
|
||||
}
|
||||
}
|
||||
|
||||
private void restorePlaylist() {
|
||||
final PlaylistBackup backup = new PlaylistBackup(getContext());
|
||||
try {
|
||||
final List<BackupFile> list = backup.list();
|
||||
final int size = list.size();
|
||||
if (size == 0) {
|
||||
ExceptionHandler.alert(getContext(), R.string.empty_backup_list);
|
||||
return;
|
||||
}
|
||||
String[] playlistNames = new String[size];
|
||||
final boolean[] checkedItems = new boolean[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
playlistNames[i] = list.get(i).getPlaylistName();
|
||||
checkedItems[i] = false;
|
||||
}
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.restore_playlist)
|
||||
.setCancelable(true)
|
||||
// Items
|
||||
.setMultiChoiceItems(playlistNames, checkedItems,
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which,
|
||||
boolean isChecked) {
|
||||
checkedItems[which] = isChecked;
|
||||
}
|
||||
})
|
||||
// Restore
|
||||
.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
boolean isRestored = false;
|
||||
for (int i = 0; i < size; i++) {
|
||||
try {
|
||||
if (checkedItems[i]) {
|
||||
backup.restore(list.get(i));
|
||||
isRestored = true;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ExceptionHandler.alert(getContext(),
|
||||
getString(R.string.error_while_restore_backup)
|
||||
+ list.get(i).getPlaylistName());
|
||||
}
|
||||
}
|
||||
if (isRestored) {
|
||||
Toast.makeText(getContext(), getString(R.string.done),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
// Update list of playlists.
|
||||
getPlaylists();
|
||||
}
|
||||
})
|
||||
// Delete backups
|
||||
.setNegativeButton(R.string.delete,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (checkedItems[i]) {
|
||||
backup.delete(list.get(i).getFilename());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// Cancel
|
||||
.setNeutralButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
} catch (IOException ex) {
|
||||
ExceptionHandler.alert(getContext(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/** Show editor window by select item. */
|
||||
private OnItemClickListener itemClickListener = new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> p, View v, int pos, long id) {
|
||||
int playlistId = adapter.getItem(pos).getId();
|
||||
String playlistName = adapter.getItem(pos).getName();
|
||||
editPlaylist(playlistId, playlistName);
|
||||
}
|
||||
};
|
||||
|
||||
/*** API SPECIFIC METHODS ***/
|
||||
private boolean isNewApi() {
|
||||
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
getMenuInflater().inflate(R.menu.main_context, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterView.AdapterContextMenuInfo info =
|
||||
(AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
selectedIndex = info.position;
|
||||
|
||||
actionOrContextMenuItemClicked(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean actionOrContextMenuItemClicked(MenuItem item) {
|
||||
if ( !(0 <= selectedIndex) && (selectedIndex < adapter.getCount()) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int playlistId = adapter.getItem(selectedIndex).getId();
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_delete_playlist:
|
||||
deletePlaylist(playlistId);
|
||||
return true;
|
||||
case R.id.menu_rename_playlist:
|
||||
renamePlaylist(playlistId);
|
||||
return true;
|
||||
case R.id.menu_backup_playlist:
|
||||
backupPlaylist(adapter.getItem(selectedIndex));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Implements only in SDK >= 11 */
|
||||
private OnItemLongClickListener itemLongClickListener = new OnItemLongClickListener() {
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> p, View v, int pos, long id) {
|
||||
if (actionMode != null) return false;
|
||||
|
||||
selectedIndex = pos;
|
||||
if (isNewApi()) {
|
||||
actionMode = startActionMode(new ActionMode.Callback() {
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
MenuInflater inflater = mode.getMenuInflater();
|
||||
inflater.inflate(R.menu.main_context, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
if (actionOrContextMenuItemClicked(item)) {
|
||||
mode.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
actionMode = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
v.setSelected(true);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
85
src/com/annimon/playlisteditor/TracksAdapter.java
Normal file
@ -0,0 +1,85 @@
|
||||
package com.annimon.playlisteditor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import com.annimon.playlisteditor.data.Track;
|
||||
|
||||
/**
|
||||
* Adapter for track info.
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public class TracksAdapter extends ArrayAdapter<Track> {
|
||||
|
||||
private int resource;
|
||||
private LayoutInflater inflater;
|
||||
private List<Track> objects;
|
||||
|
||||
public TracksAdapter(Context context, int resource, Track[] tracksArray) {
|
||||
super(context, resource, tracksArray);
|
||||
|
||||
this.resource = resource;
|
||||
objects = new ArrayList<Track>();
|
||||
objects.addAll(Arrays.asList(tracksArray));
|
||||
|
||||
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
public void addTrack(int position, Track track) {
|
||||
if (position > objects.size()) {
|
||||
objects.add(track);
|
||||
} else {
|
||||
objects.add(position, track);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeTrack(int position) {
|
||||
objects.remove(position);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Track getItem(int position) {
|
||||
return objects.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = convertView;
|
||||
ViewHolder holder;
|
||||
|
||||
if (convertView == null) {
|
||||
holder = new ViewHolder();
|
||||
v = inflater.inflate(resource, null);
|
||||
holder.text = (TextView) v.findViewById(android.R.id.text1);
|
||||
v.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) v.getTag();
|
||||
}
|
||||
|
||||
holder.text.setText(objects.get(position).toString());
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
TextView text;
|
||||
}
|
||||
}
|
21
src/com/annimon/playlisteditor/data/BackupFile.java
Normal file
@ -0,0 +1,21 @@
|
||||
package com.annimon.playlisteditor.data;
|
||||
|
||||
public class BackupFile {
|
||||
|
||||
private String filename;
|
||||
private String playlistName;
|
||||
|
||||
public BackupFile(String filename, String playlistName) {
|
||||
this.filename = filename;
|
||||
this.playlistName = playlistName;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public String getPlaylistName() {
|
||||
return playlistName;
|
||||
}
|
||||
|
||||
}
|
36
src/com/annimon/playlisteditor/data/Playlist.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.annimon.playlisteditor.data;
|
||||
|
||||
public class Playlist {
|
||||
|
||||
private int id;
|
||||
private String name;
|
||||
|
||||
|
||||
public Playlist(int id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
}
|
23
src/com/annimon/playlisteditor/data/Track.java
Normal file
@ -0,0 +1,23 @@
|
||||
package com.annimon.playlisteditor.data;
|
||||
|
||||
|
||||
public class Track {
|
||||
|
||||
private int audioId;
|
||||
private String artist, title;
|
||||
|
||||
public Track(int audioId, String artist, String title) {
|
||||
this.audioId = audioId;
|
||||
this.artist = artist;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public int getAudioId() {
|
||||
return audioId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return artist + " - " + title;
|
||||
}
|
||||
}
|