/* 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 2015 (C) Scott Jackson */ package github.daneren2005.dsub.fragments; import android.content.res.Resources; import android.support.v7.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.v7.widget.GridLayoutManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import github.daneren2005.dsub.R; import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PodcastChannel; import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.ServerInfo; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.service.OfflineException; import github.daneren2005.dsub.service.ServerTooOldException; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.SyncUtil; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.LoadingTask; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.UserUtil; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.adapter.PodcastChannelAdapter; import github.daneren2005.dsub.view.UpdateView; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class SelectPodcastsFragment extends SelectRecyclerFragment<Serializable> { private static final String TAG = SelectPodcastsFragment.class.getSimpleName(); private boolean hasCoverArt; private MusicDirectory newestEpisodes; @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); hasCoverArt = ServerInfo.checkServerVersion(context, "1.13") || Util.isOffline(context); if (Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true) && hasCoverArt) { largeAlbums = true; } if(bundle != null && serialize) { newestEpisodes = (MusicDirectory) bundle.getSerializable(Constants.FRAGMENT_LIST2); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if(serialize) { outState.putSerializable(Constants.FRAGMENT_LIST2, newestEpisodes); } } @Override public boolean onOptionsItemSelected(MenuItem item) { if(super.onOptionsItemSelected(item)) { return true; } switch (item.getItemId()) { case R.id.menu_check: refreshPodcasts(); break; case R.id.menu_add_podcast: addNewPodcast(); break; } return false; } @Override public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Serializable> updateView, Serializable item) { if(item instanceof PodcastChannel) { PodcastChannel channel = (PodcastChannel) item; if (!Util.isOffline(context) && UserUtil.canPodcast()) { menuInflater.inflate(R.menu.select_podcasts_context, menu); if (SyncUtil.isSyncedPodcast(context, channel.getId())) { menu.removeItem(R.id.podcast_menu_sync); } else { menu.removeItem(R.id.podcast_menu_stop_sync); } } else { menuInflater.inflate(R.menu.select_podcasts_context_offline, menu); } } else { onCreateContextMenuSupport(menu, menuInflater, updateView, item); } recreateContextMenu(menu); } @Override public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Serializable> updateView, Serializable item) { if(item instanceof PodcastChannel) { PodcastChannel channel = (PodcastChannel) item; switch (menuItem.getItemId()) { case R.id.podcast_menu_sync: syncPodcast(channel); break; case R.id.podcast_menu_stop_sync: stopSyncPodcast(channel); break; case R.id.podcast_channel_info: displayPodcastInfo(channel); break; case R.id.podcast_channel_delete: deletePodcast(channel); break; } } else { return onContextItemSelected(menuItem, item); } return true; } @Override public int getOptionsMenu() { return (UserUtil.canPodcast() && !Util.isOffline(context)) ? R.menu.select_podcasts : R.menu.abstract_top_menu; } @Override public SectionAdapter getAdapter(List<Serializable> channels) { if(newestEpisodes == null || newestEpisodes.getChildrenSize() == 0) { return new PodcastChannelAdapter(context, channels, hasCoverArt ? getImageLoader() : null, this, largeAlbums); } else { Resources res = context.getResources(); List<String> headers = Arrays.asList(res.getString(R.string.main_albums_newest), res.getString(R.string.select_podcasts_channels)); List<MusicDirectory.Entry> episodes = newestEpisodes.getChildren(false, true); List<Serializable> serializableEpisodes = new ArrayList<>(); serializableEpisodes.addAll(episodes); List<List<Serializable>> sections = new ArrayList<>(); sections.add(serializableEpisodes); sections.add(channels); return new PodcastChannelAdapter(context, headers, sections, ServerInfo.checkServerVersion(context, "1.13") ? getImageLoader() : null, this, largeAlbums); } } @Override public List<Serializable> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception { List<PodcastChannel> channels = musicService.getPodcastChannels(refresh, context, listener); if(!Util.isOffline(context) && ServerInfo.hasNewestPodcastEpisodes(context)) { try { newestEpisodes = musicService.getNewestPodcastEpisodes(refresh, context, listener, 10); for(MusicDirectory.Entry entry: newestEpisodes.getChildren()) { for(PodcastChannel channel: channels) { if(channel.getId().equals(entry.getParent())) { PodcastEpisode episode = (PodcastEpisode) entry; // Update with information normally done in PodcastEntryParser episode.setArtist(channel.getName()); episode.setCoverArt(channel.getCoverArt()); episode.setPath(FileUtil.getPodcastPath(context, episode)); break; } } } } catch (Exception e) { Log.e(TAG, "Failed to download newest episodes", e); newestEpisodes = null; } } else { newestEpisodes = null; } List<Serializable> serializableList = new ArrayList<>(); serializableList.addAll(channels); return serializableList; } @Override public int getTitleResource() { return R.string.button_bar_podcasts; } @Override public void onItemClicked(UpdateView<Serializable> updateView, Serializable item) { if(item instanceof PodcastChannel) { PodcastChannel channel = (PodcastChannel) item; if ("error".equals(channel.getStatus())) { Util.toast(context, context.getResources().getString(R.string.select_podcasts_invalid_podcast_channel, channel.getErrorMessage() == null ? "error" : channel.getErrorMessage())); } else if ("downloading".equals(channel.getStatus())) { Util.toast(context, R.string.select_podcasts_initializing); } else { 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); } } else { PodcastEpisode episode = (PodcastEpisode) item; String status = episode.getStatus(); if("error".equals(status)) { Util.toast(context, R.string.select_podcasts_error); return; } else if(!"completed".equals(status)) { Util.toast(context, R.string.select_podcasts_skipped); return; } onSongPress(Arrays.asList((MusicDirectory.Entry) episode), episode, false); } } @Override public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) { return new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { SectionAdapter adapter = getCurrentAdapter(); if(adapter != null) { int viewType = getCurrentAdapter().getItemViewType(position); if (viewType == SectionAdapter.VIEW_TYPE_HEADER || viewType == PodcastChannelAdapter.VIEW_TYPE_PODCAST_EPISODE || viewType == PodcastChannelAdapter.VIEW_TYPE_PODCAST_LEGACY) { return gridLayoutManager.getSpanCount(); } else { return 1; } } else { return 1; } } }; } @Override public void onFinishRefresh() { Bundle args = getArguments(); if(args != null) { String podcastId = args.getString(Constants.INTENT_EXTRA_NAME_ID, null); if (podcastId != null && objects != null) { for (Serializable ser : objects) { if (ser instanceof PodcastChannel) { PodcastChannel podcast = (PodcastChannel) ser; if (podcastId.equals(podcast.getId())) { onItemClicked(null, podcast); break; } } } } } } public void refreshPodcasts() { new SilentBackgroundTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); musicService.refreshPodcasts(context, null); return null; } @Override protected void done(Void result) { Util.toast(context, R.string.select_podcasts_refreshing); } @Override protected void error(Throwable error) { Util.toast(context, getErrorMessage(error), false); } }.execute(); } private void addNewPodcast() { View dialogView = context.getLayoutInflater().inflate(R.layout.create_podcast, null); final TextView urlBox = (TextView) dialogView.findViewById(R.id.create_podcast_url); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.menu_add_podcast) .setView(dialogView) .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { addNewPodcast(urlBox.getText().toString()); } }) .setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }) .setCancelable(true); AlertDialog dialog = builder.create(); dialog.show(); } private void addNewPodcast(final String url) { new LoadingTask<Void>(context, false) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); musicService.createPodcastChannel(url, context, null); return null; } @Override protected void done(Void result) { refresh(); } @Override protected void error(Throwable error) { String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.select_podcasts_created_error) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } private void displayPodcastInfo(final PodcastChannel channel) { List<Integer> headers = new ArrayList<>(); List<String> details = new ArrayList<>(); if(channel.getName() != null) { headers.add(R.string.details_title); details.add(channel.getName()); } headers.add(R.string.details_url); details.add(channel.getUrl()); headers.add(R.string.details_status); details.add(channel.getStatus()); if(channel.getErrorMessage() != null) { headers.add(R.string.details_error); details.add(channel.getErrorMessage()); } if(channel.getDescription() != null) { headers.add(R.string.details_description); details.add(channel.getDescription()); } Util.showDetailsDialog(context, R.string.details_title_podcast, headers, details); } private void deletePodcast(final PodcastChannel channel) { Util.confirmDialog(context, R.string.common_delete, channel.getName(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new LoadingTask<Void>(context, false) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); musicService.deletePodcastChannel(channel.getId(), context, null); stopSyncPodcast(channel); return null; } @Override protected void done(Void result) { adapter.removeItem(channel); Util.toast(context, context.getResources().getString(R.string.select_podcasts_deleted, channel.getName())); } @Override protected void error(Throwable error) { String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.select_podcasts_deleted_error, channel.getName()) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } }); } private void syncPodcast(final PodcastChannel podcast) { new LoadingTask<MusicDirectory>(context, false) { @Override protected MusicDirectory doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); return musicService.getPodcastEpisodes(true, podcast.getId(), context, this); } @Override protected void done(MusicDirectory result) { List<String> existingEpisodes = new ArrayList<String>(); for(MusicDirectory.Entry entry: result.getChildren()) { String id = entry.getId(); if(id != null) { existingEpisodes.add(entry.getId()); } } SyncUtil.addSyncedPodcast(context, podcast.getId(), existingEpisodes); } }.execute(); } private void stopSyncPodcast(PodcastChannel podcast) { SyncUtil.removeSyncedPodcast(context, podcast.getId()); } }