From e3e90cebf1568ccc724980b3105faff4f8097ff7 Mon Sep 17 00:00:00 2001 From: rcocula Date: Fri, 26 Feb 2016 10:40:52 +0100 Subject: [PATCH 1/2] podcasts feature Work on podcats Work on podcats podcasts Dont't bypass the context menu in long click on album view. Work on podcats Work on podcats Work on podcats podcasts If items are maximized they will return to minimize form when scrolling. (cherry picked from commit ada247d) podcasts Podcasts icon podcasts podcasts (cherry picked from commit b4ff614) --- ultrasonic/src/main/AndroidManifest.xml | 5 + .../GetPodcastEpisodesTestReaderProvider.java | 85 +++++++++++ .../service/GetPodcastTestReaderProvider.java | 41 +++++ .../ultrasonic/activity/PodcastsActivity.java | 133 ++++++++++++++++ .../activity/SelectAlbumActivity.java | 19 +++ .../activity/SubsonicTabActivity.java | 6 + .../ultrasonic/domain/PodcastEpisode.java | 94 ++++++++++++ .../ultrasonic/domain/PodcastsChannel.java | 94 ++++++++++++ .../service/CachedMusicService.java | 21 +++ .../ultrasonic/service/MusicService.java | 7 + .../ultrasonic/service/RESTMusicService.java | 47 ++++++ .../service/parser/PodcastEpisodeParser.java | 143 ++++++++++++++++++ .../parser/PodcastsChannelsParser.java | 81 ++++++++++ .../org/moire/ultrasonic/util/Constants.java | 1 + .../view/PodcastsChannelsAdapter.java | 83 ++++++++++ .../view/PodcatsChannelItemView.java | 63 ++++++++ .../drawable-hdpi/ic_menu_podcasts_dark.png | Bin 0 -> 752 bytes .../drawable-hdpi/ic_menu_podcasts_light.png | Bin 0 -> 846 bytes .../drawable-mdpi/ic_menu_podcasts_dark.png | Bin 0 -> 513 bytes .../drawable-mdpi/ic_menu_podcasts_light.png | Bin 0 -> 601 bytes .../drawable-xhdpi/ic_menu_podcasts_dark.png | Bin 0 -> 994 bytes .../drawable-xhdpi/ic_menu_podcasts_light.png | Bin 0 -> 1131 bytes .../drawable-xxhdpi/ic_menu_podcasts_dark.png | Bin 0 -> 1641 bytes .../ic_menu_podcasts_light.png | Bin 0 -> 1853 bytes ultrasonic/src/main/res/layout/menu_main.xml | 8 + ultrasonic/src/main/res/layout/podcasts.xml | 27 ++++ .../main/res/layout/podcasts_channel_item.xml | 9 ++ ultrasonic/src/main/res/values/strings.xml | 3 + ultrasonic/src/main/res/values/styles.xml | 1 + ultrasonic/src/main/res/values/themes.xml | 2 + 30 files changed, 973 insertions(+) create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastEpisode.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastsChannelsParser.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcatsChannelItemView.java create mode 100644 ultrasonic/src/main/res/drawable-hdpi/ic_menu_podcasts_dark.png create mode 100644 ultrasonic/src/main/res/drawable-hdpi/ic_menu_podcasts_light.png create mode 100644 ultrasonic/src/main/res/drawable-mdpi/ic_menu_podcasts_dark.png create mode 100644 ultrasonic/src/main/res/drawable-mdpi/ic_menu_podcasts_light.png create mode 100644 ultrasonic/src/main/res/drawable-xhdpi/ic_menu_podcasts_dark.png create mode 100644 ultrasonic/src/main/res/drawable-xhdpi/ic_menu_podcasts_light.png create mode 100644 ultrasonic/src/main/res/drawable-xxhdpi/ic_menu_podcasts_dark.png create mode 100644 ultrasonic/src/main/res/drawable-xxhdpi/ic_menu_podcasts_light.png create mode 100644 ultrasonic/src/main/res/layout/podcasts.xml create mode 100644 ultrasonic/src/main/res/layout/podcasts_channel_item.xml diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml index d94bbd8b..fa9c65f5 100644 --- a/ultrasonic/src/main/AndroidManifest.xml +++ b/ultrasonic/src/main/AndroidManifest.xml @@ -57,6 +57,11 @@ a:configChanges="orientation|keyboardHidden" a:label="@string/playlist.label" a:launchMode="standard"/> + diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java b/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java new file mode 100644 index 00000000..538ad595 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java @@ -0,0 +1,85 @@ +package org.moire.ultrasonic.Test.service; + +import java.io.Reader; +import java.io.StringReader; + +/** + * Created by rcocula on 11/03/2016. + */ +public class GetPodcastEpisodesTestReaderProvider { + + private static String data = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + + public static Reader getReader() { + + return new StringReader(data); + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java b/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java new file mode 100644 index 00000000..91d3f62d --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java @@ -0,0 +1,41 @@ +package org.moire.ultrasonic.Test.service; + +import java.io.Reader; +import java.io.StringReader; + +/** + * Created by rcocula on 11/03/2016. + */ +public class GetPodcastTestReaderProvider { + + private static String data = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + + public static Reader getReader() { + + return new StringReader(data); + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java new file mode 100644 index 00000000..74f9a223 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/PodcastsActivity.java @@ -0,0 +1,133 @@ +/* + 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 . + + Copyright 2009 (C) Sindre Mehus + */ + +package org.moire.ultrasonic.activity; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +import com.handmark.pulltorefresh.library.PullToRefreshBase; +import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener; +import com.handmark.pulltorefresh.library.PullToRefreshListView; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.domain.Playlist; +import org.moire.ultrasonic.domain.PodcastsChannel; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.service.OfflineException; +import org.moire.ultrasonic.service.ServerTooOldException; +import org.moire.ultrasonic.util.BackgroundTask; +import org.moire.ultrasonic.util.CacheCleaner; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.LoadingTask; +import org.moire.ultrasonic.util.TabActivityBackgroundTask; +import org.moire.ultrasonic.util.Util; +import org.moire.ultrasonic.view.PlaylistAdapter; +import org.moire.ultrasonic.view.PodcastsChannelsAdapter; + +import java.util.List; + +public class PodcastsActivity extends SubsonicTabActivity { + + private View emptyTextView; + SubsonicTabActivity currentActivity = null; + ListView channelItemsListView = null; + + Context currentContext = (Context)this; + + @Override + public void onCreate(Bundle savedInstanceState) + { + this.currentActivity = this; + + super.onCreate(savedInstanceState); + setContentView(R.layout.podcasts); + + + emptyTextView = findViewById(R.id.select_podcasts_empty); + channelItemsListView = (ListView)findViewById(R.id.podcasts_channels_items_list); + channelItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + PodcastsChannel pc = (PodcastsChannel) parent.getItemAtPosition(position); + if (pc == null) { + return; + } + + Intent intent = new Intent(currentContext, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID, pc.getId()); + startActivityForResultWithoutTransition(PodcastsActivity.this, intent); + } + }); + + load(); + } + + + + private void load() + { + BackgroundTask> task = new TabActivityBackgroundTask>(this, true) + { + @Override + protected List doInBackground() throws Throwable + { + MusicService musicService = MusicServiceFactory.getMusicService(PodcastsActivity.this); + List channels = musicService.getPodcastsChannels(false,PodcastsActivity.this, this); + + /* TODO c'est quoi ce nettoyage de cache ? + if (!Util.isOffline(PodcastsActivity.this)) + new CacheCleaner(PodcastsActivity.this, getDownloadService()).cleanPlaylists(playlists); + */ + return channels; + } + + @Override + protected void done(List result) + { + channelItemsListView.setAdapter(new PodcastsChannelsAdapter(currentActivity, result)); + emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); + } + }; + task.execute(); + } + + +} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java index 3a3e9d6a..0958aabe 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java @@ -278,6 +278,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity String name = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME); String parentId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID); String playlistId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); + String podcastChannelId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID); String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); String shareId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_SHARE_ID); String shareName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_SHARE_NAME); @@ -297,6 +298,9 @@ public class SelectAlbumActivity extends SubsonicTabActivity { getPlaylist(playlistId, playlistName); } + else if (podcastChannelId != null) { + getPodcastEpisodes(podcastChannelId); + } else if (shareId != null) { getShare(shareId, shareName); @@ -852,6 +856,21 @@ public class SelectAlbumActivity extends SubsonicTabActivity }.execute(); } + private void getPodcastEpisodes(final String podcastChannelId) + { + // TODO on fait quoi là ? + //setActionBarSubtitle(playlistName); + + new LoadTask() + { + @Override + protected MusicDirectory load(MusicService service) throws Exception + { + return service.getPodcastEpisodes(podcastChannelId, SelectAlbumActivity.this, this); + } + }.execute(); + } + private void getShare(final String shareId, final CharSequence shareName) { setActionBarSubtitle(shareName); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java index d5a4ec08..3756c76a 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -151,6 +151,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen findViewById(R.id.menu_browse).setOnClickListener(this); findViewById(R.id.menu_search).setOnClickListener(this); findViewById(R.id.menu_playlists).setOnClickListener(this); + findViewById(R.id.menu_podcasts).setOnClickListener(this); sharesMenuItem.setOnClickListener(this); chatMenuItem.setOnClickListener(this); bookmarksMenuItem.setOnClickListener(this); @@ -1436,6 +1437,11 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); break; + case R.id.menu_podcasts: + intent = new Intent(SubsonicTabActivity.this, PodcastsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent); + break; case R.id.menu_shares: intent = new Intent(SubsonicTabActivity.this, ShareActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastEpisode.java b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastEpisode.java new file mode 100644 index 00000000..857cb420 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastEpisode.java @@ -0,0 +1,94 @@ +/* + 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 . + + Copyright 2009 (C) Sindre Mehus + */ +package org.moire.ultrasonic.domain; + +import java.io.Serializable; + +/** + * @author Sindre Mehus + */ +public class PodcastEpisode implements Serializable +{ + + /** + * + */ + private static final long serialVersionUID = -4160515427075433798L; + private String id; + private String title; + private String url; + private String description; + private String status; + + public PodcastEpisode(String id, String title, String url, String description, String status) + { + this.id = id; + this.title = title; + this.url = url; + this.description = description; + this.status = status; + } + + public String getId() + { + return id; + } + + public void setId(String id) + { + this.id = id; + } + + 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; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @Override + public String toString() { + return getTitle(); + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java new file mode 100644 index 00000000..b525c7d6 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java @@ -0,0 +1,94 @@ +/* + 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 . + + Copyright 2009 (C) Sindre Mehus + */ +package org.moire.ultrasonic.domain; + +import java.io.Serializable; + +/** + * @author Sindre Mehus + */ +public class PodcastsChannel implements Serializable +{ + + /** + * + */ + private static final long serialVersionUID = -4160515427075433798L; + private String id; + private String title; + private String url; + private String description; + private String status; + + public PodcastsChannel(String id, String title,String url, String description, String status) + { + this.id = id; + this.title = title; + this.url = url; + this.description = description; + this.status = status; + } + + public String getId() + { + return id; + } + + public void setId(String id) + { + this.id = id; + } + + 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; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @Override + public String toString() { + return getTitle(); + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java index e112084d..06938bb1 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java @@ -30,6 +30,7 @@ import org.moire.ultrasonic.domain.Lyrics; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicFolder; import org.moire.ultrasonic.domain.Playlist; +import org.moire.ultrasonic.domain.PodcastsChannel; import org.moire.ultrasonic.domain.SearchCriteria; import org.moire.ultrasonic.domain.SearchResult; import org.moire.ultrasonic.domain.Share; @@ -66,6 +67,7 @@ public class CachedMusicService implements MusicService private final TimeLimitedCache cachedIndexes = new TimeLimitedCache(60 * 60, TimeUnit.SECONDS); private final TimeLimitedCache cachedArtists = new TimeLimitedCache(60 * 60, TimeUnit.SECONDS); private final TimeLimitedCache> cachedPlaylists = new TimeLimitedCache>(3600, TimeUnit.SECONDS); + private final TimeLimitedCache> cachedPodcastsChannels = new TimeLimitedCache>(3600, TimeUnit.SECONDS); private final TimeLimitedCache> cachedMusicFolders = new TimeLimitedCache>(10 * 3600, TimeUnit.SECONDS); private final TimeLimitedCache> cachedGenres = new TimeLimitedCache>(10 * 3600, TimeUnit.SECONDS); @@ -215,6 +217,24 @@ public class CachedMusicService implements MusicService return musicService.getPlaylist(id, name, context, progressListener); } + @Override + public List getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + List result = refresh ? null : cachedPodcastsChannels.get(); + if (result == null) + { + result = musicService.getPodcastsChannels(refresh, context, progressListener); + cachedPodcastsChannels.set(result); + } + return result; + } + + @Override + public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) throws Exception { + return musicService.getPodcastEpisodes(podcastChannelId,context,progressListener); + } + + @Override public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { @@ -532,4 +552,5 @@ public class CachedMusicService implements MusicService { return musicService.getAvatar(context, username, size, saveToFile, highQuality, progressListener); } + } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java index 1889eb2a..7439efbb 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java @@ -21,6 +21,7 @@ package org.moire.ultrasonic.service; import android.content.Context; import android.graphics.Bitmap; +import org.moire.ultrasonic.activity.SelectAlbumActivity; import org.moire.ultrasonic.domain.Bookmark; import org.moire.ultrasonic.domain.ChatMessage; import org.moire.ultrasonic.domain.Genre; @@ -30,6 +31,8 @@ import org.moire.ultrasonic.domain.Lyrics; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicFolder; import org.moire.ultrasonic.domain.Playlist; +import org.moire.ultrasonic.domain.PodcastEpisode; +import org.moire.ultrasonic.domain.PodcastsChannel; import org.moire.ultrasonic.domain.SearchCriteria; import org.moire.ultrasonic.domain.SearchResult; import org.moire.ultrasonic.domain.Share; @@ -74,6 +77,8 @@ public interface MusicService MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception; + List getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception; + List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception; void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception; @@ -149,4 +154,6 @@ public interface MusicService void updateShare(String id, String description, Long expires, Context context, ProgressListener progressListener) throws Exception; Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception; + + MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) throws Exception; } \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index 8e2f0869..536102b1 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -26,6 +26,7 @@ import android.net.NetworkInfo; import android.util.Log; import org.moire.ultrasonic.R; +import org.moire.ultrasonic.Test.service.GetPodcastEpisodesTestReaderProvider; import org.moire.ultrasonic.domain.Bookmark; import org.moire.ultrasonic.domain.ChatMessage; import org.moire.ultrasonic.domain.Genre; @@ -35,6 +36,8 @@ import org.moire.ultrasonic.domain.Lyrics; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicFolder; import org.moire.ultrasonic.domain.Playlist; +import org.moire.ultrasonic.domain.PodcastEpisode; +import org.moire.ultrasonic.domain.PodcastsChannel; import org.moire.ultrasonic.domain.SearchCriteria; import org.moire.ultrasonic.domain.SearchResult; import org.moire.ultrasonic.domain.ServerInfo; @@ -54,6 +57,8 @@ import org.moire.ultrasonic.service.parser.MusicDirectoryParser; import org.moire.ultrasonic.service.parser.MusicFoldersParser; import org.moire.ultrasonic.service.parser.PlaylistParser; import org.moire.ultrasonic.service.parser.PlaylistsParser; +import org.moire.ultrasonic.service.parser.PodcastEpisodeParser; +import org.moire.ultrasonic.service.parser.PodcastsChannelsParser; import org.moire.ultrasonic.service.parser.RandomSongsParser; import org.moire.ultrasonic.service.parser.SearchResult2Parser; import org.moire.ultrasonic.service.parser.SearchResultParser; @@ -66,6 +71,7 @@ import org.moire.ultrasonic.util.CancellableTask; import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.FileUtil; import org.moire.ultrasonic.util.ProgressListener; +import org.moire.ultrasonic.util.StreamProxy; import org.moire.ultrasonic.util.Util; import org.apache.http.Header; @@ -104,6 +110,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Array; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; @@ -111,6 +119,7 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import static java.util.Arrays.asList; @@ -594,6 +603,43 @@ public class RESTMusicService implements MusicService } } + @Override + public List getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception + { + Reader reader = getReader(context, progressListener, "getPodcasts", null,"includeEpisodes", "false"); + try { + return new PodcastsChannelsParser(context).parse(reader, progressListener); + } + finally + { + Util.close(reader); + } + } + + @Override + public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) throws Exception { + + List names = new ArrayList(); + names.add("id"); + names.add("includeEpisodes"); + List values = new ArrayList(); + values.add(podcastChannelId); + values.add("true"); + + // TODO + Reader reader = getReader(context, progressListener, "getPodcasts", null, names,values); + //Reader reader = GetPodcastEpisodesTestReaderProvider.getReader(); + try { + return new PodcastEpisodeParser(context).parse(reader, progressListener); + } + finally + { + Util.close(reader); + } + } + + + @Override public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { @@ -1741,4 +1787,5 @@ public class RESTMusicService implements MusicService return Util.scaleBitmap(bitmap, size); } } + } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java new file mode 100644 index 00000000..09115940 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java @@ -0,0 +1,143 @@ +/* + 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 . + + Copyright 2009 (C) Sindre Mehus + */ +package org.moire.ultrasonic.service.parser; + +import android.content.Context; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.domain.PodcastEpisode; +import org.moire.ultrasonic.domain.PodcastsChannel; +import org.moire.ultrasonic.util.ProgressListener; +import org.xmlpull.v1.XmlPullParser; + +import java.io.Reader; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * @author Sindre Mehus + */ +public class PodcastEpisodeParser extends AbstractParser +{ + + public PodcastEpisodeParser(Context context) + { + super(context); + } + + public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception + { + + MusicDirectory musicDirectory = new MusicDirectory(); + SortedMap sortedEntries = new TreeMap(); + + Locale currentLocale = getContext().getResources().getConfiguration().locale; + + DateFormat shortDateFormat = DateFormat.getDateTimeInstance( + DateFormat.SHORT, + DateFormat.SHORT, currentLocale); + DateFormat parseDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + + updateProgress(progressListener, R.string.parser_reading); + init(reader); + + int eventType; + do + { + eventType = nextParseEvent(); + if (eventType == XmlPullParser.START_TAG) + { + String tag = getElementName(); + if ("episode".equals(tag)) + { + String status = get("status"); + if (!"skipped".equals(status)) { + MusicDirectory.Entry entry = new MusicDirectory.Entry(); + String streamId = get("streamId"); + entry.setId(streamId); + entry.setIsDirectory(Boolean.parseBoolean(get("isDir"))); + entry.setIsVideo(Boolean.parseBoolean(get("isVideo"))); + entry.setType(get("type")); + entry.setPath(get("path")); + entry.setSuffix(get("suffix")); + String size = get("size"); + if (size != null) { + entry.setSize(Long.parseLong(size)); + } + entry.setCoverArt(get("coverArt")); + entry.setAlbum(get("album")); + entry.setTitle(get("title")); + entry.setAlbumId(get("albumId")); + entry.setArtist(get("artist")); + entry.setArtistId(get("artistId")); + String bitRate = get("bitRate"); + if (bitRate != null) { + entry.setBitRate(Integer.parseInt(get("bitRate"))); + } + entry.setContentType(get("contentType")); + String duration = get("duration"); + if (duration != null) { + entry.setDuration(Long.parseLong(duration)); + } + entry.setGenre(get("genre")); + entry.setParent(get("parent")); + entry.setCreated("created"); + + + String publishDate = get("publishDate"); + if (publishDate != null) { + try { + Date publishDateDate = parseDateFormat.parse(publishDate); + entry.setArtist(shortDateFormat.format(publishDateDate)); + sortedEntries.put(publishDateDate, entry); + } catch (Exception e) { + // nothing to do + } + } + } + } + else if ("error".equals(tag)) + { + handleError(); + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + + validate(); + updateProgress(progressListener, R.string.parser_reading_done); + + for (Date pubDate : sortedEntries.keySet()) { + musicDirectory.addFirst(sortedEntries.get(pubDate)); + } + return musicDirectory; + } +} + diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastsChannelsParser.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastsChannelsParser.java new file mode 100644 index 00000000..8c10a0cb --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastsChannelsParser.java @@ -0,0 +1,81 @@ +/* + 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 . + + Copyright 2009 (C) Sindre Mehus + */ +package org.moire.ultrasonic.service.parser; + +import android.content.Context; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.domain.Playlist; +import org.moire.ultrasonic.domain.PodcastsChannel; +import org.moire.ultrasonic.util.ProgressListener; +import org.moire.ultrasonic.view.PlaylistAdapter; +import org.xmlpull.v1.XmlPullParser; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Sindre Mehus + */ +public class PodcastsChannelsParser extends AbstractParser +{ + + public PodcastsChannelsParser(Context context) + { + super(context); + } + + public List parse(Reader reader, ProgressListener progressListener) throws Exception + { + + updateProgress(progressListener, R.string.parser_reading); + init(reader); + + List result = new ArrayList(); + int eventType; + do + { + eventType = nextParseEvent(); + if (eventType == XmlPullParser.START_TAG) + { + String tag = getElementName(); + if ("channel".equals(tag)) + { + String id = get("id"); + String title = get("title"); + String url = get("url"); + String description = get("description"); + String status = get("status"); + result.add(new PodcastsChannel(id,title, url,description,status)); + } + else if ("error".equals(tag)) + { + handleError(); + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + + validate(); + updateProgress(progressListener, R.string.parser_reading_done); + + return result; + } + +} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java index 32a65b3a..19c4a199 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java @@ -41,6 +41,7 @@ public final class Constants public static final String INTENT_EXTRA_NAME_AUTOPLAY = "subsonic.playall"; public static final String INTENT_EXTRA_NAME_QUERY = "subsonic.query"; public static final String INTENT_EXTRA_NAME_PLAYLIST_ID = "subsonic.playlist.id"; + public static final String INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID = "subsonic.podcastChannel.id"; public static final String INTENT_EXTRA_NAME_PARENT_ID = "subsonic.parent.id"; public static final String INTENT_EXTRA_NAME_PLAYLIST_NAME = "subsonic.playlist.name"; public static final String INTENT_EXTRA_NAME_SHARE_ID = "subsonic.share.id"; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java new file mode 100644 index 00000000..3e480a55 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java @@ -0,0 +1,83 @@ +package org.moire.ultrasonic.view; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.activity.SubsonicTabActivity; +import org.moire.ultrasonic.domain.Playlist; +import org.moire.ultrasonic.domain.PodcastsChannel; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @author Sindre Mehus + */ +public class PodcastsChannelsAdapter extends ArrayAdapter +{ + + //private final SubsonicTabActivity activity; + + public PodcastsChannelsAdapter(Activity activity, List channels) + { + super(activity, R.layout.podcasts_channel_item, channels); + //this.activity = activity; + } + + @Override + public void add(PodcastsChannel object) { + super.add(object); + } +/* @Override + public View getView(int position, View convertView, ViewGroup parent) + { + PodcastsChannel entry = getItem(position); + PlaylistView view; + + if (convertView != null && convertView instanceof PlaylistView) + { + PlaylistView currentView = (PlaylistView) convertView; + + ViewHolder viewHolder = (ViewHolder) convertView.getTag(); + view = currentView; + view.setViewHolder(viewHolder); + } + else + { + view = new PlaylistView(activity); + view.setLayout(); + } + + view.setPlaylist(entry); + return view; + } + */ + + /* public static class PlaylistComparator implements Comparator, Serializable + { + private static final long serialVersionUID = -6201663557439120008L; + + @Override + public int compare(Playlist playlist1, Playlist playlist2) + { + return playlist1.getName().compareToIgnoreCase(playlist2.getName()); + } + + public static List sort(List playlists) + { + Collections.sort(playlists, new PlaylistComparator()); + return playlists; + } + } */ + + /* static class ViewHolder + { + TextView name; + } */ +} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcatsChannelItemView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcatsChannelItemView.java new file mode 100644 index 00000000..89163d86 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcatsChannelItemView.java @@ -0,0 +1,63 @@ +/* + 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 . + + Copyright 2009 (C) Sindre Mehus + */ +package org.moire.ultrasonic.view; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.TextView; + +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.domain.Playlist; + +/** + * Used to display playlists in a {@code ListView}. + * + * @author Sindre Mehus + */ +public class PodcatsChannelItemView extends UpdateView +{ + private Context context; + private PlaylistAdapter.ViewHolder viewHolder; + + public PodcatsChannelItemView(Context context) + { + super(context); + this.context = context; + } + + public void setLayout() + { + LayoutInflater.from(context).inflate(R.layout.playlist_list_item, this, true); + viewHolder = new PlaylistAdapter.ViewHolder(); + viewHolder.name = (TextView) findViewById(R.id.playlist_name); + setTag(viewHolder); + } + + public void setViewHolder(PlaylistAdapter.ViewHolder viewHolder) + { + this.viewHolder = viewHolder; + setTag(this.viewHolder); + } + + public void setPlaylist(Playlist playlist) + { + viewHolder.name.setText(playlist.getName()); + update(); + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/res/drawable-hdpi/ic_menu_podcasts_dark.png b/ultrasonic/src/main/res/drawable-hdpi/ic_menu_podcasts_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..78734e481f4d9d769ed59468ba0eb99173254b0d GIT binary patch literal 752 zcmVN&jN$sJNN?Lf=8eq><6nrMb-rjfqw=c!DX-&%*~^KfJqi}64d2Uz*NS;IZ&Hd z0Ri;v9H9h|R{;S}LBmXnpcGmQ)`2~sk0>+4u@tm}C*W@k5#-Yg%CeTN zO2Jxi6a0xG&_phT9LT_#ID6fW&9cNOi^-OTaT@40>iZ&y;|G3C=DHm2nnvrqbd+ zBvQ4fAs}EBG%I6OgApV4S{4Klzd?souyCy&j2SgzOA-Pu@E#tEnd5`WoKi$=Gm73& zVyTr=>N3y@9`H&>wb$LsSOZ4f?z9RPoyKlOv=H=NL5Qb%@=1vqPP8@%LU!_T(`u<# z3b6+b(VNn4AsV>AX(9&0t4h5Pc$Zp6iXmdTF`-FiD|IUV-?WN3R=K0)Y+OHTY^Ci&X1lBSo_96*a&TL@Nr&KHVN~4vg8x3n z_ob858{a_d?deFlLWtRG94o7ZXGim`aZ$V;Mii`0A#NV{8h3AVS1b8$aFOSqM+oRq zVy`RyX9Qnuky>4Bp(_yr3QF7o#s8k*+pqX`xe!3FQhq5npp3LYiEC0~UW6Y`d_JN0 zo^_Rp3PQeqL;a8uQ?E?weYoSq=fjHcC6~3JhBHzZ*b((6nkp%`)IA}Nh2IPDuY6iy im;Z;xn>TOX61D$XTn5O@M!#JE0000w1^ literal 0 HcmV?d00001 diff --git a/ultrasonic/src/main/res/drawable-hdpi/ic_menu_podcasts_light.png b/ultrasonic/src/main/res/drawable-hdpi/ic_menu_podcasts_light.png new file mode 100644 index 0000000000000000000000000000000000000000..c3b1ae6ef720fff278fed55448d208d7ca82bab0 GIT binary patch literal 846 zcmV-U1F`&xP)<{ZQZ+45jn=H(O$B@}M-Z$@@Nl%XEEEdEu=86GY!SS^8M6X6 zx+xT=s@3X2oa`6`dkNkM0Ad3?4cgju(TduEaE}85dC;x-gFbT#{DNWC2gsvlv$-t} zGZ}&~XP9*}narLbHcY`VX9B$D1L9u0-QE)Qi7vSk)f#@k2hV^l+qQS=CIkFQmLIz0 zhwqcI01#~XDcq%70%f!Wn6rFsxYHbNn@eh-ViRwG`0OYHaeov!iaROWC->8jx;um`iPl$ z-X~#Acqj3J3*Wv6wQZ@q0D#kB0C??FI^0lA1h9v~_?rri%?kjA|J#K4Y^VdE3Xi%( zh5jcBGi?dloeIMT2Bqf{CnEI;x3M8XsJGrwj6JRhhjTluOlz|w@NDoA4tQLe17|%f zU0%EuC)U3xjGYNB7ce3?5owC`kKao)HN%qH%Mk3c_g#oD8T2JDW50^|hsKa0Lxv)y Yzge50w^`92Pyhe`07*qoM6N<$g3!=>?EnA( literal 0 HcmV?d00001 diff --git a/ultrasonic/src/main/res/drawable-mdpi/ic_menu_podcasts_dark.png b/ultrasonic/src/main/res/drawable-mdpi/ic_menu_podcasts_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3e8e06de2266c3aa7f081ba82b69925c8a321b4c GIT binary patch literal 513 zcmV+c0{;DpP)`kg;s(rufLjI>#~|jkff^aaRxkt>z#;f3Vd5Rki1R9)jW&=0 zPbG}30izlrZIPYvr7@8$yL~Z1NDTUJ*9kxCd)SD2#D8pr*bF3H+85COel3~c_AdXt@JYTC2YiPQ^2 ztMba}0-_5Nq^=&EvYHJi+iwuuvjxQ6iISR8roV!)lP3DW3tME1=m8m2s!!Nis0qx0 z6|oO6Ey)%T;p-rJn(4cC7C&c`B#Caw*D}?Yh#u3tIsH})OluNfpp)QuoarY-^UDt! z+O|x#vAL1Fwu|L`Ee@-Vwz25Pg8fsYI=ya&9sEo93;R6cmO+~SyOW$ z?}8#uv~{@=DJJc7f(Tpi4`0s;gS8{o>b(CO-q4L72d_vHYFB&I00000NkvXXu0mjf DfRo@6 literal 0 HcmV?d00001 diff --git a/ultrasonic/src/main/res/drawable-mdpi/ic_menu_podcasts_light.png b/ultrasonic/src/main/res/drawable-mdpi/ic_menu_podcasts_light.png new file mode 100644 index 0000000000000000000000000000000000000000..04b43f5bf070dceffd7cce82145b4775fee7bcd3 GIT binary patch literal 601 zcmV-f0;c_mP)s6gmSMkSWkzwH~?jl*!1 zR@dyo10TC*&+N=Mb7tn8wzk%2jsG|Tfq>QFaCGC<(i8%Izkd@b0xuwQ6$}RV0oCnx z5B~`P7)t?PGH8TUMNxX2LO>w$46M+0HHCnHTqqQpGMj^iJOhS29*+yTu#Wc&pj;su zC5lGFla`VY2GcNBs6b>Vm&>&`JW0J??;uqMBJ(LBlC3)uKA$g6dkc@wU?d65s;b&4 zDc6838X&k)ZxW^@;eEt24kA6QX^rFilPF7(35UZTQwaz}9w;$IdCbSCX)(FH)9LIt zoq+Bko@?S>!{CghO0qQ(h(sa-2;n65avn+!s$_kFZAXHaC-P~u?yRR!!s~7BAw>cP z6kX2ca&K*;VX68xhAf?R9Lw%jfl@gVaT&$(* z*`Rn8eSVAj0Nd*rGf)Ob1d>U%eqz`-e!OE!V){C@Tgz$my0y1-sg2?|N9WE7wT{1pg;_uw`-0#>A&0CNd4432@N zsVKl~=+(Ilsz7Ec3J~EB7yuhmRe%T~&MssItFq8tX6kLqU2^MmzyD~ z`MW?1=m#&ruXzgb1?&dtiI^mFsP2RP+y_iXDFTVu4i+SG$xNr?%=LdPN+Ehl)MRL& zGC@5zL}8C2+@jVxSvtuao)_MFOpbR!Ub3~+z1#Ujtd;U%aRo{`I4 zq-LaUNHhV6pWu+~mEdagToG#`n(7m80f=cXp;?w^tp#5dvD#ubIB}wC;BKxJTmj=z zw6~FxcFwjv=AkO2s5r~w-5}9jK{FrQkVg?Zh0%LZ5$Al6di^>cdO{vvhbGf8#?MzN z#_CU?JZMADQ7(VlZSi``SlyFD`0A4Fc-V8&G9z|nR7|Sn3eL*=lEoIVmIZbhj z#7NE0N`wDqp}K@)GAVLqt_4rHwWWvu0FS^9!M9U!3fe8n@Ri`(5sQ0))0V$Pn42Qg zFlZanRd{j|0Sxh#M&;1Ic>=U*Mw)c`XxY? zhs)0opg<5)YHX?uzGno#i+<*SL8SowidfHt7>x$M1JZSxp8{-BY-*-V)7^iu`h+;g z4Sw~4-;m#>u#p=d)6TC!v!U&^KrL}TPkVV@my_&MYN5cAj7xd`n2VA$XU?2CbLPyMGiS2>1H}|$1a?GE QSpWb407*qoM6N<$g7yZ*?f?J) literal 0 HcmV?d00001 diff --git a/ultrasonic/src/main/res/drawable-xhdpi/ic_menu_podcasts_light.png b/ultrasonic/src/main/res/drawable-xhdpi/ic_menu_podcasts_light.png new file mode 100644 index 0000000000000000000000000000000000000000..a4d249146bd035a602cf6a502ab67ccffe266502 GIT binary patch literal 1131 zcmV-x1eE)UP)-X|uWpr|!1Z7JORp}m<*W_dMd%@EC*`5*eB6d(X*KS8aD8bY{A zR`|LbAf5ogbv3~x`8M%Dr=U}Af+GNU&bU)kMHS6k@BmXwcvSH;$tn+vcuBJ)r9~#_ z#ppl|pfq0N_IjYiQ5H@vtl>^Y10z$PgvjKvxM_@!Y2p|+nWiyj4a|RM5*uhSOu4=T zKvwjaa(!W0Lx!v|56d~?YXBDM2wg;H5%h1&y=w@sb*$O8HDw>^Ob3OD!=wu1>wov}wQ2B8(ne5P!C1xCCg>4ljNbKl{ zl6cGw7=q^ZY4#z84X$(3H(a0W&P~xH$c-a2<5ViOi*H%iQQ~HeuZkX+?**nmLu;7D z{JpZWntpC@jc(r+Jq%=6SkBNLva*QI-}#nxUgXHTT06_R?Qu4|IqOMk8X|1E%ODZHX;>|R0)NPl;9S&peyyhY1 z@DMv?=c}>{kCm%Z4B=)GEOu0MaC!3|`2HH7PiQzppFvRURRlw2gOv=k91nj*_9jFy z>ynu<8=MAR;wf1V5XWw7TaG}o0Sy3`BPDs)JE6g|JVNt=q1zLZSB z6sZAVq8weI25x>ZB8xvwa=eE(zuN_n>>|x8G$XlpBMT>mj+d((2r*K#&=C>aRdcfN xPZOESf+&Nr>O2P6IxAMJSg~ToiWMt*`3oy7oLXycyPp66002ovPDHLkV1i2Z0aO40 literal 0 HcmV?d00001 diff --git a/ultrasonic/src/main/res/drawable-xxhdpi/ic_menu_podcasts_dark.png b/ultrasonic/src/main/res/drawable-xxhdpi/ic_menu_podcasts_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..79dcc4b666c378b6e8a8fd3bdb72f400d803325b GIT binary patch literal 1641 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 literal 0 HcmV?d00001 diff --git a/ultrasonic/src/main/res/layout/menu_main.xml b/ultrasonic/src/main/res/layout/menu_main.xml index 391feb57..481ecd8e 100644 --- a/ultrasonic/src/main/res/layout/menu_main.xml +++ b/ultrasonic/src/main/res/layout/menu_main.xml @@ -81,6 +81,14 @@ android:drawableLeft="?attr/media_play" android:text="@string/button_bar.now_playing"/> + + + + + + + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/podcasts_channel_item.xml b/ultrasonic/src/main/res/layout/podcasts_channel_item.xml new file mode 100644 index 00000000..9b8560e8 --- /dev/null +++ b/ultrasonic/src/main/res/layout/podcasts_channel_item.xml @@ -0,0 +1,9 @@ + + + + diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 9a142f56..9249b973 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -12,6 +12,9 @@ Chat UltraSonic Main Now Playing + Podcast + No podcasts channels registered + Podcast Playlists Search Send a message diff --git a/ultrasonic/src/main/res/values/styles.xml b/ultrasonic/src/main/res/values/styles.xml index 4c10b38e..f8589503 100644 --- a/ultrasonic/src/main/res/values/styles.xml +++ b/ultrasonic/src/main/res/values/styles.xml @@ -80,6 +80,7 @@ + diff --git a/ultrasonic/src/main/res/values/themes.xml b/ultrasonic/src/main/res/values/themes.xml index f6302ced..0c3dae13 100644 --- a/ultrasonic/src/main/res/values/themes.xml +++ b/ultrasonic/src/main/res/values/themes.xml @@ -35,6 +35,7 @@ @drawable/media_repeat_single @drawable/media_shuffle_normal_dark @drawable/media_start_normal_dark + @drawable/ic_menu_podcasts_dark @drawable/media_play_next @drawable/ic_stat_play_dark @drawable/media_stop_normal_dark @@ -79,6 +80,7 @@ @drawable/media_repeat_single @drawable/media_shuffle_normal_light @drawable/media_start_normal_light + @drawable/ic_menu_podcasts_light @drawable/media_play_next @drawable/ic_stat_play_light @drawable/media_stop_normal_light From a9d96227e4368dc0cf7a9fb13cd2a69e139e7bba Mon Sep 17 00:00:00 2001 From: biconou Date: Wed, 23 Mar 2016 21:54:44 +0100 Subject: [PATCH 2/2] bug fix : if a podcast episode is in status "error" it can not be added in the episodes list. Application crashes. (cherry picked from commit 3fedd79) --- .../moire/ultrasonic/service/parser/PodcastEpisodeParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java index 09115940..5b4c56c3 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java @@ -79,7 +79,7 @@ public class PodcastEpisodeParser extends AbstractParser if ("episode".equals(tag)) { String status = get("status"); - if (!"skipped".equals(status)) { + if (!"skipped".equals(status) && !"error".equals(status)) { MusicDirectory.Entry entry = new MusicDirectory.Entry(); String streamId = get("streamId"); entry.setId(streamId);