aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--subsonic-android/res/drawable-hdpi-v4/list_item_more_saved.9.pngbin0 -> 5919 bytes
-rw-r--r--subsonic-android/res/layout/select_podcasts.xml30
-rw-r--r--subsonic-android/res/menu/select_podcasts.xml18
-rw-r--r--subsonic-android/res/values/strings.xml3
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/domain/PodcastChannel.java72
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/domain/PodcastEpisode.java62
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java38
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java140
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java20
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/MusicService.java8
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java12
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java22
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastChannelParser.java70
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastEntryParser.java79
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/Constants.java3
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelAdapter.java59
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelView.java53
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/view/SongView.java7
18 files changed, 689 insertions, 7 deletions
diff --git a/subsonic-android/res/drawable-hdpi-v4/list_item_more_saved.9.png b/subsonic-android/res/drawable-hdpi-v4/list_item_more_saved.9.png
new file mode 100644
index 00000000..f3805bfb
--- /dev/null
+++ b/subsonic-android/res/drawable-hdpi-v4/list_item_more_saved.9.png
Binary files differ
diff --git a/subsonic-android/res/layout/select_podcasts.xml b/subsonic-android/res/layout/select_podcasts.xml
new file mode 100644
index 00000000..3fda5799
--- /dev/null
+++ b/subsonic-android/res/layout/select_podcasts.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/select_podcasts_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <include layout="@layout/tab_progress" />
+
+ <TextView
+ android:id="@+id/select_podcasts_empty"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dip"
+ android:text="@string/select_podcasts.empty"
+ android:visibility="gone" />
+
+ <ListView
+ android:id="@+id/select_podcasts_list"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0"
+ android:textFilterEnabled="true"
+ android:fastScrollEnabled="true"/>
+ </LinearLayout>
+</FrameLayout>
diff --git a/subsonic-android/res/menu/select_podcasts.xml b/subsonic-android/res/menu/select_podcasts.xml
new file mode 100644
index 00000000..e0f9a718
--- /dev/null
+++ b/subsonic-android/res/menu/select_podcasts.xml
@@ -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_refresh"
+ android:icon="@drawable/action_refresh"
+ android:title="@string/menu.refresh"
+ android:showAsAction="always|withText"/>
+
+ <item
+ android:id="@+id/menu_settings"
+ android:icon="@drawable/action_settings"
+ android:title="@string/menu.settings"/>
+
+ <item
+ android:id="@+id/menu_exit"
+ android:icon="@drawable/action_exit"
+ android:title="@string/menu.exit"/>
+</menu> \ No newline at end of file
diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml
index 150933fd..d7c0d089 100644
--- a/subsonic-android/res/values/strings.xml
+++ b/subsonic-android/res/values/strings.xml
@@ -27,6 +27,7 @@
<string name="button_bar.search">Search</string>
<string name="button_bar.playlists">Playlists</string>
<string name="button_bar.now_playing">Playing</string>
+ <string name="button_bar.podcasts">Podcasts</string>
<string name="button_bar.chat">Chat</string>
<string name="main.welcome_title">Welcome!</string>
@@ -129,6 +130,8 @@
<string name="select_genre.empty">No genres found</string>
<string name="select_genre.blank">Blank</string>
+ <string name="select_podcasts.empty">No podcasts found</string>
+
<string name="select_playlist.empty">No saved playlists on server</string>
<string name="download.empty">Playlist is empty</string>
diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/PodcastChannel.java b/subsonic-android/src/github/daneren2005/dsub/domain/PodcastChannel.java
new file mode 100644
index 00000000..6cb3944f
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/domain/PodcastChannel.java
@@ -0,0 +1,72 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.domain;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author Scott
+ */
+public class PodcastChannel implements Serializable {
+ private String id;
+ private String name;
+ private String url;
+ private String description;
+ private String status;
+
+ public PodcastChannel() {
+
+ }
+
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+ public void setStatus(String status) {
+ this.status = status;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/PodcastEpisode.java b/subsonic-android/src/github/daneren2005/dsub/domain/PodcastEpisode.java
new file mode 100644
index 00000000..d85e8325
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/domain/PodcastEpisode.java
@@ -0,0 +1,62 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.domain;
+
+/**
+ *
+ * @author Scott
+ */
+public class PodcastEpisode extends MusicDirectory.Entry {
+ private String episodeId;
+ private String description;
+ private String date;
+ private String status;
+
+ public PodcastEpisode() {
+ setDirectory(false);
+ }
+
+ public String getEpisodeId() {
+ return episodeId;
+ }
+ public void setEpisodeId(String episodeId) {
+ this.episodeId = episodeId;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getDate() {
+ return date;
+ }
+ public void setDate(String date) {
+ this.date = date;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+ public void setStatus(String status) {
+ this.status = status;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
index 82b6c481..5b007e6f 100644
--- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
+++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
@@ -54,6 +54,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
String name;
String playlistId;
String playlistName;
+ String podcastId;
+ String podcastName;
+ String podcastDescription;
String albumListType;
String albumListExtra;
int albumListSize;
@@ -100,6 +103,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
name = args.getString(Constants.INTENT_EXTRA_NAME_NAME);
playlistId = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID);
playlistName = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME);
+ podcastId = args.getString(Constants.INTENT_EXTRA_NAME_PODCAST_ID);
+ podcastName = args.getString(Constants.INTENT_EXTRA_NAME_PODCAST_NAME);
+ podcastDescription = args.getString(Constants.INTENT_EXTRA_NAME_PODCAST_DESCRIPTION);
albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE);
albumListExtra = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA);
albumListSize = args.getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0);
@@ -267,6 +273,8 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
emptyView.setVisibility(View.INVISIBLE);
if (playlistId != null) {
getPlaylist(playlistId, playlistName);
+ } else if(podcastId != null) {
+ getPodcast(podcastId, podcastName);
} else if (albumListType != null) {
getAlbumList(albumListType, albumListSize);
} else {
@@ -295,6 +303,17 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
}
}.execute();
}
+
+ private void getPodcast(final String podcastId, final String podcastName) {
+ setTitle(podcastName);
+
+ new LoadTask() {
+ @Override
+ protected MusicDirectory load(MusicService service) throws Exception {
+ return service.getPodcastEpisodes(podcastId, context, this);
+ }
+ }.execute();
+ }
private void getAlbumList(final String albumListType, final int size) {
showHeader = false;
@@ -648,6 +667,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
TextView titleView = (TextView) header.findViewById(R.id.select_album_title);
if(playlistName != null) {
titleView.setText(playlistName);
+ } else if(podcastName != null) {
+ titleView.setText(podcastName);
+ titleView.setPadding(0, 6, 4, 8);
} else if(name != null) {
titleView.setText(name);
}
@@ -673,16 +695,24 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
if (artists.size() == 1) {
artistView.setText(artists.iterator().next());
artistView.setVisibility(View.VISIBLE);
+ } else if(podcastDescription != null) {
+ artistView.setText(podcastDescription);
+ artistView.setSingleLine(false);
+ artistView.setLines(5);
} else {
artistView.setVisibility(View.GONE);
}
TextView songCountView = (TextView) header.findViewById(R.id.select_album_song_count);
- String s = context.getResources().getQuantityString(R.plurals.select_album_n_songs, songCount, songCount);
- songCountView.setText(s.toUpperCase());
-
TextView songLengthView = (TextView) header.findViewById(R.id.select_album_song_length);
- songLengthView.setText(Util.formatDuration(totalDuration));
+ if(podcastDescription == null) {
+ String s = context.getResources().getQuantityString(R.plurals.select_album_n_songs, songCount, songCount);
+ songCountView.setText(s.toUpperCase());
+ songLengthView.setText(Util.formatDuration(totalDuration));
+ } else {
+ songCountView.setVisibility(View.GONE);
+ songLengthView.setVisibility(View.GONE);
+ }
if(add) {
return header;
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java
new file mode 100644
index 00000000..923eac87
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java
@@ -0,0 +1,140 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2010 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.fragments;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.MenuInflater;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.PodcastChannel;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
+import github.daneren2005.dsub.util.BackgroundTask;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.TabBackgroundTask;
+import github.daneren2005.dsub.view.PodcastChannelAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Scott
+ */
+public class SelectPodcastsFragment extends SubsonicFragment implements AdapterView.OnItemClickListener {
+ private static final String TAG = SelectPodcastsFragment.class.getSimpleName();
+ private ListView podcastListView;
+ private View emptyView;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+ rootView = inflater.inflate(R.layout.select_podcasts, container, false);
+
+ podcastListView = (ListView)rootView.findViewById(R.id.select_podcasts_list);
+ podcastListView.setOnItemClickListener(this);
+ emptyView = rootView.findViewById(R.id.select_podcasts_empty);
+ refresh();
+
+ return rootView;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
+ menuInflater.inflate(R.menu.select_podcasts, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if(super.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void setPrimaryFragment(boolean primary) {
+ super.setPrimaryFragment(primary);
+ if(rootView != null) {
+ if(primary) {
+ ((ViewGroup)rootView).getChildAt(0).setVisibility(View.VISIBLE);
+ } else {
+ ((ViewGroup)rootView).getChildAt(0).setVisibility(View.GONE);
+ }
+ }
+ }
+
+ @Override
+ protected void refresh(final boolean refresh) {
+ setTitle(R.string.button_bar_podcasts);
+
+ BackgroundTask<List<PodcastChannel>> task = new TabBackgroundTask<List<PodcastChannel>>(this) {
+ @Override
+ protected List<PodcastChannel> doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+
+ List<PodcastChannel> channels = new ArrayList<PodcastChannel>();
+
+ try {
+ channels = musicService.getPodcastChannels(refresh, context, this);
+ } catch (Exception x) {
+ Log.e(TAG, "Failed to load podcasts", x);
+ }
+
+ return channels;
+ }
+
+ @Override
+ protected void done(List<PodcastChannel> result) {
+ emptyView.setVisibility(result == null || result.isEmpty() ? View.VISIBLE : View.GONE);
+
+ if (result != null) {
+ podcastListView.setAdapter(new PodcastChannelAdapter(context, result));
+ }
+
+ }
+ };
+ task.execute();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ PodcastChannel channel = (PodcastChannel) parent.getItemAtPosition(position);
+
+ SubsonicFragment fragment = new SelectDirectoryFragment();
+ Bundle args = new Bundle();
+ args.putString(Constants.INTENT_EXTRA_NAME_PODCAST_ID, channel.getId());
+ args.putString(Constants.INTENT_EXTRA_NAME_PODCAST_NAME, channel.getName());
+ args.putString(Constants.INTENT_EXTRA_NAME_PODCAST_DESCRIPTION, channel.getDescription());
+ fragment.setArguments(args);
+
+ replaceFragment(fragment, R.id.select_podcasts_layout);
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
index a6d6e8f8..e06f3372 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
@@ -33,6 +33,8 @@ import github.daneren2005.dsub.domain.Lyrics;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.Playlist;
+import github.daneren2005.dsub.domain.PodcastChannel;
+import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
import github.daneren2005.dsub.domain.Share;
@@ -59,6 +61,7 @@ public class CachedMusicService implements MusicService {
private final TimeLimitedCache<List<Playlist>> cachedPlaylists = new TimeLimitedCache<List<Playlist>>(3600, TimeUnit.SECONDS);
private final TimeLimitedCache<List<MusicFolder>> cachedMusicFolders = new TimeLimitedCache<List<MusicFolder>>(10 * 3600, TimeUnit.SECONDS);
private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS);
+ private final TimeLimitedCache<List<PodcastChannel>> cachedPodcastChannels = new TimeLimitedCache<List<PodcastChannel>>(10 * 3600, TimeUnit.SECONDS);
private String restUrl;
public CachedMusicService(MusicService musicService) {
@@ -303,6 +306,23 @@ public class CachedMusicService implements MusicService {
}
@Override
+ public List<PodcastChannel> getPodcastChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ List<PodcastChannel> result = refresh ? null : cachedPodcastChannels.get();
+
+ if (result == null) {
+ result = musicService.getPodcastChannels(refresh, context, progressListener);
+ cachedPodcastChannels.set(result);
+ }
+
+ return result;
+ }
+
+ @Override
+ public MusicDirectory getPodcastEpisodes(String id, Context context, ProgressListener progressListener) throws Exception {
+ return musicService.getPodcastEpisodes(id, context, progressListener);
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
return musicService.processOfflineSyncs(context, progressListener);
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
index c6efffc3..57deff8f 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
@@ -32,6 +32,8 @@ import github.daneren2005.dsub.domain.Lyrics;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.Playlist;
+import github.daneren2005.dsub.domain.PodcastChannel;
+import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
import github.daneren2005.dsub.domain.Share;
@@ -116,7 +118,11 @@ public interface MusicService {
List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
- public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception;
+ MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception;
+
+ List<PodcastChannel> getPodcastChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
+
+ MusicDirectory getPodcastEpisodes(String id, Context context, ProgressListener progressListener) throws Exception;
int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception;
} \ No newline at end of file
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
index ba194562..ea45b626 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
@@ -42,6 +42,8 @@ import github.daneren2005.dsub.domain.Lyrics;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.Playlist;
+import github.daneren2005.dsub.domain.PodcastChannel;
+import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
import github.daneren2005.dsub.util.Constants;
@@ -595,6 +597,16 @@ public class OfflineMusicService extends RESTMusicService {
return result;
}
+
+ @Override
+ public List<PodcastChannel> getPodcastChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Getting Podcasts not available in offline mode");
+ }
+
+ @Override
+ public MusicDirectory getPodcastEpisodes(String id, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Getting Podcasts not available in offline mode");
+ }
@Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
index ac564f29..ffa8e676 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
@@ -84,6 +84,8 @@ import github.daneren2005.dsub.service.parser.MusicDirectoryParser;
import github.daneren2005.dsub.service.parser.MusicFoldersParser;
import github.daneren2005.dsub.service.parser.PlaylistParser;
import github.daneren2005.dsub.service.parser.PlaylistsParser;
+import github.daneren2005.dsub.service.parser.PodcastChannelParser;
+import github.daneren2005.dsub.service.parser.PodcastEntryParser;
import github.daneren2005.dsub.service.parser.RandomSongsParser;
import github.daneren2005.dsub.service.parser.SearchResult2Parser;
import github.daneren2005.dsub.service.parser.SearchResultParser;
@@ -885,6 +887,26 @@ public class RESTMusicService implements MusicService {
}
@Override
+ public List<PodcastChannel> getPodcastChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ Reader reader = getReader(context, progressListener, "getPodcasts", null, Arrays.asList("includeEpisodes"), Arrays.<Object>asList("false"));
+ try {
+ return new PodcastChannelParser(context).parse(reader, progressListener);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public MusicDirectory getPodcastEpisodes(String id, Context context, ProgressListener progressListener) throws Exception {
+ Reader reader = getReader(context, progressListener, "getPodcasts", null, Arrays.asList("id"), Arrays.<Object>asList(id));
+ try {
+ return new PodcastEntryParser(context).parse(reader, progressListener);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
return processOfflineScrobbles(context, progressListener) + processOfflineStars(context, progressListener);
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastChannelParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastChannelParser.java
new file mode 100644
index 00000000..4fc49290
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastChannelParser.java
@@ -0,0 +1,70 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.PodcastChannel;
+import github.daneren2005.dsub.util.ProgressListener;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ *
+ * @author Scott
+ */
+public class PodcastChannelParser extends AbstractParser {
+ public PodcastChannelParser(Context context) {
+ super(context);
+ }
+
+ public List<PodcastChannel> parse(Reader reader, ProgressListener progressListener) throws Exception {
+ updateProgress(progressListener, R.string.parser_reading);
+ init(reader);
+
+ List<PodcastChannel> channels = new ArrayList<PodcastChannel>();
+ int eventType;
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("channel".equals(name)) {
+ PodcastChannel channel = new PodcastChannel();
+ channel.setId(get("id"));
+ channel.setUrl(get("url"));
+ channel.setName(get("title"));
+ channel.setDescription(get("description"));
+ channel.setStatus(get("status"));
+
+ if("error".equals(channel.getStatus())) {
+ channel.setStatus(get("errorMessage"));
+ }
+ channels.add(channel);
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+ return channels;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastEntryParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastEntryParser.java
new file mode 100644
index 00000000..efc36a7e
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/PodcastEntryParser.java
@@ -0,0 +1,79 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.PodcastEpisode;
+import github.daneren2005.dsub.util.ProgressListener;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ *
+ * @author Scott
+ */
+public class PodcastEntryParser extends AbstractParser {
+ public PodcastEntryParser(Context context) {
+ super(context);
+ }
+
+ public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception {
+ updateProgress(progressListener, R.string.parser_reading);
+ init(reader);
+
+ MusicDirectory episodes = new MusicDirectory();
+ int eventType;
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("channel".equals(name)) {
+ episodes.setId(get("id"));
+ episodes.setName(get("title"));
+ }
+ else if ("episode".equals(name)) {
+ PodcastEpisode episode = new PodcastEpisode();
+ episode.setEpisodeId(get("id"));
+ episode.setId(get("streamId"));
+ episode.setTitle(get("title"));
+ episode.setDescription(get("description"));
+ episode.setDate(get("publishDate"));
+ episode.setStatus(get("status"));
+ episode.setCoverArt(get("coverArt"));
+ episode.setSize(getLong("size"));
+ episode.setContentType(get("contentType"));
+ episode.setSuffix(get("suffix"));
+ episode.setDuration(getInteger("duration"));
+ episode.setBitRate(getInteger("bitRate"));
+ episode.setPath(get("path"));
+ episodes.addChild(episode);
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+ return episodes;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java
index 2d7edbab..a5168a9a 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java
@@ -55,6 +55,9 @@ public final class Constants {
public static final String INTENT_EXTRA_NAME_EXIT = "subsonic.exit" ;
public static final String INTENT_EXTRA_NAME_DOWNLOAD = "subsonic.download";
public static final String INTENT_EXTRA_VIEW_ALBUM = "subsonic.view_album";
+ public static final String INTENT_EXTRA_NAME_PODCAST_ID = "subsonic.podcast.id";
+ public static final String INTENT_EXTRA_NAME_PODCAST_NAME = "subsonic.podcast.name";
+ public static final String INTENT_EXTRA_NAME_PODCAST_DESCRIPTION = "subsonic.podcast.description";
// Notification IDs.
public static final int NOTIFICATION_ID_PLAYING = 100;
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelAdapter.java b/subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelAdapter.java
new file mode 100644
index 00000000..6b7af991
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelAdapter.java
@@ -0,0 +1,59 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2010 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.view;
+
+import android.widget.ArrayAdapter;
+import android.widget.SectionIndexer;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.PodcastChannel;
+
+import java.util.List;
+import java.util.Set;
+import java.util.LinkedHashSet;
+import java.util.ArrayList;
+
+/**
+ * @author Sindre Mehus
+*/
+public class PodcastChannelAdapter extends ArrayAdapter<PodcastChannel>{
+ private Context activity;
+ private List<PodcastChannel> podcasts;
+
+ public PodcastChannelAdapter(Context context, List<PodcastChannel> podcasts) {
+ super(context, android.R.layout.simple_list_item_1, podcasts);
+ this.activity = context;
+ this.podcasts = podcasts;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ PodcastChannel podcast = podcasts.get(position);
+ PodcastChannelView view;
+ if (convertView != null && convertView instanceof PodcastChannelView) {
+ view = (PodcastChannelView) convertView;
+ } else {
+ view = new PodcastChannelView(activity);
+ }
+ view.setPodcastChannel(podcast);
+ return view;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelView.java b/subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelView.java
new file mode 100644
index 00000000..62e67a9a
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/view/PodcastChannelView.java
@@ -0,0 +1,53 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.PodcastChannel;
+
+public class PodcastChannelView extends UpdateView {
+ private static final String TAG = PodcastChannelView.class.getSimpleName();
+
+ private TextView titleView;
+ private ImageButton starButton;
+ private ImageView moreButton;
+
+ public PodcastChannelView(Context context) {
+ super(context);
+ LayoutInflater.from(context).inflate(R.layout.artist_list_item, this, true);
+
+ titleView = (TextView) findViewById(R.id.artist_name);
+ starButton = (ImageButton) findViewById(R.id.artist_star);
+ moreButton = (ImageView) findViewById(R.id.artist_more);
+ moreButton.setClickable(false);
+ }
+
+ public void setPodcastChannel(PodcastChannel podcastChannel) {
+ titleView.setText(podcastChannel.getName());
+
+ starButton.setVisibility(View.GONE);
+ starButton.setFocusable(false);
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java
index 64a1d911..6f594048 100644
--- a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java
+++ b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java
@@ -87,7 +87,10 @@ public class SongView extends UpdateView implements Checkable {
}
if(!song.isVideo()) {
- artist.append(song.getArtist()).append(" (")
+ if(song.getArtist() != null) {
+ artist.append(song.getArtist());
+ }
+ artist.append(" (")
.append(String.format(getContext().getString(R.string.song_details_all), bitRate == null ? "" : bitRate, fileFormat))
.append(")");
} else {
@@ -101,7 +104,7 @@ public class SongView extends UpdateView implements Checkable {
}
titleTextView.setText(title);
- artistTextView.setText(artist);
+ artistTextView.setText(artist);
durationTextView.setText(Util.formatDuration(song.getDuration()));
checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE);
starButton.setVisibility(!song.isStarred() ? View.GONE : View.VISIBLE);