package github.daneren2005.dsub.fragments; import android.annotation.TargetApi; import android.support.v7.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Html; import android.text.SpannableString; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.Display; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RatingBar; import android.widget.RelativeLayout; import android.widget.TextView; import github.daneren2005.dsub.R; import github.daneren2005.dsub.adapter.AlphabeticalAlbumAdapter; import github.daneren2005.dsub.adapter.EntryInfiniteGridAdapter; import github.daneren2005.dsub.adapter.EntryGridAdapter; import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.adapter.TopRatedAlbumAdapter; import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.ServerInfo; import github.daneren2005.dsub.domain.Share; import github.daneren2005.dsub.service.CachedMusicService; import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.util.DrawableTint; import github.daneren2005.dsub.util.ImageLoader; import java.io.Serializable; import java.util.Iterator; import java.util.List; import github.daneren2005.dsub.domain.PodcastEpisode; 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.Constants; import github.daneren2005.dsub.util.LoadingTask; import github.daneren2005.dsub.util.Pair; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.TabBackgroundTask; import github.daneren2005.dsub.util.UpdateHelper; import github.daneren2005.dsub.util.UserUtil; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.view.FastScroller; import github.daneren2005.dsub.view.GridSpacingDecoration; import github.daneren2005.dsub.view.MyLeadingMarginSpan2; import github.daneren2005.dsub.view.RecyclingImageView; import github.daneren2005.dsub.view.UpdateView; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static github.daneren2005.dsub.domain.MusicDirectory.Entry; public class SelectDirectoryFragment extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<Entry> { private static final String TAG = SelectDirectoryFragment.class.getSimpleName(); private RecyclerView recyclerView; private FastScroller fastScroller; private EntryGridAdapter entryGridAdapter; private Boolean licenseValid; private List<Entry> albums; private List<Entry> entries; private LoadTask currentTask; private ArtistInfo artistInfo; private String artistInfoDelayed; private SilentBackgroundTask updateCoverArtTask; private ImageView coverArtView; private Entry coverArtRep; private String coverArtId; String id; String name; Entry directory; String playlistId; String playlistName; boolean playlistOwner; String podcastId; String podcastName; String podcastDescription; String albumListType; String albumListExtra; int albumListSize; boolean refreshListing = false; boolean showAll = false; boolean restoredInstance = false; boolean lookupParent = false; boolean largeAlbums = false; boolean topTracks = false; String lookupEntry; public SelectDirectoryFragment() { super(); } @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); if(bundle != null) { entries = (List<Entry>) bundle.getSerializable(Constants.FRAGMENT_LIST); albums = (List<Entry>) bundle.getSerializable(Constants.FRAGMENT_LIST2); if(albums == null) { albums = new ArrayList<>(); } artistInfo = (ArtistInfo) bundle.getSerializable(Constants.FRAGMENT_EXTRA); restoredInstance = true; } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable(Constants.FRAGMENT_LIST, (Serializable) entries); outState.putSerializable(Constants.FRAGMENT_LIST2, (Serializable) albums); outState.putSerializable(Constants.FRAGMENT_EXTRA, (Serializable) artistInfo); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { Bundle args = getArguments(); if(args != null) { id = args.getString(Constants.INTENT_EXTRA_NAME_ID); name = args.getString(Constants.INTENT_EXTRA_NAME_NAME); directory = (Entry) args.getSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY); playlistId = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); playlistName = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); playlistOwner = args.getBoolean(Constants.INTENT_EXTRA_NAME_PLAYLIST_OWNER, false); 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); Object shareObj = args.getSerializable(Constants.INTENT_EXTRA_NAME_SHARE); share = (shareObj != null) ? (Share) shareObj : null; 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); refreshListing = args.getBoolean(Constants.INTENT_EXTRA_REFRESH_LISTINGS); artist = args.getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false); lookupEntry = args.getString(Constants.INTENT_EXTRA_SEARCH_SONG); topTracks = args.getBoolean(Constants.INTENT_EXTRA_TOP_TRACKS); showAll = args.getBoolean(Constants.INTENT_EXTRA_SHOW_ALL); String childId = args.getString(Constants.INTENT_EXTRA_NAME_CHILD_ID); if(childId != null) { id = childId; lookupParent = true; } if(entries == null) { entries = (List<Entry>) args.getSerializable(Constants.FRAGMENT_LIST); albums = (List<Entry>) args.getSerializable(Constants.FRAGMENT_LIST2); if(albums == null) { albums = new ArrayList<Entry>(); } } } rootView = inflater.inflate(R.layout.abstract_recycler_fragment, container, false); refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout); refreshLayout.setOnRefreshListener(this); if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true)) { largeAlbums = true; } recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler); recyclerView.setHasFixedSize(true); fastScroller = (FastScroller) rootView.findViewById(R.id.fragment_fast_scroller); setupScrollList(recyclerView); setupLayoutManager(recyclerView, largeAlbums); if(entries == null) { if(primaryFragment || secondaryFragment) { load(false); } else { invalidated = true; } } else { licenseValid = true; finishLoading(); } if(name != null) { setTitle(name); } return rootView; } @Override public void setIsOnlyVisible(boolean isOnlyVisible) { boolean update = this.isOnlyVisible != isOnlyVisible; super.setIsOnlyVisible(isOnlyVisible); if(update && entryGridAdapter != null) { RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if(layoutManager instanceof GridLayoutManager) { ((GridLayoutManager) layoutManager).setSpanCount(getRecyclerColumnCount()); } } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { if(licenseValid == null) { menuInflater.inflate(R.menu.empty, menu); } else if(albumListType != null && !"starred".equals(albumListType)) { menuInflater.inflate(R.menu.select_album_list, menu); } else if(artist && !showAll) { menuInflater.inflate(R.menu.select_album, menu); if(!ServerInfo.hasTopSongs(context)) { menu.removeItem(R.id.menu_top_tracks); } if(!ServerInfo.checkServerVersion(context, "1.11")) { menu.removeItem(R.id.menu_radio); menu.removeItem(R.id.menu_similar_artists); } else if(!ServerInfo.hasSimilarArtists(context)) { menu.removeItem(R.id.menu_similar_artists); } } else { if(podcastId == null) { if(Util.isOffline(context)) { menuInflater.inflate(R.menu.select_song_offline, menu); } else { menuInflater.inflate(R.menu.select_song, menu); if(playlistId == null || !playlistOwner) { menu.removeItem(R.id.menu_remove_playlist); } } SharedPreferences prefs = Util.getPreferences(context); if(!prefs.getBoolean(Constants.PREFERENCES_KEY_MENU_PLAY_NEXT, true)) { menu.setGroupVisible(R.id.hide_play_next, false); } if(!prefs.getBoolean(Constants.PREFERENCES_KEY_MENU_PLAY_LAST, true)) { menu.setGroupVisible(R.id.hide_play_last, false); } } else { if(Util.isOffline(context)) { menuInflater.inflate(R.menu.select_podcast_episode_offline, menu); } else { menuInflater.inflate(R.menu.select_podcast_episode, menu); if(!UserUtil.canPodcast()) { menu.removeItem(R.id.menu_download_all); } } } } if("starred".equals(albumListType)) { menuInflater.inflate(R.menu.unstar, menu); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_remove_playlist: removeFromPlaylist(playlistId, playlistName, getSelectedIndexes()); return true; case R.id.menu_download_all: downloadAllPodcastEpisodes(); return true; case R.id.menu_show_all: setShowAll(); return true; case R.id.menu_top_tracks: showTopTracks(); return true; case R.id.menu_similar_artists: showSimilarArtists(id); return true; case R.id.menu_radio: startArtistRadio(id); return true; } return super.onOptionsItemSelected(item); } @Override public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView updateView, Entry entry) { onCreateContextMenuSupport(menu, menuInflater, updateView, entry); if(!entry.isVideo() && !Util.isOffline(context) && (playlistId == null || !playlistOwner) && (podcastId == null || Util.isOffline(context) && podcastId != null)) { menu.removeItem(R.id.song_menu_remove_playlist); } recreateContextMenu(menu); } @Override public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Entry> updateView, Entry entry) { if(onContextItemSelected(menuItem, entry)) { return true; } switch (menuItem.getItemId()) { case R.id.song_menu_remove_playlist: removeFromPlaylist(playlistId, playlistName, Arrays.<Integer>asList(entries.indexOf(entry))); break; } return true; } @Override public void onItemClicked(UpdateView<Entry> updateView, Entry entry) { if (entry.isDirectory()) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getId()); args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getTitle()); args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, entry); if ("newest".equals(albumListType)) { args.putBoolean(Constants.INTENT_EXTRA_REFRESH_LISTINGS, true); } if(!entry.isAlbum()) { args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true); } fragment.setArguments(args); replaceFragment(fragment, true); } else if (entry.isVideo()) { playVideo(entry); } else if(entry instanceof PodcastEpisode) { String status = ((PodcastEpisode)entry).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(entry), entry, false); } else { onSongPress(entries, entry, albumListType == null || "starred".equals(albumListType)); } } @Override protected void refresh(boolean refresh) { load(refresh); } @Override protected boolean isShowArtistEnabled() { return albumListType != null; } private void load(boolean refresh) { if(refreshListing) { refresh = true; } if(currentTask != null) { currentTask.cancel(); } recyclerView.setVisibility(View.INVISIBLE); if (playlistId != null) { getPlaylist(playlistId, playlistName, refresh); } else if(podcastId != null) { getPodcast(podcastId, podcastName, refresh); } else if (share != null) { if(showAll) { getRecursiveMusicDirectory(share.getId(), share.getName(), refresh); } else { getShare(share, refresh); } } else if (albumListType != null) { getAlbumList(albumListType, albumListSize, refresh); } else { if(showAll) { getRecursiveMusicDirectory(id, name, refresh); } else if(topTracks) { getTopTracks(id, name, refresh); } else { getMusicDirectory(id, name, refresh); } } } private void getMusicDirectory(final String id, final String name, final boolean refresh) { setTitle(name); new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { MusicDirectory dir = getMusicDirectory(id, name, refresh, service, this); if(lookupParent && dir.getParent() != null) { dir = getMusicDirectory(dir.getParent(), name, refresh, service, this); // Update the fragment pointers so other stuff works correctly SelectDirectoryFragment.this.id = dir.getId(); SelectDirectoryFragment.this.name = dir.getName(); } else if(id != null && directory == null && dir.getParent() != null && !artist) { // View Album, try to lookup parent to get a complete entry to use for starring MusicDirectory parentDir = getMusicDirectory(dir.getParent(), name, refresh, true, service, this); for(Entry child: parentDir.getChildren()) { if(id.equals(child.getId())) { directory = child; break; } } } return dir; } @Override protected void done(Pair<MusicDirectory, Boolean> result) { SelectDirectoryFragment.this.name = result.getFirst().getName(); setTitle(SelectDirectoryFragment.this.name); super.done(result); } }.execute(); } private void getRecursiveMusicDirectory(final String id, final String name, final boolean refresh) { setTitle(name); new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { MusicDirectory root; if(share == null) { root = getMusicDirectory(id, name, refresh, service, this); } else { root = share.getMusicDirectory(); } List<Entry> songs = new ArrayList<Entry>(); getSongsRecursively(root, songs); // CachedMusicService is refreshing this data in the background, so will wipe out the songs list from root MusicDirectory clonedRoot = new MusicDirectory(songs); clonedRoot.setId(root.getId()); clonedRoot.setName(root.getName()); return clonedRoot; } private void getSongsRecursively(MusicDirectory parent, List<Entry> songs) throws Exception { songs.addAll(parent.getChildren(false, true)); for (Entry dir : parent.getChildren(true, false)) { MusicService musicService = MusicServiceFactory.getMusicService(context); MusicDirectory musicDirectory; if(Util.isTagBrowsing(context) && !Util.isOffline(context)) { musicDirectory = musicService.getAlbum(dir.getId(), dir.getTitle(), false, context, this); } else { musicDirectory = musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, context, this); } getSongsRecursively(musicDirectory, songs); } } @Override protected void done(Pair<MusicDirectory, Boolean> result) { SelectDirectoryFragment.this.name = result.getFirst().getName(); setTitle(SelectDirectoryFragment.this.name); super.done(result); } }.execute(); } private void getPlaylist(final String playlistId, final String playlistName, final boolean refresh) { setTitle(playlistName); new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return service.getPlaylist(refresh, playlistId, playlistName, context, this); } }.execute(); } private void getPodcast(final String podcastId, final String podcastName, final boolean refresh) { setTitle(podcastName); new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return service.getPodcastEpisodes(refresh, podcastId, context, this); } }.execute(); } private void getShare(final Share share, final boolean refresh) { setTitle(share.getName()); new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return share.getMusicDirectory(); } }.execute(); } private void getTopTracks(final String id, final String name, final boolean refresh) { setTitle(name); new LoadTask(refresh) { @Override protected MusicDirectory load(MusicService service) throws Exception { return service.getTopTrackSongs(name, 50, context, this); } }.execute(); } private void getAlbumList(final String albumListType, final int size, final boolean refresh) { if ("newest".equals(albumListType)) { setTitle(R.string.main_albums_newest); } else if ("random".equals(albumListType)) { setTitle(R.string.main_albums_random); } else if ("highest".equals(albumListType)) { setTitle(R.string.main_albums_highest); } else if ("recent".equals(albumListType)) { setTitle(R.string.main_albums_recent); } else if ("frequent".equals(albumListType)) { setTitle(R.string.main_albums_frequent); } else if ("starred".equals(albumListType)) { setTitle(R.string.main_albums_starred); } else if("genres".equals(albumListType) || "years".equals(albumListType)) { setTitle(albumListExtra); } else if("alphabeticalByName".equals(albumListType)) { setTitle(R.string.main_albums_alphabetical); } if (MainFragment.SONGS_NEWEST.equals(albumListType)) { setTitle(R.string.main_songs_newest); } else if (MainFragment.SONGS_TOP_PLAYED.equals(albumListType)) { setTitle(R.string.main_songs_top_played); } else if (MainFragment.SONGS_RECENT.equals(albumListType)) { setTitle(R.string.main_songs_recent); } else if (MainFragment.SONGS_FREQUENT.equals(albumListType)) { setTitle(R.string.main_songs_frequent); } new LoadTask(true) { @Override protected MusicDirectory load(MusicService service) throws Exception { MusicDirectory result; if ("starred".equals(albumListType)) { result = service.getStarredList(context, this); } else if(("genres".equals(albumListType) && ServerInfo.checkServerVersion(context, "1.10.0")) || "years".equals(albumListType)) { result = service.getAlbumList(albumListType, albumListExtra, size, 0, refresh, context, this); if(result.getChildrenSize() == 0 && "genres".equals(albumListType)) { SelectDirectoryFragment.this.albumListType = "genres-songs"; result = service.getSongsByGenre(albumListExtra, size, 0, context, this); } } else if("genres".equals(albumListType) || "genres-songs".equals(albumListType)) { result = service.getSongsByGenre(albumListExtra, size, 0, context, this); } else if(albumListType.indexOf(MainFragment.SONGS_LIST_PREFIX) != -1) { result = service.getSongList(albumListType, size, 0, context, this); } else { result = service.getAlbumList(albumListType, size, 0, refresh, context, this); } return result; } }.execute(); } private abstract class LoadTask extends TabBackgroundTask<Pair<MusicDirectory, Boolean>> { private boolean refresh; public LoadTask(boolean refresh) { super(SelectDirectoryFragment.this); this.refresh = refresh; currentTask = this; } protected abstract MusicDirectory load(MusicService service) throws Exception; @Override protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); MusicDirectory dir = load(musicService); licenseValid = musicService.isLicenseValid(context, this); albums = dir.getChildren(true, false); entries = dir.getChildren(); // This isn't really an artist if no albums on it! if(albums.size() == 0) { artist = false; } // If artist, we want to load the artist info to use later if(artist && ServerInfo.hasArtistInfo(context) && !Util.isOffline(context)) { try { String artistId; if(id.indexOf(';') == -1) { artistId = id; } else { artistId = id.substring(0, id.indexOf(';')); } artistInfo = musicService.getArtistInfo(artistId, refresh, false, context, this); if(artistInfo == null) { artistInfoDelayed = artistId; } } catch(Exception e) { Log.w(TAG, "Failed to get Artist Info even though it should be supported"); } } return new Pair<>(dir, licenseValid); } @Override protected void done(Pair<MusicDirectory, Boolean> result) { finishLoading(); currentTask = null; } @Override public void updateCache(int changeCode) { if(entryGridAdapter != null && changeCode == CachedMusicService.CACHE_UPDATE_LIST) { entryGridAdapter.notifyDataSetChanged(); } else if(changeCode == CachedMusicService.CACHE_UPDATE_METADATA) { if(coverArtView != null && coverArtRep != null && !Util.equals(coverArtRep.getCoverArt(), coverArtId)) { synchronized (coverArtRep) { if (updateCoverArtTask != null && updateCoverArtTask.isRunning()) { updateCoverArtTask.cancel(); } updateCoverArtTask = getImageLoader().loadImage(coverArtView, coverArtRep, false, true); coverArtId = coverArtRep.getCoverArt(); } } } } } @Override public SectionAdapter<Entry> getCurrentAdapter() { return entryGridAdapter; } @Override public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) { return new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { int viewType = entryGridAdapter.getItemViewType(position); if(viewType == EntryGridAdapter.VIEW_TYPE_SONG || viewType == EntryGridAdapter.VIEW_TYPE_HEADER || viewType == EntryInfiniteGridAdapter.VIEW_TYPE_LOADING) { return gridLayoutManager.getSpanCount(); } else { return 1; } } }; } private void finishLoading() { boolean validData = !entries.isEmpty() || !albums.isEmpty(); if(!validData) { setEmpty(true); } if(validData) { recyclerView.setVisibility(View.VISIBLE); } if(albumListType == null || "starred".equals(albumListType)) { entryGridAdapter = new EntryGridAdapter(context, entries, getImageLoader(), largeAlbums); entryGridAdapter.setRemoveFromPlaylist(playlistId != null); } else { if("alphabeticalByName".equals(albumListType)) { entryGridAdapter = new AlphabeticalAlbumAdapter(context, entries, getImageLoader(), largeAlbums); } else if("highest".equals(albumListType)) { entryGridAdapter = new TopRatedAlbumAdapter(context, entries, getImageLoader(), largeAlbums); } else { entryGridAdapter = new EntryInfiniteGridAdapter(context, entries, getImageLoader(), largeAlbums); } // Setup infinite loading based on scrolling final EntryInfiniteGridAdapter infiniteGridAdapter = (EntryInfiniteGridAdapter) entryGridAdapter; infiniteGridAdapter.setData(albumListType, albumListExtra, albumListSize); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); int totalItemCount = layoutManager.getItemCount(); int lastVisibleItem; if(layoutManager instanceof GridLayoutManager) { lastVisibleItem = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); } else if(layoutManager instanceof LinearLayoutManager) { lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); } else { return; } if(totalItemCount > 0 && lastVisibleItem >= totalItemCount - 2) { infiniteGridAdapter.loadMore(); } } }); } entryGridAdapter.setOnItemClickedListener(this); // Always show artist if this is not a artist we are viewing if(!artist) { entryGridAdapter.setShowArtist(true); } if(topTracks || showAll) { entryGridAdapter.setShowAlbum(true); } // Show header if not album list type and not root and not artist // For Subsonic 5.1+ display a header for artists with getArtistInfo data if it exists boolean addedHeader = false; if(albumListType == null && (!artist || artistInfo != null || artistInfoDelayed != null) && (share == null || entries.size() != albums.size())) { View header = createHeader(); if(header != null) { if (artistInfoDelayed != null) { final View finalHeader = header.findViewById(R.id.select_album_header); final View headerProgress = header.findViewById(R.id.header_progress); finalHeader.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); new SilentBackgroundTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); artistInfo = musicService.getArtistInfo(artistInfoDelayed, false, true, context, this); return null; } @Override protected void done(Void result) { setupCoverArt(finalHeader); setupTextDisplay(finalHeader); setupButtonEvents(finalHeader); finalHeader.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); } }.execute(); } entryGridAdapter.setHeader(header); addedHeader = true; } } int scrollToPosition = -1; if(lookupEntry != null) { for(int i = 0; i < entries.size(); i++) { if(lookupEntry.equals(entries.get(i).getTitle())) { scrollToPosition = i; entryGridAdapter.addSelected(entries.get(i)); lookupEntry = null; break; } } } recyclerView.setAdapter(entryGridAdapter); fastScroller.attachRecyclerView(recyclerView); context.supportInvalidateOptionsMenu(); if(scrollToPosition != -1) { recyclerView.scrollToPosition(scrollToPosition + (addedHeader ? 1 : 0)); } Bundle args = getArguments(); boolean playAll = args.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); if (playAll && !restoredInstance) { playAll(args.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), false, false); } } @Override protected void playNow(final boolean shuffle, final boolean append, final boolean playNext) { List<Entry> songs = getSelectedEntries(); if(!songs.isEmpty()) { download(songs, append, false, !append, playNext, shuffle); entryGridAdapter.clearSelected(); } else { playAll(shuffle, append, playNext); } } private void playAll(final boolean shuffle, final boolean append, final boolean playNext) { boolean hasSubFolders = albums != null && !albums.isEmpty(); if (hasSubFolders && (id != null || share != null || "starred".equals(albumListType))) { downloadRecursively(id, false, append, !append, shuffle, false, playNext); } else if(hasSubFolders && albumListType != null) { downloadRecursively(albums, shuffle, append, playNext); } else { download(entries, append, false, !append, playNext, shuffle); } } private List<Integer> getSelectedIndexes() { List<Entry> selected = entryGridAdapter.getSelected(); List<Integer> indexes = new ArrayList<Integer>(); for(Entry entry: selected) { indexes.add(entries.indexOf(entry)); } return indexes; } @Override protected void executeOnValid(RecursiveLoader onValid) { checkLicenseAndTrialPeriod(onValid); } @Override protected void downloadBackground(final boolean save) { List<Entry> songs = getSelectedEntries(); if(playlistId != null) { songs = entries; } if(songs.isEmpty()) { // Get both songs and albums downloadRecursively(id, save, false, false, false, true); } else { downloadBackground(save, songs); } } @Override protected void downloadBackground(final boolean save, final List<Entry> entries) { if (getDownloadService() == null) { return; } warnIfStorageUnavailable(); RecursiveLoader onValid = new RecursiveLoader(context) { @Override protected Boolean doInBackground() throws Throwable { getSongsRecursively(entries, true); getDownloadService().downloadBackground(songs, save); return null; } @Override protected void done(Boolean result) { Util.toast(context, context.getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size())); } }; checkLicenseAndTrialPeriod(onValid); } @Override protected void download(List<Entry> entries, boolean append, boolean save, boolean autoplay, boolean playNext, boolean shuffle) { download(entries, append, save, autoplay, playNext, shuffle, playlistName, playlistId); } @Override protected void delete() { List<Entry> songs = getSelectedEntries(); if(songs.isEmpty()) { for(Entry entry: entries) { if(entry.isDirectory()) { deleteRecursively(entry); } else { songs.add(entry); } } } if (getDownloadService() != null) { getDownloadService().delete(songs); } } public void removeFromPlaylist(final String id, final String name, final List<Integer> indexes) { new LoadingTask<Void>(context, true) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); musicService.removeFromPlaylist(id, indexes, context, null); return null; } @Override protected void done(Void result) { for(Integer index: indexes) { entryGridAdapter.removeAt(index); } Util.toast(context, context.getResources().getString(R.string.removed_playlist, String.valueOf(indexes.size()), name)); } @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.updated_playlist_error, name) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } public void downloadAllPodcastEpisodes() { new LoadingTask<Void>(context, true) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); for(int i = 0; i < entries.size(); i++) { PodcastEpisode episode = (PodcastEpisode) entries.get(i); if("skipped".equals(episode.getStatus())) { musicService.downloadPodcastEpisode(episode.getEpisodeId(), context, null); } } return null; } @Override protected void done(Void result) { Util.toast(context, context.getResources().getString(R.string.select_podcasts_downloading, podcastName)); } @Override protected void error(Throwable error) { Util.toast(context, getErrorMessage(error), false); } }.execute(); } @Override protected void toggleSelectedStarred() { UpdateHelper.OnStarChange onStarChange = null; if(albumListType != null && "starred".equals(albumListType)) { onStarChange = new UpdateHelper.OnStarChange() { @Override public void starChange(boolean starred) { } @Override public void starCommited(boolean starred) { if(!starred) { for (Entry entry : entries) { entryGridAdapter.removeItem(entry); } } } }; } UpdateHelper.toggleStarred(context, getSelectedEntries(), onStarChange); } private void checkLicenseAndTrialPeriod(LoadingTask onValid) { if (licenseValid) { onValid.execute(); return; } int trialDaysLeft = Util.getRemainingTrialDays(context); Log.i(TAG, trialDaysLeft + " trial days left."); if (trialDaysLeft == 0) { showDonationDialog(trialDaysLeft, null); } else if (trialDaysLeft < Constants.FREE_TRIAL_DAYS / 2) { showDonationDialog(trialDaysLeft, onValid); } else { Util.toast(context, context.getResources().getString(R.string.select_album_not_licensed, trialDaysLeft)); onValid.execute(); } } private void showDonationDialog(int trialDaysLeft, final LoadingTask onValid) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setIcon(android.R.drawable.ic_dialog_info); if (trialDaysLeft == 0) { builder.setTitle(R.string.select_album_donate_dialog_0_trial_days_left); } else { builder.setTitle(context.getResources().getQuantityString(R.plurals.select_album_donate_dialog_n_trial_days_left, trialDaysLeft, trialDaysLeft)); } builder.setMessage(R.string.select_album_donate_dialog_message); builder.setPositiveButton(R.string.select_album_donate_dialog_now, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.DONATION_URL))); } }); builder.setNegativeButton(R.string.select_album_donate_dialog_later, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); if (onValid != null) { onValid.execute(); } } }); builder.create().show(); } private void showTopTracks() { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(getArguments()); args.putBoolean(Constants.INTENT_EXTRA_TOP_TRACKS, true); fragment.setArguments(args); replaceFragment(fragment, true); } private void setShowAll() { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(getArguments()); args.putBoolean(Constants.INTENT_EXTRA_SHOW_ALL, true); fragment.setArguments(args); replaceFragment(fragment, true); } private void showSimilarArtists(String artistId) { SubsonicFragment fragment = new SimilarArtistFragment(); Bundle args = new Bundle(); args.putString(Constants.INTENT_EXTRA_NAME_ARTIST, artistId); fragment.setArguments(args); replaceFragment(fragment, true); } private void startArtistRadio(final String artistId) { new LoadingTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { DownloadService downloadService = getDownloadService(); downloadService.clear(); downloadService.setArtistRadio(artistId); return null; } @Override protected void done(Void result) { context.openNowPlaying(); } }.execute(); } private View createHeader() { View header = LayoutInflater.from(context).inflate(R.layout.select_album_header, null, false); setupCoverArt(header); setupTextDisplay(header); setupButtonEvents(header); return header; } private void setupCoverArt(View header) { setupCoverArtImpl((RecyclingImageView) header.findViewById(R.id.select_album_art)); } private void setupCoverArtImpl(RecyclingImageView coverArtView) { final ImageLoader imageLoader = getImageLoader(); // Try a few times to get a random cover art if(artistInfo != null) { final String url = artistInfo.getImageUrl(); coverArtView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (url == null) { return; } AlertDialog.Builder builder = new AlertDialog.Builder(context); ImageView fullScreenView = new ImageView(context); imageLoader.loadImage(fullScreenView, url, true); builder.setCancelable(true); AlertDialog imageDialog = builder.create(); // Set view here with unecessary 0's to remove top/bottom border imageDialog.setView(fullScreenView, 0, 0, 0, 0); imageDialog.show(); } }); imageLoader.loadImage(coverArtView, url, false); } else if(entries.size() > 0) { coverArtRep = null; this.coverArtView = coverArtView; for (int i = 0; (i < 3) && (coverArtRep == null || coverArtRep.getCoverArt() == null); i++) { coverArtRep = entries.get(random.nextInt(entries.size())); } coverArtView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (coverArtRep == null || coverArtRep.getCoverArt() == null) { return; } AlertDialog.Builder builder = new AlertDialog.Builder(context); ImageView fullScreenView = new ImageView(context); imageLoader.loadImage(fullScreenView, coverArtRep, true, true); builder.setCancelable(true); AlertDialog imageDialog = builder.create(); // Set view here with unecessary 0's to remove top/bottom border imageDialog.setView(fullScreenView, 0, 0, 0, 0); imageDialog.show(); } }); synchronized (coverArtRep) { coverArtId = coverArtRep.getCoverArt(); updateCoverArtTask = imageLoader.loadImage(coverArtView, coverArtRep, false, true); } } coverArtView.setOnInvalidated(new RecyclingImageView.OnInvalidated() { @Override public void onInvalidated(RecyclingImageView imageView) { setupCoverArtImpl(imageView); } }); } private void setupTextDisplay(final View header) { final 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); if(artistInfo != null) { titleView.setPadding(0, 6, 4, 8); } } else if(share != null) { titleView.setVisibility(View.GONE); } int songCount = 0; Set<String> artists = new HashSet<String>(); Set<Integer> years = new HashSet<Integer>(); Integer totalDuration = 0; for (Entry entry : entries) { if (!entry.isDirectory()) { songCount++; if (entry.getArtist() != null) { artists.add(entry.getArtist()); } if(entry.getYear() != null) { years.add(entry.getYear()); } Integer duration = entry.getDuration(); if(duration != null) { totalDuration += duration; } } } final TextView artistView = (TextView) header.findViewById(R.id.select_album_artist); if(podcastDescription != null || artistInfo != null) { artistView.setVisibility(View.VISIBLE); String text = podcastDescription != null ? podcastDescription : artistInfo.getBiography(); Spanned spanned = null; if(text != null) { spanned = Html.fromHtml(text); } artistView.setText(spanned); artistView.setSingleLine(false); final int minLines = context.getResources().getInteger(R.integer.TextDescriptionLength); artistView.setLines(minLines); artistView.setTextAppearance(context, android.R.style.TextAppearance_Small); final Spanned spannedText = spanned; artistView.setOnClickListener(new View.OnClickListener() { @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onClick(View v) { if(artistView.getMaxLines() == minLines) { // Use LeadingMarginSpan2 to try to make text flow around image Display display = context.getWindowManager().getDefaultDisplay(); ImageView coverArtView = (ImageView) header.findViewById(R.id.select_album_art); coverArtView.measure(display.getWidth(), display.getHeight()); int height, width; ViewGroup.MarginLayoutParams vlp = (ViewGroup.MarginLayoutParams) coverArtView.getLayoutParams(); if(coverArtView.getDrawable() != null) { height = coverArtView.getMeasuredHeight() + coverArtView.getPaddingBottom(); width = coverArtView.getWidth() + coverArtView.getPaddingRight(); } else { height = coverArtView.getHeight(); width = coverArtView.getWidth() + coverArtView.getPaddingRight(); } float textLineHeight = artistView.getPaint().getTextSize(); int lines = (int) Math.ceil(height / textLineHeight); SpannableString ss = new SpannableString(spannedText); ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); View linearLayout = header.findViewById(R.id.select_album_text_layout); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) linearLayout.getLayoutParams(); int[]rules = params.getRules(); rules[RelativeLayout.RIGHT_OF] = 0; params.leftMargin = vlp.rightMargin; artistView.setText(ss); artistView.setMaxLines(100); vlp = (ViewGroup.MarginLayoutParams) titleView.getLayoutParams(); vlp.leftMargin = width; } else { artistView.setMaxLines(minLines); } } }); artistView.setMovementMethod(LinkMovementMethod.getInstance()); } else if(topTracks) { artistView.setText(R.string.menu_top_tracks); artistView.setVisibility(View.VISIBLE); } else if(showAll) { artistView.setText(R.string.menu_show_all); artistView.setVisibility(View.VISIBLE); } else if (artists.size() == 1) { String artistText = artists.iterator().next(); if(years.size() == 1) { artistText += " - " + years.iterator().next(); } artistView.setText(artistText); artistView.setVisibility(View.VISIBLE); } else { artistView.setVisibility(View.GONE); } TextView songCountView = (TextView) header.findViewById(R.id.select_album_song_count); TextView songLengthView = (TextView) header.findViewById(R.id.select_album_song_length); if(podcastDescription != null || artistInfo != null) { songCountView.setVisibility(View.GONE); songLengthView.setVisibility(View.GONE); } else { String s = context.getResources().getQuantityString(R.plurals.select_album_n_songs, songCount, songCount); songCountView.setText(s.toUpperCase()); songLengthView.setText(Util.formatDuration(totalDuration)); } } private void setupButtonEvents(View header) { ImageView shareButton = (ImageView) header.findViewById(R.id.select_album_share); if(share != null || podcastId != null || !Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_SHARED, true) || Util.isOffline(context) || !UserUtil.canShare() || artistInfo != null) { shareButton.setVisibility(View.GONE); } else { shareButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createShare(SelectDirectoryFragment.this.entries); } }); } final ImageButton starButton = (ImageButton) header.findViewById(R.id.select_album_star); if(directory != null && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_STAR, true) && artistInfo == null) { if(directory.isStarred()) { starButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_star)); } else { starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline)); } starButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { UpdateHelper.toggleStarred(context, directory, new UpdateHelper.OnStarChange() { @Override public void starChange(boolean starred) { if (directory.isStarred()) { starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline)); starButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_star)); } else { starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline)); } } @Override public void starCommited(boolean starred) { } }); } }); } else { starButton.setVisibility(View.GONE); } View ratingBarWrapper = header.findViewById(R.id.select_album_rate_wrapper); final RatingBar ratingBar = (RatingBar) header.findViewById(R.id.select_album_rate); if(directory != null && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_MENU_RATING, true) && !Util.isOffline(context) && artistInfo == null) { ratingBar.setRating(directory.getRating()); ratingBarWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { UpdateHelper.setRating(context, directory, new UpdateHelper.OnRatingChange() { @Override public void ratingChange(int rating) { ratingBar.setRating(directory.getRating()); } }); } }); } else { ratingBar.setVisibility(View.GONE); } } }