diff options
20 files changed, 754 insertions, 706 deletions
diff --git a/app/build.gradle b/app/build.gradle index 56da71aa..1e2a6fe1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "github.daneren2005.dsub" - minSdkVersion 9 + minSdkVersion 14 targetSdkVersion 19 } buildTypes { @@ -32,6 +32,7 @@ dependencies { compile 'com.android.support:support-v4:22.1.1' compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.android.support:mediarouter-v7:22.1.1' + compile 'com.android.support:recyclerview-v7:22.1.1' compile 'com.google.android.gms:play-services-cast:7.0.0' compile 'com.sothree.slidinguppanel:library:3.0.0' compile 'de.hdodenhof:circleimageview:1.2.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 30a6eb3c..8eeaab2e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="github.daneren2005.dsub" android:installLocation="internalOnly" - android:versionCode="151" + android:versionCode="152" android:versionName="4.9.6"> <instrumentation android:name="android.test.InstrumentationTestRunner" @@ -31,7 +31,7 @@ <uses-feature android:name="android.hardware.microphone" android:required="false" /> <uses-feature android:name="android.hardware.wifi" android:required="false" /> - <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19"/> + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19"/> <supports-screens android:anyDensity="true" android:xlargeScreens="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/> diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/AlbumGridAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/AlbumGridAdapter.java deleted file mode 100644 index eb187569..00000000 --- a/app/src/main/java/github/daneren2005/dsub/adapter/AlbumGridAdapter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - 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 2014 (C) Scott Jackson -*/ - -package github.daneren2005.dsub.adapter; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; - -import java.util.List; - -import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.util.ImageLoader; -import github.daneren2005.dsub.view.AlbumCell; - -public class AlbumGridAdapter extends ArrayAdapter<MusicDirectory.Entry> { - private final static String TAG = AlbumGridAdapter.class.getSimpleName(); - private final Context activity; - private final ImageLoader imageLoader; - private List<MusicDirectory.Entry> entries; - private boolean showArtist; - - public AlbumGridAdapter(Context activity, ImageLoader imageLoader, List<MusicDirectory.Entry> entries, boolean showArtist) { - super(activity, android.R.layout.simple_list_item_1, entries); - this.entries = entries; - this.activity = activity; - this.imageLoader = imageLoader; - - // Always show artist if they aren't all the same - if(!showArtist) { - String artist = null; - for(MusicDirectory.Entry entry: entries) { - if(artist == null) { - artist = entry.getArtist(); - } - - if(artist != null && !artist.equals(entry.getArtist())) { - showArtist = true; - } - } - } - this.showArtist = showArtist; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - MusicDirectory.Entry entry = getItem(position); - - AlbumCell view; - if(convertView instanceof AlbumCell) { - view = (AlbumCell) convertView; - } else { - view = new AlbumCell(activity); - } - - view.setShowArtist(showArtist); - view.setObject(entry, imageLoader); - return view; - } -} diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/EntryAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/EntryAdapter.java index 9e506e5a..9cc96ae0 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/EntryAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/EntryAdapter.java @@ -60,7 +60,7 @@ public class EntryAdapter extends ArrayAdapter<MusicDirectory.Entry> { if (entry.isDirectory()) { if(entry.isAlbum()) { AlbumView view; - view = new AlbumView(activity); + view = new AlbumView(activity, false); view.setObject(entry, imageLoader); return view; } else { diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java new file mode 100644 index 00000000..6de7ce33 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java @@ -0,0 +1,254 @@ +/* + 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.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.MusicDirectory.Entry; +import github.daneren2005.dsub.util.ImageLoader; +import github.daneren2005.dsub.view.AlbumView; +import github.daneren2005.dsub.view.SongView; +import github.daneren2005.dsub.view.UpdateView; +import github.daneren2005.dsub.view.UpdateView.UpdateViewHolder; + +public class EntryGridAdapter extends RecyclerView.Adapter<UpdateViewHolder> { + private static String TAG = EntryGridAdapter.class.getSimpleName(); + + public static int VIEW_TYPE_HEADER = 0; + public static int VIEW_TYPE_ALBUM_CELL = 1; + public static int VIEW_TYPE_ALBUM_LINE = 2; + public static int VIEW_TYPE_SONG = 3; + + protected Context context; + protected List<Entry> entries; + private ImageLoader imageLoader; + private boolean largeAlbums; + private boolean showArtist = false; + private boolean checkable = true; + private OnEntryClickedListener onEntryClickedListener; + + private View header; + private List<Entry> selected = new ArrayList<Entry>(); + private UpdateView contextView; + private Entry contextEntry; + + public EntryGridAdapter(Context context, List<Entry> entries, ImageLoader imageLoader, boolean largeCell) { + this.context = context; + this.entries = entries; + this.imageLoader = imageLoader; + this.largeAlbums = largeCell; + + // Always show artist if they aren't all the same + String artist = null; + for(MusicDirectory.Entry entry: entries) { + if(artist == null) { + artist = entry.getArtist(); + } + + if(artist != null && !artist.equals(entry.getArtist())) { + showArtist = true; + } + } + } + + @Override + public UpdateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if(viewType == VIEW_TYPE_HEADER) { + return new UpdateViewHolder(header, false); + } + + UpdateView updateView = null; + if(viewType == VIEW_TYPE_ALBUM_LINE || viewType == VIEW_TYPE_ALBUM_CELL) { + updateView = new AlbumView(context, viewType == VIEW_TYPE_ALBUM_CELL); + } else if(viewType == VIEW_TYPE_SONG) { + updateView = new SongView(context); + } + + if(viewType != VIEW_TYPE_HEADER && updateView != null) { + final UpdateView view = updateView; + updateView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Entry entry = getEntryForView(view); + + if (view.isCheckable() && v instanceof SongView) { + SongView songView = (SongView) v; + + if (selected.contains(entry)) { + selected.remove(entry); + songView.setChecked(false); + } else { + selected.add(entry); + songView.setChecked(true); + } + } else if (onEntryClickedListener != null) { + onEntryClickedListener.onEntryClicked(entry); + } + } + }); + updateView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + Entry entry = getEntryForView(view); + + setContextEntry(view, entry); + v.showContextMenu(); + return false; + } + }); + + View moreButton = updateView.findViewById(R.id.more_button); + if(moreButton != null) { + moreButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Entry entry = getEntryForView(view); + setContextEntry(view, entry); + v.showContextMenu(); + } + }); + } + } + + return new UpdateViewHolder(updateView); + } + + @Override + public void onBindViewHolder(UpdateViewHolder holder, int position) { + // Header already created + if(header != null && position == 0) { + return; + } + + UpdateView view = holder.getUpdateView(); + + int viewType = getItemViewType(position); + Entry entry = getEntryForPosition(position); + if(viewType == VIEW_TYPE_ALBUM_CELL || viewType == VIEW_TYPE_ALBUM_LINE) { + AlbumView albumView = (AlbumView) view; + albumView.setShowArtist(showArtist); + albumView.setObject(entry, imageLoader); + } else if(viewType == VIEW_TYPE_SONG) { + SongView songView = (SongView) view; + songView.setObject(entry, checkable); + songView.setChecked(selected.contains(entry)); + } + view.setPosition(position); + } + + @Override + public int getItemCount() { + int size = entries.size(); + + if(header != null) { + size++; + } + + return size; + } + + @Override + public int getItemViewType(int position) { + if(header != null && position == 0) { + return VIEW_TYPE_HEADER; + } + + Entry entry = getEntryForPosition(position); + if(entry.isDirectory()) { + if (largeAlbums) { + return VIEW_TYPE_ALBUM_CELL; + } else { + return VIEW_TYPE_ALBUM_LINE; + } + } else { + return VIEW_TYPE_SONG; + } + } + + public Entry getEntryForView(UpdateView view) { + int position = view.getPosition(); + return getEntryForPosition(position); + } + public Entry getEntryForPosition(int position) { + if(header != null) { + position--; + } + + return entries.get(position); + } + + public void setHeader(View header) { + this.header = header; + } + + public void setShowArtist(boolean showArtist) { + this.showArtist = showArtist; + } + public void setCheckable(boolean checkable) { + this.checkable = checkable; + } + + public void setOnEntryClickedListener(OnEntryClickedListener listener) { + this.onEntryClickedListener = listener; + } + + public List<Entry> getSelected() { + List<Entry> selected = new ArrayList<>(); + selected.addAll(this.selected); + return selected; + } + public void clearSelected() { + for(Entry entry: selected) { + int index = entries.indexOf(entry); + this.notifyItemChanged(index); + } + selected.clear(); + } + + public void removeEntry(Entry entry) { + int index = entries.indexOf(entry); + if(index != -1) { + removeAt(index); + } + } + public void removeAt(int index) { + entries.remove(index); + notifyItemRemoved(index); + } + + public void setContextEntry(UpdateView view, Entry entry) { + this.contextView = view; + this.contextEntry = entry; + } + public UpdateView getContextView() { + return contextView; + } + public Entry getContextEntry() { + return contextEntry; + } + + public interface OnEntryClickedListener { + void onEntryClicked(Entry entry); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java new file mode 100644 index 00000000..3bc3f6aa --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java @@ -0,0 +1,95 @@ +/* + 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.adapter; + +import android.content.Context; + +import java.util.List; + +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.MusicDirectory.Entry; +import github.daneren2005.dsub.domain.ServerInfo; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.service.MusicServiceFactory; +import github.daneren2005.dsub.util.ImageLoader; +import github.daneren2005.dsub.util.SilentBackgroundTask; + +public class EntryInfiniteGridAdapter extends EntryGridAdapter { + private String type; + private String extra; + private int size; + + private boolean loading = false; + private boolean allLoaded = false; + + public EntryInfiniteGridAdapter(Context context, List<Entry> entries, ImageLoader imageLoader, boolean largeCell) { + super(context, entries, imageLoader, largeCell); + } + + public void setData(String type, String extra, int size) { + this.type = type; + this.extra = extra; + this.size = size; + } + + public void loadMore() { + if(loading || allLoaded) { + return; + } + loading = true; + + new SilentBackgroundTask<Void>(context) { + private List<Entry> newData; + + @Override + protected Void doInBackground() throws Throwable { + newData = cacheInBackground(); + if(newData.isEmpty()) { + allLoaded = true; + } + return null; + } + + @Override + protected void done(Void result) { + appendCachedData(newData); + loading = false; + } + }.execute(); + } + + protected List<Entry> cacheInBackground() throws Exception { + MusicService service = MusicServiceFactory.getMusicService(context); + MusicDirectory result; + int offset = entries.size(); + if(("genres".equals(type) && ServerInfo.checkServerVersion(context, "1.10.0")) || "years".equals(type)) { + result = service.getAlbumList(type, extra, size, offset, context, null); + } else if("genres".equals(type) || "genres-songs".equals(type)) { + result = service.getSongsByGenre(extra, size, offset, context, null); + } else { + result = service.getAlbumList(type, size, offset, context, null); + } + return result.getChildren(); + } + + protected void appendCachedData(List<Entry> newData) { + if(newData.size() > 0) { + int start = entries.size(); + entries.addAll(newData); + this.notifyItemRangeInserted(start, newData.size()); + } + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 9e7cd053..0a64e8fd 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -9,6 +9,9 @@ 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; @@ -23,24 +26,20 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.GridView; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.ListView; import android.widget.RatingBar; import android.widget.RelativeLayout; import android.widget.TextView; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.EntryInfiniteGridAdapter; +import github.daneren2005.dsub.adapter.EntryGridAdapter; 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.DownloadService; import github.daneren2005.dsub.util.ImageLoader; -import github.daneren2005.dsub.adapter.AlbumGridAdapter; -import github.daneren2005.dsub.adapter.EntryAdapter; import java.io.Serializable; import java.util.Iterator; @@ -58,9 +57,9 @@ import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.TabBackgroundTask; import github.daneren2005.dsub.util.UserUtil; import github.daneren2005.dsub.util.Util; -import github.daneren2005.dsub.adapter.AlbumListAdapter; -import github.daneren2005.dsub.view.HeaderGridView; +import github.daneren2005.dsub.view.GridSpacingDecoration; import github.daneren2005.dsub.view.MyLeadingMarginSpan2; +import github.daneren2005.dsub.view.UpdateView; import java.util.ArrayList; import java.util.Arrays; @@ -69,16 +68,14 @@ import java.util.Set; import static github.daneren2005.dsub.domain.MusicDirectory.Entry; -public class SelectDirectoryFragment extends SubsonicFragment implements AdapterView.OnItemClickListener { +public class SelectDirectoryFragment extends SubsonicFragment implements EntryGridAdapter.OnEntryClickedListener { private static final String TAG = SelectDirectoryFragment.class.getSimpleName(); - private GridView albumList; - private ListView entryList; + private RecyclerView recyclerView; + private EntryGridAdapter entryGridAdapter; private Boolean licenseValid; - private EntryAdapter entryAdapter; private List<Entry> albums; private List<Entry> entries; - private boolean albumContext = false; private boolean addAlbumHeader = false; private LoadTask currentTask; private ArtistInfo artistInfo; @@ -113,7 +110,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter super.onCreate(bundle); if(bundle != null) { entries = (List<Entry>) bundle.getSerializable(Constants.FRAGMENT_LIST); - albums = (List<Entry>) bundle.getSerializable(Constants.FRAGMENT_LIST2); artistInfo = (ArtistInfo) bundle.getSerializable(Constants.FRAGMENT_EXTRA); restoredInstance = true; } @@ -171,28 +167,40 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout); refreshLayout.setOnRefreshListener(this); - entryList = (ListView) rootView.findViewById(R.id.select_album_entries); - entryList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - entryList.setOnItemClickListener(this); - setupScrollList(entryList); - if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true)) { largeAlbums = true; } - if(albumListType == null || "starred".equals(albumListType) || !largeAlbums) { - albumList = (GridView) inflater.inflate(R.layout.unscrollable_grid_view, entryList, false); - addAlbumHeader = true; + recyclerView = (RecyclerView) rootView.findViewById(R.id.select_album_entries); + recyclerView.setHasFixedSize(true); + setupScrollList(recyclerView); + + if(largeAlbums) { + final int columns = context.getResources().getInteger(R.integer.Grid_Columns); + GridLayoutManager gridLayoutManager = new GridLayoutManager(context, columns); + gridLayoutManager.setSpanSizeLookup(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) { + return columns; + } else { + return 1; + } + } + }); + recyclerView.addItemDecoration(new GridSpacingDecoration()); + recyclerView.setLayoutManager(gridLayoutManager); } else { - ViewGroup rootGroup = (ViewGroup) rootView.findViewById(R.id.select_album_layout); - albumList = (GridView) inflater.inflate(R.layout.grid_view, rootGroup, false); - rootGroup.removeView(entryList); - rootGroup.addView(albumList); + LinearLayoutManager layoutManager = new LinearLayoutManager(context); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + recyclerView.setLayoutManager(layoutManager); + } - setupScrollList(albumList); + if(albumListType == null || "starred".equals(albumListType)) { + addAlbumHeader = true; } - registerForContextMenu(entryList); - setupAlbumList(); + registerForContextMenu(recyclerView); if(entries == null) { if(primaryFragment || secondaryFragment) { @@ -286,21 +294,22 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter return true; case R.id.menu_download: downloadBackground(false); - selectAll(false, false); + entryGridAdapter.clearSelected(); return true; case R.id.menu_cache: downloadBackground(true); - selectAll(false, false); + entryGridAdapter.clearSelected(); return true; case R.id.menu_delete: delete(); - selectAll(false, false); + entryGridAdapter.clearSelected(); return true; case R.id.menu_add_playlist: - if(getSelectedSongs().isEmpty()) { - selectAll(true, false); + List<Entry> songs = getSelectedSongs(); + if(songs.isEmpty()) { + songs = entries; } - addToPlaylist(getSelectedSongs()); + addToPlaylist(songs); return true; case R.id.menu_remove_playlist: removeFromPlaylist(playlistId, playlistName, getSelectedIndexes()); @@ -332,23 +341,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - - Entry entry; - if(view.getId() == R.id.select_album_entries) { - if(info.position == 0) { - return; - } - entry = (Entry) entryList.getItemAtPosition(info.position); - // When List has Grid embedded in header, this is called against the header as well - if(entry != null) { - albumContext = false; - } - } else { - entry = (Entry) albumList.getItemAtPosition(info.position); - albumContext = true; - } + Entry entry = entryGridAdapter.getContextEntry(); + UpdateView targetView = entryGridAdapter.getContextView(); + menuInfo = new AdapterView.AdapterContextMenuInfo(targetView, 0, 0); // Don't try to display a context menu if error here if(entry == null) { @@ -383,22 +378,12 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter if(menuItem.getGroupId() != getSupportTag()) { return false; } - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Object selectedItem; - int headers = entryList.getHeaderViewsCount(); - if(albumContext) { - selectedItem = albumList.getItemAtPosition(info.position); - } else { - if(info.position == 0) { - return false; - } - selectedItem = entries.get(info.position - headers); - } + Entry entry = entryGridAdapter.getContextEntry(); if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_PLAY_NOW_AFTER, false) && menuItem.getItemId() == R.id.song_menu_play_now) { List<Entry> songs = new ArrayList<Entry>(); - Iterator it = entries.listIterator(info.position - headers); + songs.add(entry); + Iterator it = entries.listIterator(entries.indexOf(entry)); while(it.hasNext()) { songs.add((Entry) it.next()); } @@ -407,19 +392,19 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter return true; } - if(onContextItemSelected(menuItem, selectedItem)) { + if(onContextItemSelected(menuItem, entry)) { return true; } switch (menuItem.getItemId()) { case R.id.song_menu_remove_playlist: - removeFromPlaylist(playlistId, playlistName, Arrays.<Integer>asList(info.position - headers)); + removeFromPlaylist(playlistId, playlistName, Arrays.<Integer>asList(entries.indexOf(entry))); break; case R.id.song_menu_server_download: - downloadPodcastEpisode((PodcastEpisode)selectedItem); + downloadPodcastEpisode((PodcastEpisode) entry); break; case R.id.song_menu_server_delete: - deletePodcastEpisode((PodcastEpisode)selectedItem); + deletePodcastEpisode((PodcastEpisode) entry); break; } @@ -427,38 +412,35 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (position >= 0) { - Entry entry = (Entry) parent.getItemAtPosition(position); - 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.getArtist() == null && entry.getParent() == null) { - 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; - } - - playNow(Arrays.asList(entry)); + public void onEntryClicked(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.getArtist() == null && entry.getParent() == null) { + 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; } + + playNow(Arrays.asList(entry)); } } @@ -477,8 +459,8 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter if(currentTask != null) { currentTask.cancel(); } - - entryList.setVisibility(View.INVISIBLE); + + recyclerView.setVisibility(View.INVISIBLE); if (playlistId != null) { getPlaylist(playlistId, playlistName, refresh); } else if(podcastId != null) { @@ -685,11 +667,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter licenseValid = musicService.isLicenseValid(context, this); albums = dir.getChildren(true, false); - if(largeAlbums) { - entries = dir.getChildren(false, true); - } else { - entries = dir.getChildren(); - } + entries = dir.getChildren(); // This isn't really an artist if no albums on it! if(albums.size() == 0) { @@ -727,119 +705,112 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } private void finishLoading() { - // 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 - View header = null; - if(albumListType == null && !"root".equals(id) && (!artist || artistInfo != null || artistInfoDelayed != null)) { - header = createHeader(); + boolean validData = !entries.isEmpty() || !albums.isEmpty(); + if(!validData) { + setEmpty(true); + } - if(header != null && artistInfoDelayed != null) { - final View finalHeader = header.findViewById(R.id.select_album_header); - final View headerProgress = header.findViewById(R.id.header_progress); + if(validData) { + recyclerView.setVisibility(View.VISIBLE); + } - finalHeader.setVisibility(View.INVISIBLE); - headerProgress.setVisibility(View.VISIBLE); + if(albumListType == null || "starred".equals(albumListType)) { + entryGridAdapter = new EntryGridAdapter(context, entries, getImageLoader(), largeAlbums); + } else { + entryGridAdapter = new EntryInfiniteGridAdapter(context, entries, getImageLoader(), largeAlbums); - new SilentBackgroundTask<Void>(context) { - @Override - protected Void doInBackground() throws Throwable { - MusicService musicService = MusicServiceFactory.getMusicService(context); - artistInfo = musicService.getArtistInfo(artistInfoDelayed, false, true, context, this); + // Setup infinite loading based on scrolling + final EntryInfiniteGridAdapter infiniteGridAdapter = (EntryInfiniteGridAdapter) entryGridAdapter; + infiniteGridAdapter.setData(albumListType, albumListExtra, albumListSize); - return null; - } + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + } - @Override - protected void done(Void result) { - /*if(albumList instanceof HeaderGridView) { - HeaderGridView headerGridView = (HeaderGridView) albumList; - headerGridView.invalidateRowHeight(); - ((BaseAdapter) headerGridView.getAdapter()).notifyDataSetChanged(); - }*/ - - setupCoverArt(finalHeader); - setupTextDisplay(finalHeader); - setupButtonEvents(finalHeader); - - finalHeader.setVisibility(View.VISIBLE); - headerProgress.setVisibility(View.GONE); + @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; } - }.execute(); - } - - // Only add header to entry list if we aren't going recreate album grid as root anyways - if(header != null && entryList != null && (!addAlbumHeader || entries.size() > 0)) { - entryList.addHeaderView(header, null, false); - header = null; - } - } - // Needs to be added here, GB crashes if you to try to remove the header view before adapter is set - if(addAlbumHeader) { - if(entries.size() > 0 || playlistId != null || podcastId != null) { - entryList.addHeaderView(albumList); - } else { - ViewGroup rootGroup = (ViewGroup) rootView.findViewById(R.id.select_album_layout); - albumList = (GridView) context.getLayoutInflater().inflate(R.layout.grid_view, rootGroup, false); - rootGroup.removeView(entryList); - rootGroup.addView(albumList); - - setupScrollList(albumList); - setupAlbumList(); - - // This should only not be null for a artist with only albums - if(header != null) { - HeaderGridView headerGridView = (HeaderGridView) albumList; - headerGridView.addHeaderView(header); + if(totalItemCount > 0 && lastVisibleItem >= totalItemCount - 1) { + infiniteGridAdapter.loadMore(); + } } - } - addAlbumHeader = false; + }); } - - boolean validData = !entries.isEmpty() || !albums.isEmpty(); - if(!validData) { - setEmpty(true); + entryGridAdapter.setOnEntryClickedListener(this); + // Always show artist if this is not a artist we are viewing + if(!artist) { + entryGridAdapter.setShowArtist(true); + } + // Podcasts are not checkable + if(podcastId != null) { + entryGridAdapter.setCheckable(false); } - // Always going to have entries in entryAdapter - entryAdapter = new EntryAdapter(context, getImageLoader(), entries, (podcastId == null)); - ListAdapter listAdapter = entryAdapter; - // Song-only genre needs to always be entry list + infinite adapter - if("genres-songs".equals(albumListType)) { - ViewGroup rootGroup = (ViewGroup) rootView.findViewById(R.id.select_album_layout); - if(rootGroup.findViewById(R.id.gridview) != null && largeAlbums) { - rootGroup.removeView(albumList); - rootGroup.addView(entryList); - } - listAdapter = new AlbumListAdapter(context, entryAdapter, albumListType, albumListExtra, albumListSize); - } else if(albumListType == null || "starred".equals(albumListType)) { - // Only set standard album adapter if not album list and largeAlbums is true - if(largeAlbums) { - albumList.setAdapter(new AlbumGridAdapter(context, getImageLoader(), albums, !artist)); - } - } else { - // If album list, use infinite adapters for either depending on whether or not largeAlbums is true - if(largeAlbums) { - albumList.setAdapter(new AlbumListAdapter(context, new AlbumGridAdapter(context, getImageLoader(), albums, true), albumListType, albumListExtra, albumListSize)); - } else { - listAdapter = new AlbumListAdapter(context, entryAdapter, albumListType, albumListExtra, albumListSize); + // 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 + if(albumListType == null && !"root".equals(id) && (!artist || artistInfo != null || artistInfoDelayed != null)) { + 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); } } - entryList.setAdapter(listAdapter); - if(validData) { - entryList.setVisibility(View.VISIBLE); - } + + recyclerView.setAdapter(entryGridAdapter); context.supportInvalidateOptionsMenu(); - if(lookupEntry != null) { + /*if(lookupEntry != null) { for(int i = 0; i < entries.size(); i++) { if(lookupEntry.equals(entries.get(i).getTitle())) { + recyclerView.scrollToPosition(); entryList.setSelection(i + entryList.getHeaderViewsCount()); lookupEntry = null; break; } } - } + }*/ Bundle args = getArguments(); boolean playAll = args.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); @@ -848,122 +819,51 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } } - private void setupAlbumList() { - albumList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Entry entry = (Entry) parent.getItemAtPosition(position); - 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.getArtist() == null && entry.getParent() == null) { - args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true); - } - fragment.setArguments(args); - - replaceFragment(fragment, true); - } - }); - - registerForContextMenu(entryList); - registerForContextMenu(albumList); - } - private void playNow(final boolean shuffle, final boolean append) { playNow(shuffle, append, false); } private void playNow(final boolean shuffle, final boolean append, final boolean playNext) { - if(getSelectedSongs().size() > 0) { - download(append, false, !append, playNext, shuffle); - selectAll(false, false); + List<Entry> songs = getSelectedSongs(); + if(!songs.isEmpty()) { + download(songs, append, false, !append, playNext, shuffle); + entryGridAdapter.clearSelected(); } else { playAll(shuffle, append); } } private void playAll(final boolean shuffle, final boolean append) { - boolean hasSubFolders = false; - for (int i = 0; i < entryList.getCount(); i++) { - Entry entry = (Entry) entryList.getItemAtPosition(i); - if (entry != null && entry.isDirectory()) { - hasSubFolders = true; - break; - } - } - if(albums.size() > 0) { - hasSubFolders = true; - } + boolean hasSubFolders = !albums.isEmpty(); if (hasSubFolders && (id != null || share != null || "starred".equals(albumListType))) { downloadRecursively(id, false, append, !append, shuffle, false); } else if(hasSubFolders && albumListType != null) { downloadRecursively(albums, shuffle, append); } else { - selectAll(true, false); - download(append, false, !append, false, shuffle); - selectAll(false, false); - } - } - - private void selectAll(boolean selected, boolean toast) { - int count = entryList.getCount(); - int selectedCount = 0; - for (int i = 0; i < count; i++) { - Entry entry = (Entry) entryList.getItemAtPosition(i); - if (entry != null && !entry.isDirectory() && !entry.isVideo()) { - entryList.setItemChecked(i, selected); - selectedCount++; - } - } - - // Display toast: N tracks selected / N tracks unselected - if (toast) { - int toastResId = selected ? R.string.select_album_n_selected - : R.string.select_album_n_unselected; - Util.toast(context, context.getString(toastResId, selectedCount)); + download(entries, append, false, !append, false, shuffle); } } private List<Entry> getSelectedSongs() { - List<Entry> songs = new ArrayList<Entry>(10); - int count = entryList.getCount(); - for (int i = 0; i < count; i++) { - if (entryList.isItemChecked(i)) { - Entry entry = (Entry) entryList.getItemAtPosition(i); - // Don't try to add directories or 1-starred songs - if(!entry.isDirectory() && entry.getRating() != 1) { - songs.add(entry); - } - } - } - return songs; + return entryGridAdapter.getSelected(); } private List<Integer> getSelectedIndexes() { + List<Entry> selected = entryGridAdapter.getSelected(); List<Integer> indexes = new ArrayList<Integer>(); - int count = entryList.getCount(); - int headers = entryList.getHeaderViewsCount(); - for (int i = 0; i < count; i++) { - if (entryList.isItemChecked(i)) { - indexes.add(i - headers); - } + for(Entry entry: selected) { + indexes.add(entries.indexOf(entry)); } return indexes; } - private void download(final boolean append, final boolean save, final boolean autoplay, final boolean playNext, final boolean shuffle) { + private void download(final List<Entry> songs, final boolean append, final boolean save, final boolean autoplay, final boolean playNext, final boolean shuffle) { if (getDownloadService() == null) { return; } - final List<Entry> songs = getSelectedSongs(); warnIfStorageUnavailable(); // Conditions for using play now button @@ -1006,11 +906,11 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter checkLicenseAndTrialPeriod(onValid); } private void downloadBackground(final boolean save) { + List<Entry> songs = getSelectedSongs(); if(playlistId != null) { - selectAll(true, false); + songs = entries; } - List<Entry> songs = getSelectedSongs(); if(songs.isEmpty()) { // Get both songs and albums downloadRecursively(id, save, false, false, false, true); @@ -1043,12 +943,12 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter private void delete() { List<Entry> songs = getSelectedSongs(); if(songs.isEmpty()) { - selectAll(true, false); - songs = getSelectedSongs(); - - // Also delete all directories - for(Entry album: albums) { - deleteRecursively(album); + for(Entry entry: entries) { + if(entry.isDirectory()) { + deleteRecursively(entry); + } else { + songs.add(entry); + } } } if (getDownloadService() != null) { @@ -1067,11 +967,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter @Override protected void done(Void result) { - for(int i = indexes.size() - 1; i >= 0; i--) { - entryList.setItemChecked(indexes.get(i) + 1, false); - entryAdapter.removeAt(indexes.get(i)); + for(Integer index: indexes) { + entryGridAdapter.removeAt(index); } - entryAdapter.notifyDataSetChanged(); Util.toast(context, context.getResources().getString(R.string.removed_playlist, indexes.size(), name)); } @@ -1156,8 +1054,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter @Override protected void done(Void result) { - entries.remove(episode); - entryAdapter.notifyDataSetChanged(); + entryGridAdapter.removeEntry(episode); } @Override @@ -1218,10 +1115,8 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter Util.toast(context, context.getResources().getString(R.string.starring_content_unstarred, Integer.toString(unstar.size()))); for(Entry entry: unstar) { - entries.remove(entry); + entryGridAdapter.removeEntry(entry); } - entryAdapter.notifyDataSetChanged(); - selectAll(false, false); } @Override @@ -1340,25 +1235,13 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } private View createHeader() { - View header = entryList.findViewById(R.id.select_album_header_wrapper); - boolean add = false; - if(header == null) { - header = LayoutInflater.from(context).inflate(R.layout.select_album_header, entryList, false); - add = true; - } + View header = LayoutInflater.from(context).inflate(R.layout.select_album_header, null, false); setupCoverArt(header); setupTextDisplay(header); + setupButtonEvents(header); - if(add) { - setupButtonEvents(header); - } - - if(add) { - return header; - } else { - return null; - } + return header; } private void setupCoverArt(View header) { @@ -1507,11 +1390,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } else { artistView.setMaxLines(minLines); } - - if(albumList instanceof HeaderGridView) { - HeaderGridView headerGridView = (HeaderGridView) albumList; - ((BaseAdapter) headerGridView.getAdapter()).notifyDataSetChanged(); - } } }); artistView.setMovementMethod(LinkMovementMethod.getInstance()); diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java index fcae3a5c..6070185d 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.StatFs; import android.support.v4.app.Fragment; import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.ContextMenu; import android.view.GestureDetector; @@ -76,7 +77,6 @@ import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.LoadingTask; import github.daneren2005.dsub.util.UserUtil; import github.daneren2005.dsub.util.Util; -import github.daneren2005.dsub.view.AlbumCell; import github.daneren2005.dsub.view.AlbumView; import github.daneren2005.dsub.view.ArtistEntryView; import github.daneren2005.dsub.view.ArtistView; @@ -308,14 +308,10 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR } } // Apply similar logic to album views - else if(info.targetView instanceof AlbumCell || info.targetView instanceof AlbumView - || info.targetView instanceof ArtistView || info.targetView instanceof ArtistEntryView) { + else if(info.targetView instanceof AlbumView || info.targetView instanceof ArtistView || info.targetView instanceof ArtistEntryView) { File folder = null; int id = 0; - if(info.targetView instanceof AlbumCell) { - folder = ((AlbumCell) info.targetView).getFile(); - id = R.id.album_menu_delete; - } else if(info.targetView instanceof AlbumView) { + if(info.targetView instanceof AlbumView) { folder = ((AlbumView) info.targetView).getFile(); id = R.id.album_menu_delete; } else if(info.targetView instanceof ArtistView) { @@ -632,6 +628,29 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR R.color.holo_red_light); } } + protected void setupScrollList(final RecyclerView recyclerView) { + if(!context.isTouchscreen()) { + refreshLayout.setEnabled(false); + } else { + 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) { + refreshLayout.setEnabled(!recyclerView.canScrollVertically(-1)); + } + }); + + refreshLayout.setColorScheme( + R.color.holo_blue_light, + R.color.holo_orange_light, + R.color.holo_green_light, + R.color.holo_red_light); + } + } protected void warnIfStorageUnavailable() { if (!Util.isExternalStoragePresent()) { @@ -1788,11 +1807,6 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR return; } - for (Entry song : parent.getChildren(false, true)) { - if (!song.isVideo() && song.getRating() != 1) { - songs.add(song); - } - } for (Entry dir : parent.getChildren(true, false)) { if(dir.getRating() == 1) { continue; @@ -1806,6 +1820,12 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR } getSongsRecursively(musicDirectory, songs); } + + for (Entry song : parent.getChildren(false, true)) { + if (!song.isVideo() && song.getRating() != 1) { + songs.add(song); + } + } } @Override diff --git a/app/src/main/java/github/daneren2005/dsub/view/AlbumCell.java b/app/src/main/java/github/daneren2005/dsub/view/AlbumCell.java deleted file mode 100644 index 8707ece7..00000000 --- a/app/src/main/java/github/daneren2005/dsub/view/AlbumCell.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - 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 2014 (C) Scott Jackson -*/ - -package github.daneren2005.dsub.view; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.RatingBar; -import android.widget.TextView; - -import java.io.File; - -import github.daneren2005.dsub.R; -import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.util.FileUtil; -import github.daneren2005.dsub.util.ImageLoader; - -public class AlbumCell extends UpdateView { - private static final String TAG = AlbumCell.class.getSimpleName(); - - private Context context; - private MusicDirectory.Entry album; - private File file; - - private View coverArtView; - private TextView titleView; - private TextView artistView; - private boolean showArtist = true; - - public AlbumCell(Context context) { - super(context); - this.context = context; - LayoutInflater.from(context).inflate(R.layout.album_cell_item, this, true); - - coverArtView = findViewById(R.id.album_coverart); - titleView = (TextView) findViewById(R.id.album_title); - artistView = (TextView) findViewById(R.id.album_artist); - - ratingBar = (RatingBar) findViewById(R.id.album_rating); - ratingBar.setFocusable(false); - starButton = (ImageButton) findViewById(R.id.album_star); - starButton.setFocusable(false); - moreButton = (ImageView) findViewById(R.id.album_more); - moreButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - v.showContextMenu(); - } - }); - } - - public void setShowArtist(boolean showArtist) { - this.showArtist = showArtist; - } - - protected void setObjectImpl(Object obj1, Object obj2) { - this.album = (MusicDirectory.Entry) obj1; - titleView.setText(album.getAlbumDisplay()); - String artist = ""; - if(showArtist) { - artist = album.getArtist(); - if (artist == null) { - artist = ""; - } - if (album.getYear() != null) { - artist += " - " + album.getYear(); - } - } else if(album.getYear() != null) { - artist += album.getYear(); - } - artistView.setText(album.getArtist() == null ? "" : artist); - imageTask = ((ImageLoader)obj2).loadImage(coverArtView, album, false, true); - file = null; - } - - @Override - protected void updateBackground() { - if(file == null) { - file = FileUtil.getAlbumDirectory(context, album); - } - - exists = file.exists(); - isStarred = album.isStarred(); - isRated = album.getRating(); - } - - public MusicDirectory.Entry getEntry() { - return album; - } - - public File getFile() { - return file; - } -} diff --git a/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java b/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java index bd54ea1e..2eb30037 100644 --- a/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java +++ b/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java @@ -16,6 +16,7 @@ Copyright 2009 (C) Sindre Mehus */ + package github.daneren2005.dsub.view; import android.content.Context; @@ -34,11 +35,6 @@ import github.daneren2005.dsub.util.Util; import java.io.File; import java.util.List; -/** - * Used to display albums in a {@code ListView}. - * - * @author Sindre Mehus - */ public class AlbumView extends UpdateView { private static final String TAG = AlbumView.class.getSimpleName(); @@ -46,42 +42,55 @@ public class AlbumView extends UpdateView { private MusicDirectory.Entry album; private File file; + private View coverArtView; private TextView titleView; private TextView artistView; - private View coverArtView; + private boolean showArtist = true; - public AlbumView(Context context) { + public AlbumView(Context context, boolean cell) { super(context); this.context = context; - LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true); + if(cell) { + LayoutInflater.from(context).inflate(R.layout.album_cell_item, this, true); + } else { + LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true); + } + + coverArtView = findViewById(R.id.album_coverart); titleView = (TextView) findViewById(R.id.album_title); artistView = (TextView) findViewById(R.id.album_artist); - coverArtView = findViewById(R.id.album_coverart); + ratingBar = (RatingBar) findViewById(R.id.album_rating); + ratingBar.setFocusable(false); starButton = (ImageButton) findViewById(R.id.album_star); starButton.setFocusable(false); + moreButton = (ImageView) findViewById(R.id.more_button); - moreButton = (ImageView) findViewById(R.id.album_more); - moreButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - v.showContextMenu(); - } - }); + setClickable(true); + setLongClickable(true); + } + + public void setShowArtist(boolean showArtist) { + this.showArtist = showArtist; } protected void setObjectImpl(Object obj1, Object obj2) { this.album = (MusicDirectory.Entry) obj1; titleView.setText(album.getAlbumDisplay()); - String artist = album.getArtist(); - if(artist == null) { - artist = ""; - } - if(album.getYear() != null) { - artist += " - " + album.getYear(); + String artist = ""; + if(showArtist) { + artist = album.getArtist(); + if (artist == null) { + artist = ""; + } + if (album.getYear() != null) { + artist += " - " + album.getYear(); + } + } else if(album.getYear() != null) { + artist += album.getYear(); } - artistView.setText(artist); - artistView.setVisibility(album.getArtist() == null ? View.GONE : View.VISIBLE); + artistView.setText(album.getArtist() == null ? "" : artist); imageTask = ((ImageLoader)obj2).loadImage(coverArtView, album, false, true); file = null; } diff --git a/app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java b/app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java new file mode 100644 index 00000000..3bb3e8a1 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java @@ -0,0 +1,99 @@ +/* + 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.view; + +import android.graphics.Rect; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; +import android.view.View; + +public class GridSpacingDecoration extends RecyclerView.ItemDecoration { + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + + int spacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, view.getResources().getDisplayMetrics()); + int halfSpacing = spacing / 2; + + int childCount = parent.getChildCount(); + int childIndex = parent.getChildPosition(view); + int spanCount = getTotalSpan(view, parent); + int spanIndex = childIndex % spanCount; + int spanSize = getSpanSize(parent, childIndex); + + /* INVALID SPAN */ + if (spanCount < 1 || spanSize > 1) return; + + outRect.top = halfSpacing; + outRect.bottom = halfSpacing; + outRect.left = halfSpacing; + outRect.right = halfSpacing; + + if (isTopEdge(childIndex, spanCount)) { + outRect.top = spacing; + } + + if (isLeftEdge(spanIndex, spanCount)) { + outRect.left = spacing; + } + + if (isRightEdge(spanIndex, spanCount)) { + outRect.right = spacing; + } + + if (isBottomEdge(childIndex, childCount, spanCount)) { + outRect.bottom = spacing; + } + } + + protected int getTotalSpan(View view, RecyclerView parent) { + RecyclerView.LayoutManager mgr = parent.getLayoutManager(); + if (mgr instanceof GridLayoutManager) { + return ((GridLayoutManager) mgr).getSpanCount(); + } + + return -1; + } + protected int getSpanSize(RecyclerView parent, int childIndex) { + RecyclerView.LayoutManager mgr = parent.getLayoutManager(); + if (mgr instanceof GridLayoutManager) { + GridLayoutManager.SpanSizeLookup lookup = ((GridLayoutManager) mgr).getSpanSizeLookup(); + if(lookup != null) { + return lookup.getSpanSize(childIndex); + } + } + + return 1; + } + + protected boolean isLeftEdge(int spanIndex, int spanCount) { + return spanIndex == 0; + } + + protected boolean isRightEdge(int spanIndex, int spanCount) { + return spanIndex == spanCount - 1; + } + + protected boolean isTopEdge(int childIndex, int spanCount) { + return childIndex < spanCount; + } + + protected boolean isBottomEdge(int childIndex, int childCount, int spanCount) { + return childIndex >= childCount - spanCount; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/view/SongView.java b/app/src/main/java/github/daneren2005/dsub/view/SongView.java index 2fbaedc3..a5522719 100644 --- a/app/src/main/java/github/daneren2005/dsub/view/SongView.java +++ b/app/src/main/java/github/daneren2005/dsub/view/SongView.java @@ -83,18 +83,13 @@ public class SongView extends UpdateView implements Checkable { starButton.setFocusable(false); bookmarkButton = (ImageButton) findViewById(R.id.song_bookmark); bookmarkButton.setFocusable(false); - moreButton = (ImageView) findViewById(R.id.artist_more); - moreButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - v.showContextMenu(); - } - }); + moreButton = (ImageView) findViewById(R.id.more_button); bottomRowView = findViewById(R.id.song_bottom); } public void setObjectImpl(Object obj1, Object obj2) { this.song = (MusicDirectory.Entry) obj1; - boolean checkable = (Boolean) obj2; + checkable = (Boolean) obj2; StringBuilder artist = new StringBuilder(40); diff --git a/app/src/main/java/github/daneren2005/dsub/view/UnscrollableGridView.java b/app/src/main/java/github/daneren2005/dsub/view/UnscrollableGridView.java deleted file mode 100644 index 3047d5d7..00000000 --- a/app/src/main/java/github/daneren2005/dsub/view/UnscrollableGridView.java +++ /dev/null @@ -1,128 +0,0 @@ -package github.daneren2005.dsub.view; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.widget.AbsListView; -import android.widget.GridView; -import android.widget.ListAdapter; - -import java.lang.reflect.Field; - -/** - * Created by Scott on 4/26/2014. - */ -public class UnscrollableGridView extends GridView { - private static final String TAG = UnscrollableGridView.class.getSimpleName(); - - public UnscrollableGridView(Context context) { - super(context); - } - - public UnscrollableGridView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public UnscrollableGridView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public int getColumnWidth() { - // This method will be called from onMeasure() too. - // It's better to use getMeasuredWidth(), as it is safe in this case. - - int hSpacing = 20; - try { - Field field = GridView.class.getDeclaredField("mHorizontalSpacing"); - field.setAccessible(true); - hSpacing = field.getInt(this); - } catch(Exception e) { - - } - - final int totalHorizontalSpacing = getNumColumnsCompat() > 0 ? (getNumColumnsCompat() - 1) * hSpacing : 0; - return (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalHorizontalSpacing) / getNumColumnsCompat(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // Sets the padding for this view. - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - final int measuredWidth = getMeasuredWidth(); - final int childWidth = getColumnWidth(); - int childHeight = 0; - - // If there's an adapter, use it to calculate the height of this view. - final ListAdapter adapter = getAdapter(); - final int count; - - // There shouldn't be any inherent size (due to padding) if there are no child views. - if (adapter == null || (count = adapter.getCount()) == 0) { - setMeasuredDimension(0, 0); - return; - } - - // Get the first child from the adapter. - final View child = adapter.getView(0, null, this); - if (child != null) { - // Set a default LayoutParams on the child, if it doesn't have one on its own. - AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams(); - if (params == null) { - params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, - AbsListView.LayoutParams.WRAP_CONTENT); - child.setLayoutParams(params); - } - - // Measure the exact width of the child, and the height based on the width. - // Note: the child takes care of calculating its height. - int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); - int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - child.measure(childWidthSpec, childHeightSpec); - childHeight = child.getMeasuredHeight(); - } - - int vSpacing = 10; - try { - Field field = GridView.class.getDeclaredField("mVerticalSpacing"); - field.setAccessible(true); - vSpacing = field.getInt(this); - } catch(Exception e) { - - } - - // Number of rows required to 'mTotal' items. - final int rows = (int) Math.ceil((double) getCount() / getNumColumnsCompat()); - final int childrenHeight = childHeight * rows; - final int totalVerticalSpacing = rows > 0 ? (rows - 1) * vSpacing : 0; - - // Total height of this view. - final int measuredHeight = Math.abs(childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing); - setMeasuredDimension(measuredWidth, measuredHeight); - } - - private int getNumColumnsCompat() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - return getNumColumnsCompat11(); - } else { - int columns = 0; - int children = getChildCount(); - if (children > 0) { - int width = getChildAt(0).getMeasuredWidth(); - if (width > 0) { - columns = getWidth() / width; - } - } - return columns > 0 ? columns : AUTO_FIT; - } - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - private int getNumColumnsCompat11() { - return getNumColumns(); - } -} diff --git a/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java b/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java index f9c62121..bdcdc46f 100644 --- a/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java +++ b/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java @@ -23,6 +23,7 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.os.Handler; import android.os.Looper; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -65,6 +66,8 @@ public class UpdateView extends LinearLayout { protected SilentBackgroundTask<Void> imageTask = null; protected final boolean autoUpdate; + protected int position; + protected boolean checkable; public UpdateView(Context context) { this(context, true); @@ -224,8 +227,6 @@ public class UpdateView extends LinearLayout { MusicDirectory.Entry check = null; if(view instanceof SongView) { check = ((SongView) view).getEntry(); - } else if(view instanceof AlbumCell) { - check = ((AlbumCell) view).getEntry(); } else if(view instanceof AlbumView) { check = ((AlbumView) view).getEntry(); } @@ -283,4 +284,34 @@ public class UpdateView extends LinearLayout { rating = isRated; } } + + public void setPosition(int position) { + this.position = position; + } + public int getPosition() { + return position; + } + + public boolean isCheckable() { + return checkable; + } + + public static class UpdateViewHolder extends RecyclerView.ViewHolder { + private UpdateView updateView; + + public UpdateViewHolder(UpdateView itemView) { + super(itemView); + + this.updateView = itemView; + } + + // Different is so that call is not ambiguous + public UpdateViewHolder(View view, boolean different) { + super(view); + } + + public UpdateView getUpdateView() { + return updateView; + } + } } diff --git a/app/src/main/res/layout/album_cell_item.xml b/app/src/main/res/layout/album_cell_item.xml index 3f708e63..46e2b1a6 100644 --- a/app/src/main/res/layout/album_cell_item.xml +++ b/app/src/main/res/layout/album_cell_item.xml @@ -2,7 +2,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@drawable/abc_item_background_holo_light"> <RelativeLayout android:layout_width="match_parent" @@ -77,7 +78,7 @@ </LinearLayout> <ImageView - android:id="@+id/album_more" + android:id="@+id/more_button" android:src="?attr/download_none" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/album_list_item.xml b/app/src/main/res/layout/album_list_item.xml index 0ee92edd..67c052eb 100644 --- a/app/src/main/res/layout/album_list_item.xml +++ b/app/src/main/res/layout/album_list_item.xml @@ -65,7 +65,7 @@ android:visibility="gone"/> <ImageView - android:id="@+id/album_more" + android:id="@+id/more_button" android:src="?attr/download_none" android:layout_width="wrap_content" android:layout_height="fill_parent" diff --git a/app/src/main/res/layout/grid_view.xml b/app/src/main/res/layout/grid_view.xml deleted file mode 100644 index 599cf92c..00000000 --- a/app/src/main/res/layout/grid_view.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<github.daneren2005.dsub.view.HeaderGridView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/gridview" - android:layout_width="fill_parent" - android:layout_height="0dip" - android:layout_weight="1.0" - android:numColumns="@integer/Grid.Columns" - android:horizontalSpacing="10dp" - android:verticalSpacing="10dp" - android:gravity="center" - android:stretchMode="columnWidth" - android:padding="24px" - android:fastScrollEnabled="true" - android:scrollbarStyle="outsideOverlay"/>
\ No newline at end of file diff --git a/app/src/main/res/layout/select_album.xml b/app/src/main/res/layout/select_album.xml index bbdf0e54..cacbc560 100644 --- a/app/src/main/res/layout/select_album.xml +++ b/app/src/main/res/layout/select_album.xml @@ -17,12 +17,11 @@ <include layout="@layout/tab_progress"/> - <ListView + <android.support.v7.widget.RecyclerView android:id="@+id/select_album_entries" - android:textFilterEnabled="true" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1.0" - android:fastScrollEnabled="true"/> + android:scrollbars="vertical"/> </LinearLayout> </android.support.v4.widget.SwipeRefreshLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/song_list_item.xml b/app/src/main/res/layout/song_list_item.xml index 86f77869..26c89b2b 100644 --- a/app/src/main/res/layout/song_list_item.xml +++ b/app/src/main/res/layout/song_list_item.xml @@ -117,7 +117,7 @@ </LinearLayout> <ImageView - android:id="@+id/artist_more" + android:id="@+id/more_button" android:src="?attr/download_none" android:layout_width="wrap_content" android:layout_height="fill_parent" diff --git a/app/src/main/res/layout/unscrollable_grid_view.xml b/app/src/main/res/layout/unscrollable_grid_view.xml deleted file mode 100644 index 96bea5ce..00000000 --- a/app/src/main/res/layout/unscrollable_grid_view.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<github.daneren2005.dsub.view.UnscrollableGridView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/gridview" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:numColumns="@integer/Grid.Columns" - android:horizontalSpacing="10dp" - android:verticalSpacing="10dp" - android:gravity="center" - android:padding="20px" - android:stretchMode="columnWidth"/>
\ No newline at end of file |