diff options
author | Scott Jackson <daneren2005@gmail.com> | 2015-05-29 17:38:15 -0700 |
---|---|---|
committer | Scott Jackson <daneren2005@gmail.com> | 2015-05-29 17:38:15 -0700 |
commit | 7111cf31fefa5136c62f39befa9fb4ee214d47ca (patch) | |
tree | e31e164e33efb85d8d604ce501860dc953f083f6 /app | |
parent | cfa23075eb3c446fd22a67d782252781648e5dab (diff) | |
parent | 1697ead7480395a4850e2cfc06d2af2d58910d5a (diff) | |
download | dsub-7111cf31fefa5136c62f39befa9fb4ee214d47ca.tar.gz dsub-7111cf31fefa5136c62f39befa9fb4ee214d47ca.tar.bz2 dsub-7111cf31fefa5136c62f39befa9fb4ee214d47ca.zip |
Merge branch 'RecyclerView' into SlideUpPanel2
Diffstat (limited to 'app')
58 files changed, 2087 insertions, 2320 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 38d80ae8..2b3a5fbc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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/activity/SubsonicFragmentActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java index 4a0420ab..f3d62999 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java @@ -343,7 +343,6 @@ public class SubsonicFragmentActivity extends SubsonicActivity { if (query != null) { ((SearchFragment)currentFragment).search(query, autoplay); } else { - ((SearchFragment)currentFragment).populateList(); if (requestsearch) { onSearchRequested(); } 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/ArtistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java index 4d469faf..0563b38d 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java @@ -1,97 +1,128 @@ /* - This file is part of Subsonic. + 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 +*/ - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2010 (C) Sindre Mehus - */ package github.daneren2005.dsub.adapter; import android.content.Context; -import github.daneren2005.dsub.R; -import java.util.List; +import android.support.v7.widget.PopupMenu; +import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.SectionIndexer; +import android.widget.TextView; + +import java.util.List; + +import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.MusicFolder; +import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.view.ArtistView; +import github.daneren2005.dsub.view.UpdateView; + +public class ArtistAdapter extends SectionAdapter<Artist> { + public static int VIEW_TYPE_ARTIST = 4; + + private List<MusicFolder> musicFolders; + private OnMusicFolderChanged onMusicFolderChanged; + + public ArtistAdapter(Context context, List<Artist> artists, OnItemClickedListener listener) { + this(context, artists, null, listener, null); + } + + public ArtistAdapter(Context context, List<Artist> artists, List<MusicFolder> musicFolders, OnItemClickedListener onItemClickedListener, OnMusicFolderChanged onMusicFolderChanged) { + super(context, artists); + this.musicFolders = musicFolders; + this.onItemClickedListener = onItemClickedListener; + this.onMusicFolderChanged = onMusicFolderChanged; + + if(musicFolders != null) { + this.singleSectionHeader = true; + } + } + + @Override + public UpdateView.UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) { + final View header = LayoutInflater.from(context).inflate(R.layout.select_artist_header, parent, false); + header.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + PopupMenu popup = new PopupMenu(context, header); + + popup.getMenu().add(R.string.select_artist_all_folders); + for (MusicFolder musicFolder : musicFolders) { + popup.getMenu().add(musicFolder.getName()); + } + + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + for (MusicFolder musicFolder : musicFolders) { + if(item.getTitle().equals(musicFolder.getName())) { + if(onMusicFolderChanged != null) { + onMusicFolderChanged.onMusicFolderChanged(musicFolder); + } + return true; + } + } + + if(onMusicFolderChanged != null) { + onMusicFolderChanged.onMusicFolderChanged(null); + } + return true; + } + }); + popup.show(); + } + }); + + return new UpdateView.UpdateViewHolder(header, false); + } + @Override + public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header) { + TextView folderName = (TextView) holder.getView().findViewById(R.id.select_artist_folder_2); -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * @author Sindre Mehus - */ -public class ArtistAdapter extends ArrayAdapter<Artist> implements SectionIndexer { - - private final Context activity; - - // Both arrays are indexed by section ID. - private final Object[] sections; - private final Integer[] positions; - - public ArtistAdapter(Context activity, List<Artist> artists) { - super(activity, R.layout.basic_list_item, artists); - this.activity = activity; - - Set<String> sectionSet = new LinkedHashSet<String>(30); - List<Integer> positionList = new ArrayList<Integer>(30); - for (int i = 0; i < artists.size(); i++) { - Artist artist = artists.get(i); - String index = artist.getIndex(); - if (!sectionSet.contains(index)) { - sectionSet.add(index); - positionList.add(i); - } - } - sections = sectionSet.toArray(new Object[sectionSet.size()]); - positions = positionList.toArray(new Integer[positionList.size()]); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Artist entry = getItem(position); - ArtistView view; - if (convertView != null && convertView instanceof ArtistView) { - view = (ArtistView) convertView; + String musicFolderId = Util.getSelectedMusicFolderId(context); + if(musicFolderId != null) { + for (MusicFolder musicFolder : musicFolders) { + if (musicFolder.getId().equals(musicFolderId)) { + folderName.setText(musicFolder.getName()); + break; + } + } } else { - view = new ArtistView(activity); + folderName.setText(R.string.select_artist_all_folders); } - view.setObject(entry); - return view; - } - + } + + @Override + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new ArtistView(context)); + } + @Override - public Object[] getSections() { - return sections; - } - - @Override - public int getPositionForSection(int section) { - section = Math.min(section, positions.length - 1); - return positions[section]; - } - - @Override - public int getSectionForPosition(int pos) { - for (int i = 0; i < sections.length - 1; i++) { - if (pos < positions[i + 1]) { - return i; - } - } - return sections.length - 1; - } + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Artist item, int viewType) { + holder.getUpdateView().setObject(item); + } + + @Override + public int getItemViewType(Artist item) { + return VIEW_TYPE_ARTIST; + } + + public interface OnMusicFolderChanged { + void onMusicFolderChanged(MusicFolder musicFolder); + } } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/BasicListAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/BasicListAdapter.java new file mode 100644 index 00000000..dfea91bf --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/BasicListAdapter.java @@ -0,0 +1,48 @@ +/* + 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.view.ViewGroup; + +import java.util.List; + +import github.daneren2005.dsub.view.BasicListView; +import github.daneren2005.dsub.view.UpdateView; + +public class BasicListAdapter extends SectionAdapter<String> { + public static int VIEW_TYPE_LINE = 1; + + public BasicListAdapter(Context context, List<String> strings, OnItemClickedListener listener) { + super(context, strings); + this.onItemClickedListener = listener; + } + + @Override + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new BasicListView(context)); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, String item, int viewType) { + holder.getUpdateView().setObject(item); + } + + @Override + public int getItemViewType(String item) { + return VIEW_TYPE_LINE; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/BookmarkAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/BookmarkAdapter.java index 26d3e16a..8335966d 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/BookmarkAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/BookmarkAdapter.java @@ -1,20 +1,16 @@ /* - This file is part of Subsonic. - + This file is part of Subsonic. Subsonic is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - Subsonic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2010 (C) Sindre Mehus + Copyright 2015 (C) Scott Jackson */ package github.daneren2005.dsub.adapter; @@ -22,9 +18,7 @@ package github.daneren2005.dsub.adapter; import android.content.Context; import java.util.List; -import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.TextView; import github.daneren2005.dsub.R; @@ -32,33 +26,36 @@ import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.view.SongView; +import github.daneren2005.dsub.view.UpdateView; -public class BookmarkAdapter extends ArrayAdapter<MusicDirectory.Entry> { +public class BookmarkAdapter extends SectionAdapter<MusicDirectory.Entry> { private final static String TAG = BookmarkAdapter.class.getSimpleName(); - private Context activity; - public BookmarkAdapter(Context activity, List<MusicDirectory.Entry> bookmarks) { - super(activity, android.R.layout.simple_list_item_1, bookmarks); - this.activity = activity; + public BookmarkAdapter(Context activity, List<MusicDirectory.Entry> bookmarks, OnItemClickedListener listener) { + super(activity, bookmarks); + this.onItemClickedListener = listener; } - - public View getView(int position, View convertView, ViewGroup parent) { - MusicDirectory.Entry entry = getItem(position); - Bookmark bookmark = entry.getBookmark(); - SongView view; - if (convertView != null) { - view = (SongView) convertView; - } else { - view = new SongView(activity); - } - view.setObject(entry, false); - + @Override + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new SongView(context)); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, MusicDirectory.Entry item, int viewType) { + SongView songView = (SongView) holder.getUpdateView(); + Bookmark bookmark = item.getBookmark(); + + songView.setObject(item, false); + // Add current position to duration - TextView durationTextView = (TextView) view.findViewById(R.id.song_duration); + TextView durationTextView = (TextView) songView.findViewById(R.id.song_duration); String duration = durationTextView.getText().toString(); durationTextView.setText(Util.formatDuration(bookmark.getPosition() / 1000) + " / " + duration); - - return view; + } + + @Override + public int getItemViewType(MusicDirectory.Entry item) { + return EntryGridAdapter.VIEW_TYPE_SONG; } } 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..48b278ec --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java @@ -0,0 +1,133 @@ +/* + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2015 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.adapter; + +import android.content.Context; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +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 SectionAdapter<Entry> { + private static String TAG = EntryGridAdapter.class.getSimpleName(); + + public static int VIEW_TYPE_ALBUM_CELL = 1; + public static int VIEW_TYPE_ALBUM_LINE = 2; + public static int VIEW_TYPE_SONG = 3; + + private ImageLoader imageLoader; + private boolean largeAlbums; + private boolean showArtist = false; + private boolean checkable = true; + private View header; + + public EntryGridAdapter(Context context, List<Entry> entries, ImageLoader imageLoader, boolean largeCell) { + super(context, 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 onCreateSectionViewHolder(ViewGroup parent, int viewType) { + 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); + } + + return new UpdateViewHolder(updateView); + } + + @Override + public void onBindViewHolder(UpdateViewHolder holder, Entry entry, int viewType) { + UpdateView view = holder.getUpdateView(); + 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 && !entry.isVideo()); + } + } + + @Override + public void setChecked(UpdateView updateView, boolean checked) { + if(updateView instanceof SongView) { + ((SongView) updateView).setChecked(checked); + } + } + + public UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new UpdateViewHolder(header, false); + } + public void onBindHeaderHolder(UpdateViewHolder holder, String header) { + + } + + @Override + public int getItemViewType(Entry entry) { + if(entry.isDirectory()) { + if (largeAlbums) { + return VIEW_TYPE_ALBUM_CELL; + } else { + return VIEW_TYPE_ALBUM_LINE; + } + } else { + return VIEW_TYPE_SONG; + } + } + + public void setHeader(View header) { + this.header = header; + this.singleSectionHeader = true; + } + + public void setShowArtist(boolean showArtist) { + this.showArtist = showArtist; + } + public void setCheckable(boolean checkable) { + this.checkable = checkable; + } + + public void removeAt(int index) { + sections.get(0).remove(index); + notifyItemRemoved(index); + } +} 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..7b7dc6fc --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java @@ -0,0 +1,146 @@ +/* + 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.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +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.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; +import github.daneren2005.dsub.view.UpdateView; + +public class EntryInfiniteGridAdapter extends EntryGridAdapter { + public static int VIEW_TYPE_LOADING = 4; + + 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); + } + + @Override + public UpdateView.UpdateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if(viewType == VIEW_TYPE_LOADING) { + View progress = LayoutInflater.from(context).inflate(R.layout.tab_progress, null); + progress.setVisibility(View.VISIBLE); + return new UpdateView.UpdateViewHolder(progress, false); + } + + return super.onCreateViewHolder(parent, viewType); + } + + @Override + public int getItemViewType(int position) { + if(isLoadingView(position)) { + return VIEW_TYPE_LOADING; + } + + return super.getItemViewType(position); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, int position) { + if(!isLoadingView(position)) { + super.onBindViewHolder(holder, position); + } + } + + @Override + public int getItemCount() { + int size = super.getItemCount(); + + if(!allLoaded) { + size++; + } + + return size; + } + + 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(); + return null; + } + + @Override + protected void done(Void result) { + appendCachedData(newData); + loading = false; + + if(newData.isEmpty()) { + allLoaded = true; + notifyDataSetChanged(); + } + } + }.execute(); + } + + protected List<Entry> cacheInBackground() throws Exception { + MusicService service = MusicServiceFactory.getMusicService(context); + MusicDirectory result; + int offset = sections.get(0).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 = sections.get(0).size(); + sections.get(0).addAll(newData); + this.notifyItemRangeInserted(start, newData.size()); + } + } + + protected boolean isLoadingView(int position) { + return !allLoaded && position >= sections.get(0).size(); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/GenreAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/GenreAdapter.java index abb208c9..7e6954f9 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/GenreAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/GenreAdapter.java @@ -1,60 +1,48 @@ /* - 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/>. + 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 +*/ - Copyright 2010 (C) Sindre Mehus - */ package github.daneren2005.dsub.adapter; -import android.widget.ArrayAdapter; -import android.widget.SectionIndexer; import android.content.Context; -import android.view.View; import android.view.ViewGroup; -import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.view.GenreView; +import github.daneren2005.dsub.view.UpdateView; import java.util.List; -import java.util.Set; -import java.util.LinkedHashSet; -import java.util.ArrayList; -/** - * @author Sindre Mehus -*/ -public class GenreAdapter extends ArrayAdapter<Genre>{ - private Context activity; - private List<Genre> genres; - - public GenreAdapter(Context context, List<Genre> genres) { - super(context, android.R.layout.simple_list_item_1, genres); - this.activity = context; - this.genres = genres; +public class GenreAdapter extends SectionAdapter<Genre>{ + public static int VIEW_TYPE_GENRE = 1; + + public GenreAdapter(Context context, List<Genre> genres, OnItemClickedListener listener) { + super(context, genres); + this.onItemClickedListener = listener; } - + @Override - public View getView(int position, View convertView, ViewGroup parent) { - Genre genre = genres.get(position); - GenreView view; - if (convertView != null && convertView instanceof GenreView) { - view = (GenreView) convertView; - } else { - view = new GenreView(activity); - } - view.setObject(genre); - return view; - } + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new GenreView(context)); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Genre item, int viewType) { + holder.getUpdateView().setObject(item); + } + + @Override + public int getItemViewType(Genre item) { + return VIEW_TYPE_GENRE; + } } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java index d56a6b97..fa00c1dd 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java @@ -1,70 +1,54 @@ /* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2010 (C) Sindre Mehus - */ + 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 github.daneren2005.dsub.R; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.view.PlaylistView; +import github.daneren2005.dsub.view.UpdateView; -import java.util.Collections; -import java.util.Comparator; - -/** - * @author Sindre Mehus - */ -public class PlaylistAdapter extends ArrayAdapter<Playlist> { +public class PlaylistAdapter extends SectionAdapter<Playlist> { + public static int VIEW_TYPE_PLAYLIST = 1; - private final Context activity; - - public PlaylistAdapter(Context activity, List<Playlist> Playlists) { - super(activity, R.layout.basic_list_item, Playlists); - this.activity = activity; + public PlaylistAdapter(Context context, List<Playlist> playlists, OnItemClickedListener listener) { + super(context, playlists); + this.onItemClickedListener = listener; + } + public PlaylistAdapter(Context context, List<String> headers, List<List<Playlist>> sections, OnItemClickedListener listener) { + super(context, headers, sections); + this.onItemClickedListener = listener; } @Override - public View getView(int position, View convertView, ViewGroup parent) { - Playlist entry = getItem(position); - PlaylistView view; - if (convertView != null && convertView instanceof PlaylistView) { - view = (PlaylistView) convertView; - } else { - view = new PlaylistView(activity); - } - view.setObject(entry); - return view; + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new PlaylistView(context)); } - public static class PlaylistComparator implements Comparator<Playlist> { - @Override - public int compare(Playlist playlist1, Playlist playlist2) { - return playlist1.getName().compareToIgnoreCase(playlist2.getName()); - } - - public static List<Playlist> sort(List<Playlist> playlists) { - Collections.sort(playlists, new PlaylistComparator()); - return playlists; - } + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Playlist playlist, int viewType) { + holder.getUpdateView().setObject(playlist); + holder.setItem(playlist); + } + @Override + public int getItemViewType(Playlist playlist) { + return VIEW_TYPE_PLAYLIST; } } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java index 8ee39a10..dc94178d 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java @@ -1,60 +1,47 @@ /* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2010 (C) Sindre Mehus - */ + 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.widget.ArrayAdapter; -import android.widget.SectionIndexer; import android.content.Context; -import android.view.View; import android.view.ViewGroup; -import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.PodcastChannel; import github.daneren2005.dsub.view.PodcastChannelView; +import github.daneren2005.dsub.view.UpdateView; import java.util.List; -import java.util.Set; -import java.util.LinkedHashSet; -import java.util.ArrayList; -/** - * @author Sindre Mehus -*/ -public class PodcastChannelAdapter extends ArrayAdapter<PodcastChannel>{ - private Context activity; - private List<PodcastChannel> podcasts; +public class PodcastChannelAdapter extends SectionAdapter<PodcastChannel>{ + public static int VIEW_TYPE_PODCAST = 1; - public PodcastChannelAdapter(Context context, List<PodcastChannel> podcasts) { - super(context, android.R.layout.simple_list_item_1, podcasts); - this.activity = context; - this.podcasts = podcasts; + public PodcastChannelAdapter(Context context, List<PodcastChannel> podcasts, OnItemClickedListener listener) { + super(context, podcasts); + this.onItemClickedListener = listener; } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - PodcastChannel podcast = podcasts.get(position); - PodcastChannelView view; - if (convertView != null && convertView instanceof PodcastChannelView) { - view = (PodcastChannelView) convertView; - } else { - view = new PodcastChannelView(activity); - } - view.setObject(podcast); - return view; + + @Override + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new PodcastChannelView(context)); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, PodcastChannel item, int viewType) { + holder.getUpdateView().setObject(item); + } + + @Override + public int getItemViewType(PodcastChannel item) { + return VIEW_TYPE_PODCAST; } } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java new file mode 100644 index 00000000..f395216e --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java @@ -0,0 +1,114 @@ +/* + 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.content.res.Resources; +import android.view.ViewGroup; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.domain.MusicDirectory.Entry; +import github.daneren2005.dsub.domain.SearchResult; +import github.daneren2005.dsub.util.ImageLoader; +import github.daneren2005.dsub.view.AlbumView; +import github.daneren2005.dsub.view.ArtistView; +import github.daneren2005.dsub.view.SongView; +import github.daneren2005.dsub.view.UpdateView; + +import static github.daneren2005.dsub.adapter.ArtistAdapter.VIEW_TYPE_ARTIST; +import static github.daneren2005.dsub.adapter.EntryGridAdapter.VIEW_TYPE_ALBUM_CELL; +import static github.daneren2005.dsub.adapter.EntryGridAdapter.VIEW_TYPE_ALBUM_LINE; +import static github.daneren2005.dsub.adapter.EntryGridAdapter.VIEW_TYPE_SONG; + +public class SearchAdapter extends SectionAdapter<Serializable> { + private SearchResult searchResult; + private ImageLoader imageLoader; + private boolean largeAlbums; + + public SearchAdapter(Context context, SearchResult searchResult, ImageLoader imageLoader, boolean largeAlbums, OnItemClickedListener listener) { + this.context = context; + this.searchResult = searchResult; + this.imageLoader = imageLoader; + this.largeAlbums = largeAlbums; + + this.sections = new ArrayList<>(); + this.headers = new ArrayList<>(); + Resources res = context.getResources(); + if(!searchResult.getArtists().isEmpty()) { + this.sections.add((List<Serializable>) (List<?>) searchResult.getArtists()); + this.headers.add(res.getString(R.string.search_artists)); + } + if(!searchResult.getAlbums().isEmpty()) { + this.sections.add((List<Serializable>) (List<?>) searchResult.getAlbums()); + this.headers.add(res.getString(R.string.search_albums)); + } + if(!searchResult.getSongs().isEmpty()) { + this.sections.add((List<Serializable>) (List<?>) searchResult.getSongs()); + this.headers.add(res.getString(R.string.search_songs)); + } + this.onItemClickedListener = listener; + } + + @Override + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + UpdateView updateView = null; + if(viewType == VIEW_TYPE_ALBUM_CELL || viewType == VIEW_TYPE_ALBUM_LINE) { + updateView = new AlbumView(context, viewType == VIEW_TYPE_ALBUM_CELL); + } else if(viewType == VIEW_TYPE_SONG) { + updateView = new SongView(context); + } else if(viewType == VIEW_TYPE_ARTIST) { + updateView = new ArtistView(context); + } + + return new UpdateView.UpdateViewHolder(updateView); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Serializable item, int viewType) { + UpdateView view = holder.getUpdateView(); + if(viewType == VIEW_TYPE_ALBUM_CELL || viewType == VIEW_TYPE_ALBUM_LINE) { + AlbumView albumView = (AlbumView) view; + albumView.setObject(item, imageLoader); + } else if(viewType == VIEW_TYPE_SONG) { + SongView songView = (SongView) view; + songView.setObject(item, false); + } else if(viewType == VIEW_TYPE_ARTIST) { + view.setObject(item); + } + } + + @Override + public int getItemViewType(Serializable item) { + if(item instanceof Entry) { + Entry entry = (Entry) item; + if (entry.isDirectory()) { + if (largeAlbums) { + return VIEW_TYPE_ALBUM_CELL; + } else { + return VIEW_TYPE_ALBUM_LINE; + } + } else { + return VIEW_TYPE_SONG; + } + } else { + return VIEW_TYPE_ARTIST; + } + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java new file mode 100644 index 00000000..8d9a8682 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java @@ -0,0 +1,292 @@ +/* + 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.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.view.BasicHeaderView; +import github.daneren2005.dsub.view.UpdateView; +import github.daneren2005.dsub.view.UpdateView.UpdateViewHolder; + +public abstract class SectionAdapter<T> extends RecyclerView.Adapter<UpdateViewHolder<T>> { + private static String TAG = SectionAdapter.class.getSimpleName(); + public static int VIEW_TYPE_HEADER = 0; + + protected Context context; + protected List<String> headers; + protected List<List<T>> sections; + protected boolean singleSectionHeader; + protected OnItemClickedListener<T> onItemClickedListener; + protected UpdateView contextView; + protected T contextItem; + private List<T> selected = new ArrayList<>(); + + protected SectionAdapter() {} + public SectionAdapter(Context context, List<T> section) { + this(context, section, false); + } + public SectionAdapter(Context context, List<T> section, boolean singleSectionHeader) { + this.context = context; + this.headers = Arrays.asList("Section"); + this.sections = new ArrayList<>(); + this.sections.add(section); + this.singleSectionHeader = singleSectionHeader; + } + public SectionAdapter(Context context, List<String> headers, List<List<T>> sections) { + this(context, headers, sections, true); + } + public SectionAdapter(Context context, List<String> headers, List<List<T>> sections, boolean singleSectionHeader){ + this.context = context; + this.headers = headers; + this.sections = sections; + this.singleSectionHeader = singleSectionHeader; + } + + @Override + public UpdateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if(viewType == VIEW_TYPE_HEADER) { + return onCreateHeaderHolder(parent); + } else { + final UpdateViewHolder<T> holder = onCreateSectionViewHolder(parent, viewType); + final UpdateView updateView = holder.getUpdateView(); + + if(updateView != null) { + updateView.getChildAt(0).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + T item = holder.getItem(); + if (updateView.isCheckable()) { + if (selected.contains(item)) { + selected.remove(item); + setChecked(updateView, false); + } else { + selected.add(item); + setChecked(updateView, true); + } + } else if (onItemClickedListener != null) { + onItemClickedListener.onItemClicked(item); + } + } + }); + + View moreButton = updateView.findViewById(R.id.more_button); + if(moreButton == null) { + moreButton = updateView.findViewById(R.id.item_more); + } + if (moreButton != null) { + moreButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + T item = holder.getItem(); + setContextItem(updateView, item); + v.showContextMenu(); + } + }); + + /*updateView.getChildAt(0).setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + T item = holder.getItem(); + setContextItem(updateView, item); + v.showContextMenu(); + return false; + } + });*/ + } + } + + return holder; + } + } + + @Override + public void onBindViewHolder(UpdateViewHolder holder, int position) { + UpdateView updateView = holder.getUpdateView(); + + if(sections.size() == 1 && !singleSectionHeader) { + T item = sections.get(0).get(position); + onBindViewHolder(holder, item, getItemViewType(position)); + if(updateView.isCheckable()) { + setChecked(updateView, selected.contains(item)); + } + holder.setItem(item); + return; + } + + int subPosition = 0; + for(List<T> section: sections) { + if(position == subPosition) { + int index = sections.indexOf(section); + onBindHeaderHolder(holder, headers.get(index)); + return; + } + + if(position <= (subPosition + section.size())) { + T item = section.get(position - subPosition - 1); + onBindViewHolder(holder, item, getItemViewType(item)); + + if(updateView.isCheckable()) { + setChecked(updateView, selected.contains(item)); + } + holder.setItem(item); + return; + } + + subPosition += section.size() + 1; + } + } + + @Override + public int getItemCount() { + if(sections.size() == 1 && !singleSectionHeader) { + return sections.get(0).size(); + } + + int count = headers.size(); + for(List<T> section: sections) { + count += section.size(); + } + + return count; + } + + @Override + public int getItemViewType(int position) { + if(sections.size() == 1 && !singleSectionHeader) { + return getItemViewType(sections.get(0).get(position)); + } + + int subPosition = 0; + for(List<T> section: sections) { + if(position == subPosition) { + return VIEW_TYPE_HEADER; + } + + if(position <= (subPosition + section.size())) { + return getItemViewType(section.get(position - subPosition - 1)); + } + + subPosition += section.size() + 1; + } + + return -1; + } + + public UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new UpdateViewHolder(new BasicHeaderView(context)); + } + public void onBindHeaderHolder(UpdateViewHolder holder, String header) { + UpdateView view = holder.getUpdateView(); + if(view != null) { + view.setObject(header); + } + } + + public T getItemForPosition(int position) { + if(sections.size() == 1 && !singleSectionHeader) { + return sections.get(0).get(position); + } + + int subPosition = 0; + for(List<T> section: sections) { + if(position == subPosition) { + return null; + } + + if(position <= (subPosition + section.size())) { + return section.get(position - subPosition - 1); + } + + subPosition += section.size() + 1; + } + + return null; + } + + public void setContextItem(UpdateView updateView, T item) { + contextView = updateView; + contextItem = item; + } + public UpdateView getContextView() { + return contextView; + } + public T getContextItem() { + return contextItem; + } + + public void setOnItemClickedListener(OnItemClickedListener<T> onItemClickedListener) { + this.onItemClickedListener = onItemClickedListener; + } + + public void addSelected(T item) { + selected.add(item); + } + public List<T> getSelected() { + List<T> selected = new ArrayList<>(); + selected.addAll(this.selected); + return selected; + } + + public void clearSelected() { + // TODO: This needs to work with multiple sections + for(T item: selected) { + int index = sections.get(0).indexOf(item); + + if(singleSectionHeader) { + index++; + } + + this.notifyItemChanged(index); + } + selected.clear(); + } + + public void removeItem(T item) { + int subPosition = 0; + for(List<T> section: sections) { + if(sections.size() > 1 || singleSectionHeader) { + subPosition++; + } + + int index = section.indexOf(item); + if (index != -1) { + section.remove(item); + notifyItemRemoved(subPosition + index); + break; + } + + subPosition += section.size(); + } + } + + public abstract UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType); + public abstract void onBindViewHolder(UpdateViewHolder holder, T item, int viewType); + public abstract int getItemViewType(T item); + public void setChecked(UpdateView updateView, boolean checked) {} + + public interface OnItemClickedListener<T> { + void onItemClicked(T item); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/ShareAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/ShareAdapter.java index 4121a85a..6db3d927 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/ShareAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/ShareAdapter.java @@ -1,56 +1,49 @@ /* - 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/>. + 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 +*/ - Copyright 2010 (C) Sindre Mehus - */ 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.Share; import github.daneren2005.dsub.view.ShareView; +import github.daneren2005.dsub.view.UpdateView; -/** - * @author Sindre Mehus -*/ -public class ShareAdapter extends ArrayAdapter<Share>{ - private Context activity; - private List<Share> shares; - - public ShareAdapter(Context context, List<Share> shares) { - super(context, android.R.layout.simple_list_item_1, shares); - this.activity = context; - this.shares = shares; +public class ShareAdapter extends SectionAdapter<Share>{ + public static int VIEW_TYPE_SHARE = 1; + + public ShareAdapter(Context context, List<Share> shares, OnItemClickedListener listener) { + super(context, shares); + this.onItemClickedListener = listener; } - + @Override - public View getView(int position, View convertView, ViewGroup parent) { - Share share = shares.get(position); - ShareView view; - if (convertView != null && convertView instanceof ShareView) { - view = (ShareView) convertView; - } else { - view = new ShareView(activity); - } - view.setObject(share); - return view; - } + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new ShareView(context)); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Share item, int viewType) { + holder.getUpdateView().setObject(item); + } + + @Override + public int getItemViewType(Share item) { + return VIEW_TYPE_SHARE; + } } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/UserAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/UserAdapter.java index f0f78d97..95809e48 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/UserAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/UserAdapter.java @@ -25,28 +25,32 @@ import java.util.List; import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.User; import github.daneren2005.dsub.util.ImageLoader; +import github.daneren2005.dsub.view.UpdateView; import github.daneren2005.dsub.view.UserView; -public class UserAdapter extends ArrayAdapter<User> { - private final Context activity; +public class UserAdapter extends SectionAdapter<User> { + public static int VIEW_TYPE_USER = 1; + private final ImageLoader imageLoader; - public UserAdapter(Context activity, List<User> users, ImageLoader imageLoader) { - super(activity, R.layout.basic_list_item, users); - this.activity = activity; + public UserAdapter(Context context, List<User> users, ImageLoader imageLoader, OnItemClickedListener listener) { + super(context, users); this.imageLoader = imageLoader; + this.onItemClickedListener = listener; + } + + @Override + public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new UpdateView.UpdateViewHolder(new UserView(context)); + } + + @Override + public void onBindViewHolder(UpdateView.UpdateViewHolder holder, User item, int viewType) { + holder.getUpdateView().setObject(item, imageLoader); } @Override - public View getView(int position, View convertView, ViewGroup parent) { - User entry = getItem(position); - UserView view; - if (convertView != null && convertView instanceof UserView) { - view = (UserView) convertView; - } else { - view = new UserView(activity); - } - view.setObject(entry, imageLoader); - return view; + public int getItemViewType(User item) { + return VIEW_TYPE_USER; } }
\ No newline at end of file diff --git a/app/src/main/java/github/daneren2005/dsub/domain/Playlist.java b/app/src/main/java/github/daneren2005/dsub/domain/Playlist.java index 7cd820c0..99b85ce9 100644 --- a/app/src/main/java/github/daneren2005/dsub/domain/Playlist.java +++ b/app/src/main/java/github/daneren2005/dsub/domain/Playlist.java @@ -19,6 +19,9 @@ package github.daneren2005.dsub.domain; import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; /** * @author Sindre Mehus @@ -125,4 +128,16 @@ public class Playlist implements Serializable { Playlist playlist = (Playlist) o; return playlist.id.equals(this.id); } + + public static class PlaylistComparator implements Comparator<Playlist> { + @Override + public int compare(Playlist playlist1, Playlist playlist2) { + return playlist1.getName().compareToIgnoreCase(playlist2.getName()); + } + + public static List<Playlist> sort(List<Playlist> playlists) { + Collections.sort(playlists, new PlaylistComparator()); + return playlists; + } + } } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java index 66ce5f15..f3f9eb64 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.List; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.User; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.parser.SubsonicRESTException; @@ -37,7 +38,7 @@ import github.daneren2005.dsub.util.UserUtil; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.adapter.UserAdapter; -public class AdminFragment extends SelectListFragment<User> { +public class AdminFragment extends SelectRecyclerFragment<User> { private static String TAG = AdminFragment.class.getSimpleName(); @Override @@ -69,8 +70,7 @@ public class AdminFragment extends SelectListFragment<User> { @Override public boolean onContextItemSelected(MenuItem menuItem) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - User user = objects.get(info.position); + User user = adapter.getContextItem(); switch(menuItem.getItemId()) { case R.id.admin_change_email: @@ -97,8 +97,8 @@ public class AdminFragment extends SelectListFragment<User> { } @Override - public ArrayAdapter getAdapter(List<User> objs) { - return new UserAdapter(context, objs, getImageLoader()); + public SectionAdapter getAdapter(List<User> objs) { + return new UserAdapter(context, objs, getImageLoader(), this); } @Override @@ -134,9 +134,7 @@ public class AdminFragment extends SelectListFragment<User> { } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - User user = (User) parent.getItemAtPosition(position); - + public void onItemClicked(User user) { SubsonicFragment fragment = new UserFragment(); Bundle args = new Bundle(); args.putSerializable(Constants.INTENT_EXTRA_NAME_ID, user); diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java index 0f1598dd..20a87e7d 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java @@ -1,12 +1,14 @@ package github.daneren2005.dsub.fragments; -import java.util.ArrayList; -import java.util.List; +import java.io.Serializable; import java.util.Arrays; import android.content.Intent; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; @@ -14,11 +16,13 @@ import android.view.MenuInflater; import android.view.View; import android.view.MenuItem; import android.widget.AdapterView; -import android.widget.ListAdapter; -import android.widget.ListView; import android.net.Uri; import android.view.ViewGroup; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.ArtistAdapter; +import github.daneren2005.dsub.adapter.EntryGridAdapter; +import github.daneren2005.dsub.adapter.SearchAdapter; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.SearchCritera; @@ -26,40 +30,24 @@ import github.daneren2005.dsub.domain.SearchResult; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.service.DownloadService; -import github.daneren2005.dsub.adapter.ArtistAdapter; import github.daneren2005.dsub.util.BackgroundTask; import github.daneren2005.dsub.util.Constants; -import github.daneren2005.dsub.adapter.EntryAdapter; -import github.daneren2005.dsub.adapter.MergeAdapter; import github.daneren2005.dsub.util.TabBackgroundTask; import github.daneren2005.dsub.util.Util; +import github.daneren2005.dsub.view.UpdateView; -public class SearchFragment extends SubsonicFragment { +public class SearchFragment extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<Serializable> { private static final String TAG = SearchFragment.class.getSimpleName(); - private static final int DEFAULT_ARTISTS = 3; - private static final int DEFAULT_ALBUMS = 5; - private static final int DEFAULT_SONGS = 10; - private static final int MAX_ARTISTS = 10; - private static final int MAX_ALBUMS = 20; + private static final int MAX_ALBUMS = 10; private static final int MAX_SONGS = 25; - private ListView list; - - private View artistsHeading; - private View albumsHeading; - private View songsHeading; - private View moreArtistsButton; - private View moreAlbumsButton; - private View moreSongsButton; + + protected RecyclerView recyclerView; + protected SearchAdapter adapter; + protected boolean largeAlbums = false; + private SearchResult searchResult; - private MergeAdapter mergeAdapter; - private ArtistAdapter artistAdapter; - private ListAdapter moreArtistsAdapter; - private EntryAdapter albumAdapter; - private ListAdapter moreAlbumsAdapter; - private ListAdapter moreSongsAdapter; - private EntryAdapter songAdapter; private boolean skipSearch = false; private String currentQuery; @@ -70,6 +58,7 @@ public class SearchFragment extends SubsonicFragment { if(savedInstanceState != null) { searchResult = (SearchResult) savedInstanceState.getSerializable(Constants.FRAGMENT_LIST); } + largeAlbums = Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true); } @Override @@ -80,63 +69,43 @@ public class SearchFragment extends SubsonicFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { - rootView = inflater.inflate(R.layout.abstract_list_fragment, container, false); + rootView = inflater.inflate(R.layout.abstract_recycler_fragment, container, false); setTitle(R.string.search_title); - View buttons = inflater.inflate(R.layout.search_buttons, null); - - artistsHeading = buttons.findViewById(R.id.search_artists); - albumsHeading = buttons.findViewById(R.id.search_albums); - songsHeading = buttons.findViewById(R.id.search_songs); - - moreArtistsButton = buttons.findViewById(R.id.search_more_artists); - moreAlbumsButton = buttons.findViewById(R.id.search_more_albums); - moreSongsButton = buttons.findViewById(R.id.search_more_songs); - refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout); refreshLayout.setEnabled(false); - list = (ListView) rootView.findViewById(R.id.fragment_list); + recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler); + setupLayoutManager(recyclerView, largeAlbums); - list.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (view == moreArtistsButton) { - expandArtists(); - } else if (view == moreAlbumsButton) { - expandAlbums(); - } else if (view == moreSongsButton) { - expandSongs(); - } else { - Object item = parent.getItemAtPosition(position); - if (item instanceof Artist) { - onArtistSelected((Artist) item, false); - } else if (item instanceof MusicDirectory.Entry) { - MusicDirectory.Entry entry = (MusicDirectory.Entry) item; - if (entry.isDirectory()) { - onAlbumSelected(entry, false); - } else if (entry.isVideo()) { - onVideoSelected(entry); - } else { - onSongSelected(entry, false, true, true, false); - } - - } - } - } - }); - registerForContextMenu(list); + registerForContextMenu(recyclerView); context.onNewIntent(context.getIntent()); if(searchResult != null) { skipSearch = true; - populateList(); + recyclerView.setAdapter(adapter = new SearchAdapter(context, searchResult, getImageLoader(), largeAlbums, this)); } return rootView; } @Override + public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() { + final int columns = getRecyclerColumnCount(); + return new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + int viewType = adapter.getItemViewType(position); + if(viewType == EntryGridAdapter.VIEW_TYPE_SONG || viewType == EntryGridAdapter.VIEW_TYPE_HEADER || viewType == ArtistAdapter.VIEW_TYPE_ARTIST) { + return columns; + } else { + return 1; + } + } + }; + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { menuInflater.inflate(R.menu.search, menu); } @@ -156,11 +125,12 @@ public class SearchFragment extends SubsonicFragment { @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); + UpdateView targetView = adapter.getContextView(); + menuInfo = new AdapterView.AdapterContextMenuInfo(targetView, 0, 0); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Object selectedItem = list.getItemAtPosition(info.position); - onCreateContextMenu(menu, view, menuInfo, selectedItem); - if(selectedItem instanceof MusicDirectory.Entry && !((MusicDirectory.Entry) selectedItem).isVideo() && !Util.isOffline(context)) { + Serializable item = adapter.getContextItem(); + onCreateContextMenu(menu, view, menuInfo, item); + if(item instanceof MusicDirectory.Entry && !((MusicDirectory.Entry) item).isVideo() && !Util.isOffline(context)) { menu.removeItem(R.id.song_menu_remove_playlist); } @@ -172,27 +142,37 @@ public class SearchFragment extends SubsonicFragment { if(menuItem.getGroupId() != getSupportTag()) { return false; } - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Object selectedItem = list.getItemAtPosition(info.position); - - if(onContextItemSelected(menuItem, selectedItem)) { + + Serializable item = adapter.getContextItem(); + if(onContextItemSelected(menuItem, item)) { return true; } return true; } - - @Override - public void setPrimaryFragment(boolean primary) { - super.setPrimaryFragment(primary); - } @Override public void refresh(boolean refresh) { context.onNewIntent(context.getIntent()); } + @Override + public void onItemClicked(Serializable item) { + Log.d(TAG, item.getClass().getSimpleName()); + if (item instanceof Artist) { + onArtistSelected((Artist) item, false); + } else if (item instanceof MusicDirectory.Entry) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) item; + if (entry.isDirectory()) { + onAlbumSelected(entry, false); + } else if (entry.isVideo()) { + onVideoSelected(entry); + } else { + onSongSelected(entry, false, true, true, false); + } + } + } + public void search(final String query, final boolean autoplay) { if(skipSearch) { skipSearch = false; @@ -200,9 +180,6 @@ public class SearchFragment extends SubsonicFragment { } currentQuery = query; - mergeAdapter = new MergeAdapter(); - list.setAdapter(mergeAdapter); - BackgroundTask<SearchResult> task = new TabBackgroundTask<SearchResult>(this) { @Override protected SearchResult doInBackground() throws Throwable { @@ -214,7 +191,7 @@ public class SearchFragment extends SubsonicFragment { @Override protected void done(SearchResult result) { searchResult = result; - populateList(); + recyclerView.setAdapter(adapter = new SearchAdapter(context, searchResult, getImageLoader(), largeAlbums, SearchFragment.this)); if (autoplay) { autoplay(query); } @@ -224,82 +201,6 @@ public class SearchFragment extends SubsonicFragment { task.execute(); } - public void populateList() { - mergeAdapter = new MergeAdapter(); - - if (searchResult != null) { - List<Artist> artists = searchResult.getArtists(); - if (!artists.isEmpty()) { - mergeAdapter.addView(artistsHeading); - List<Artist> displayedArtists = new ArrayList<Artist>(artists.subList(0, Math.min(DEFAULT_ARTISTS, artists.size()))); - artistAdapter = new ArtistAdapter(context, displayedArtists); - mergeAdapter.addAdapter(artistAdapter); - if (artists.size() > DEFAULT_ARTISTS) { - moreArtistsAdapter = mergeAdapter.addView(moreArtistsButton, true); - } - } - - List<MusicDirectory.Entry> albums = searchResult.getAlbums(); - if (!albums.isEmpty()) { - mergeAdapter.addView(albumsHeading); - List<MusicDirectory.Entry> displayedAlbums = new ArrayList<MusicDirectory.Entry>(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size()))); - albumAdapter = new EntryAdapter(context, getImageLoader(), displayedAlbums, false); - mergeAdapter.addAdapter(albumAdapter); - if (albums.size() > DEFAULT_ALBUMS) { - moreAlbumsAdapter = mergeAdapter.addView(moreAlbumsButton, true); - } - } - - List<MusicDirectory.Entry> songs = searchResult.getSongs(); - if (!songs.isEmpty()) { - mergeAdapter.addView(songsHeading); - List<MusicDirectory.Entry> displayedSongs = new ArrayList<MusicDirectory.Entry>(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size()))); - songAdapter = new EntryAdapter(context, getImageLoader(), displayedSongs, false); - mergeAdapter.addAdapter(songAdapter); - if (songs.size() > DEFAULT_SONGS) { - moreSongsAdapter = mergeAdapter.addView(moreSongsButton, true); - } - } - - boolean empty = searchResult.getArtists().isEmpty() && searchResult.getAlbums().isEmpty() && searchResult.getSongs().isEmpty(); - if(empty) { - setEmpty(true); - } - } - - list.setAdapter(mergeAdapter); - } - - private void expandArtists() { - artistAdapter.clear(); - for (Artist artist : searchResult.getArtists()) { - artistAdapter.add(artist); - } - artistAdapter.notifyDataSetChanged(); - mergeAdapter.removeAdapter(moreArtistsAdapter); - mergeAdapter.notifyDataSetChanged(); - } - - private void expandAlbums() { - albumAdapter.clear(); - for (MusicDirectory.Entry album : searchResult.getAlbums()) { - albumAdapter.add(album); - } - albumAdapter.notifyDataSetChanged(); - mergeAdapter.removeAdapter(moreAlbumsAdapter); - mergeAdapter.notifyDataSetChanged(); - } - - private void expandSongs() { - songAdapter.clear(); - for (MusicDirectory.Entry song : searchResult.getSongs()) { - songAdapter.add(song); - } - songAdapter.notifyDataSetChanged(); - mergeAdapter.removeAdapter(moreSongsAdapter); - mergeAdapter.notifyDataSetChanged(); - } - private void onArtistSelected(Artist artist, boolean autoplay) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java index 5488c95b..065d622f 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java @@ -11,10 +11,11 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.ArrayAdapter; import android.widget.LinearLayout; -import android.widget.TextView; + import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.ArtistAdapter; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.Indexes; import github.daneren2005.dsub.domain.MusicDirectory; @@ -23,19 +24,16 @@ import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.Util; -import github.daneren2005.dsub.adapter.ArtistAdapter; +import github.daneren2005.dsub.view.UpdateView; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class SelectArtistFragment extends SelectListFragment<Artist> { +public class SelectArtistFragment extends SelectRecyclerFragment<Artist> implements ArtistAdapter.OnMusicFolderChanged { private static final String TAG = SelectArtistFragment.class.getSimpleName(); private static final int MENU_GROUP_MUSIC_FOLDER = 10; - private View folderButtonParent; - private View folderButton; - private TextView folderName; private List<MusicFolder> musicFolders = null; private List<MusicDirectory.Entry> entries; private String groupId; @@ -75,19 +73,7 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { } } - folderButton = null; super.onCreateView(inflater, container, bundle); - - if("4.4.2".equals(Build.VERSION.RELEASE)) { - listView.setFastScrollAlwaysVisible(true); - } - - if(objects != null && currentTask == null) { - if (Util.isOffline(context) || Util.isTagBrowsing(context) || groupId != null) { - folderButton.setVisibility(View.GONE); - } - setMusicFolders(); - } return rootView; } @@ -95,30 +81,12 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); + UpdateView targetView = adapter.getContextView(); + menuInfo = new AdapterView.AdapterContextMenuInfo(targetView, 0, 0); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Object entry = listView.getItemAtPosition(info.position); - - if (entry instanceof Artist) { - onCreateContextMenu(menu, view, menuInfo, entry); - } else if (info.position == 0) { - String musicFolderId = Util.getSelectedMusicFolderId(context); - MenuItem menuItem = menu.add(MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders); - if (musicFolderId == null) { - menuItem.setChecked(true); - } - if (musicFolders != null) { - for (int i = 0; i < musicFolders.size(); i++) { - MusicFolder musicFolder = musicFolders.get(i); - menuItem = menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, musicFolder.getName()); - if (musicFolder.getId().equals(musicFolderId)) { - menuItem.setChecked(true); - } - } - } - menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true); - } + Artist artist = adapter.getContextItem(); + onCreateContextMenu(menu, view, menuInfo, artist); recreateContextMenu(menu); } @@ -129,56 +97,33 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { } AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Artist artist = (Artist) listView.getItemAtPosition(info.position); - - if (artist != null) { - return onContextItemSelected(menuItem, artist); - } else if (info.position == 0) { - MusicFolder selectedFolder = menuItem.getItemId() == -1 ? null : musicFolders.get(menuItem.getItemId()); - String musicFolderId = selectedFolder == null ? null : selectedFolder.getId(); - String musicFolderName = selectedFolder == null ? context.getString(R.string.select_artist_all_folders) - : selectedFolder.getName(); - Util.setSelectedMusicFolderId(context, musicFolderId); - folderName.setText(musicFolderName); - context.invalidate(); - } + Artist artist = adapter.getContextItem(); - return true; + return onContextItemSelected(menuItem, artist); } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (view == folderButtonParent) { - selectFolder(); - } else { - Artist artist = (Artist) parent.getItemAtPosition(position); - - SubsonicFragment fragment; - if((Util.isFirstLevelArtist(context) || Util.isOffline(context) || Util.isTagBrowsing(context)) || "root".equals(artist.getId()) || groupId != null) { - fragment = new SelectDirectoryFragment(); - Bundle args = new Bundle(); - args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); - args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); - if ("root".equals(artist.getId())) { - args.putSerializable(Constants.FRAGMENT_LIST, (Serializable) entries); - } - args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true); - fragment.setArguments(args); - } else { - fragment = new SelectArtistFragment(); - Bundle args = new Bundle(); - args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); - args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); - fragment.setArguments(args); + public void onItemClicked(Artist artist) { + SubsonicFragment fragment; + if((Util.isFirstLevelArtist(context) || Util.isOffline(context) || Util.isTagBrowsing(context)) || "root".equals(artist.getId()) || groupId != null) { + fragment = new SelectDirectoryFragment(); + Bundle args = new Bundle(); + args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); + args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); + if ("root".equals(artist.getId())) { + args.putSerializable(Constants.FRAGMENT_LIST, (Serializable) entries); } - - replaceFragment(fragment); + args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true); + fragment.setArguments(args); + } else { + fragment = new SelectArtistFragment(); + Bundle args = new Bundle(); + args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); + args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); + fragment.setArguments(args); } - } - @Override - public void onFinishRefresh() { - setMusicFolders(); + replaceFragment(fragment); } @Override @@ -215,9 +160,8 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { } @Override - public ArrayAdapter getAdapter(List<Artist> objects) { - createMusicFolderButton(); - return new ArtistAdapter(context, objects); + public SectionAdapter getAdapter(List<Artist> objects) { + return new ArtistAdapter(context, objects, musicFolders, this, this); } @Override @@ -236,12 +180,12 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { String musicFolderId = Util.getSelectedMusicFolderId(context); Indexes indexes = musicService.getIndexes(musicFolderId, refresh, context, listener); - artists = new ArrayList<Artist>(indexes.getShortcuts().size() + indexes.getArtists().size()); + artists = new ArrayList<>(indexes.getShortcuts().size() + indexes.getArtists().size()); artists.addAll(indexes.getShortcuts()); artists.addAll(indexes.getArtists()); entries = indexes.getEntries(); } else { - artists = new ArrayList<Artist>(); + artists = new ArrayList<>(); MusicDirectory dir = musicService.getMusicDirectory(groupId, groupName, refresh, context, listener); for(MusicDirectory.Entry entry: dir.getChildren(true, false)) { Artist artist = new Artist(); @@ -251,7 +195,7 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { artists.add(artist); } - entries = new ArrayList<MusicDirectory.Entry>(); + entries = new ArrayList<>(); entries.addAll(dir.getChildren(false, true)); if(!entries.isEmpty()) { Artist root = new Artist(); @@ -270,32 +214,14 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { return groupId == null ? R.string.button_bar_browse : 0; } - private void createMusicFolderButton() { - if(folderButton == null) { - folderButtonParent = context.getLayoutInflater().inflate(R.layout.select_artist_header, listView, false); - folderName = (TextView) folderButtonParent.findViewById(R.id.select_artist_folder_2); - listView.addHeaderView(folderButtonParent); - folderButton = folderButtonParent.findViewById(R.id.select_artist_folder); - } - - if (Util.isOffline(context) || Util.isTagBrowsing(context) || musicFolders == null) { - folderButton.setVisibility(View.GONE); - } else { - folderButton.setVisibility(View.VISIBLE); - } - } - @Override public void setEmpty(boolean empty) { super.setEmpty(empty); if(empty && !Util.isOffline(context)) { - createMusicFolderButton(); - setMusicFolders(); - objects.clear(); - listView.setAdapter(new ArtistAdapter(context, objects)); - listView.setVisibility(View.VISIBLE); + recyclerView.setAdapter(new ArtistAdapter(context, objects, this)); + recyclerView.setVisibility(View.VISIBLE); View view = rootView.findViewById(R.id.tab_progress); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams(); @@ -305,29 +231,19 @@ public class SelectArtistFragment extends SelectListFragment<Artist> { } } - private void setMusicFolders() { - // Display selected music folder - if (musicFolders != null) { - String musicFolderId = Util.getSelectedMusicFolderId(context); - if (musicFolderId == null) { - folderName.setText(R.string.select_artist_all_folders); - } else { - for (MusicFolder musicFolder : musicFolders) { - if (musicFolder.getId().equals(musicFolderId)) { - folderName.setText(musicFolder.getName()); - break; - } - } - } - } - } - - private void selectFolder() { - folderButton.showContextMenu(); - } - private void toggleFirstLevelArtist() { Util.toggleFirstLevelArtist(context); context.invalidateOptionsMenu(); } + + @Override + public void onMusicFolderChanged(MusicFolder selectedFolder) { + String startMusicFolderId = Util.getSelectedMusicFolderId(context); + String musicFolderId = selectedFolder == null ? null : selectedFolder.getId(); + + if(!Util.equals(startMusicFolderId, musicFolderId)) { + Util.setSelectedMusicFolderId(context, musicFolderId); + context.invalidate(); + } + } } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java index 830e2957..a774a287 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java @@ -25,6 +25,7 @@ import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.service.DownloadService; @@ -33,16 +34,19 @@ import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.adapter.BookmarkAdapter; +import github.daneren2005.dsub.view.UpdateView; import java.util.Arrays; import java.util.List; -public class SelectBookmarkFragment extends SelectListFragment<MusicDirectory.Entry> { +public class SelectBookmarkFragment extends SelectRecyclerFragment<MusicDirectory.Entry> { private static final String TAG = SelectBookmarkFragment.class.getSimpleName(); @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); + UpdateView targetView = adapter.getContextView(); + menuInfo = new AdapterView.AdapterContextMenuInfo(targetView, 0, 0); MenuInflater inflater = context.getMenuInflater(); inflater.inflate(R.menu.select_bookmark_context, menu); @@ -52,8 +56,7 @@ public class SelectBookmarkFragment extends SelectListFragment<MusicDirectory.En @Override public boolean onContextItemSelected(MenuItem menuItem) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - MusicDirectory.Entry bookmark = objects.get(info.position); + MusicDirectory.Entry bookmark = adapter.getContextItem(); switch(menuItem.getItemId()) { case R.id.bookmark_menu_info: @@ -77,8 +80,8 @@ public class SelectBookmarkFragment extends SelectListFragment<MusicDirectory.En } @Override - public ArrayAdapter getAdapter(List<MusicDirectory.Entry> bookmarks) { - return new BookmarkAdapter(context, bookmarks); + public SectionAdapter getAdapter(List<MusicDirectory.Entry> bookmarks) { + return new BookmarkAdapter(context, bookmarks, this); } @Override @@ -92,13 +95,12 @@ public class SelectBookmarkFragment extends SelectListFragment<MusicDirectory.En } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + public void onItemClicked(final MusicDirectory.Entry bookmark) { final DownloadService downloadService = getDownloadService(); if(downloadService == null) { return; } - final MusicDirectory.Entry bookmark = (MusicDirectory.Entry) parent.getItemAtPosition(position); new SilentBackgroundTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { 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..417b1328 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,21 @@ 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.adapter.SectionAdapter; 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 +58,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,17 +69,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 SectionAdapter.OnItemClickedListener<Entry> { 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; private String artistInfoDelayed; @@ -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,37 @@ 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; - } 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); + recyclerView = (RecyclerView) rootView.findViewById(R.id.select_album_entries); + recyclerView.setHasFixedSize(true); + setupScrollList(recyclerView); - setupScrollList(albumList); + 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 || viewType == EntryInfiniteGridAdapter.VIEW_TYPE_LOADING) { + return columns; + } else { + return 1; + } + } + }); + recyclerView.addItemDecoration(new GridSpacingDecoration()); + recyclerView.setLayoutManager(gridLayoutManager); + } else { + LinearLayoutManager layoutManager = new LinearLayoutManager(context); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + recyclerView.setLayoutManager(layoutManager); } - registerForContextMenu(entryList); - setupAlbumList(); + + registerForContextMenu(recyclerView); if(entries == null) { if(primaryFragment || secondaryFragment) { @@ -286,21 +291,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 +338,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.getContextItem(); + 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 +375,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.getContextItem(); 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 +389,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 +409,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 onItemClicked(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; } + + playNow(Arrays.asList(entry)); } } @@ -477,8 +456,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 +664,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,120 +702,120 @@ 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 - 2) { + infiniteGridAdapter.loadMore(); + } } - } - addAlbumHeader = false; + }); } - - boolean validData = !entries.isEmpty() || !albums.isEmpty(); - if(!validData) { - setEmpty(true); + entryGridAdapter.setOnItemClickedListener(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 + boolean addedHeader = false; + 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); + addedHeader = true; } } - entryList.setAdapter(listAdapter); - if(validData) { - entryList.setVisibility(View.VISIBLE); - } - context.supportInvalidateOptionsMenu(); + int scrollToPosition = -1; if(lookupEntry != null) { for(int i = 0; i < entries.size(); i++) { if(lookupEntry.equals(entries.get(i).getTitle())) { - entryList.setSelection(i + entryList.getHeaderViewsCount()); + scrollToPosition = i; + entryGridAdapter.addSelected(entries.get(i)); lookupEntry = null; break; } } } + recyclerView.setAdapter(entryGridAdapter); + 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) { @@ -848,122 +823,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 +910,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 +947,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 +971,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 +1058,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter @Override protected void done(Void result) { - entries.remove(episode); - entryAdapter.notifyDataSetChanged(); + entryGridAdapter.removeItem(episode); } @Override @@ -1218,10 +1119,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.removeItem(entry); } - entryAdapter.notifyDataSetChanged(); - selectAll(false, false); } @Override @@ -1340,25 +1239,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 +1394,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/SelectGenreFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectGenreFragment.java index 2d310172..fe012f62 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectGenreFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectGenreFragment.java @@ -1,21 +1,18 @@ /* - This file is part of Subsonic. + 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 +*/ - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2010 (C) Sindre Mehus - */ package github.daneren2005.dsub.fragments; import android.os.Bundle; @@ -23,6 +20,7 @@ import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.util.Constants; @@ -31,7 +29,7 @@ import github.daneren2005.dsub.adapter.GenreAdapter; import java.util.List; -public class SelectGenreFragment extends SelectListFragment<Genre> { +public class SelectGenreFragment extends SelectRecyclerFragment<Genre> { private static final String TAG = SelectGenreFragment.class.getSimpleName(); @Override @@ -40,8 +38,8 @@ public class SelectGenreFragment extends SelectListFragment<Genre> { } @Override - public ArrayAdapter getAdapter(List<Genre> objs) { - return new GenreAdapter(context, objs); + public SectionAdapter getAdapter(List<Genre> objs) { + return new GenreAdapter(context, objs, this); } @Override @@ -55,9 +53,7 @@ public class SelectGenreFragment extends SelectListFragment<Genre> { } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Genre genre = (Genre) parent.getItemAtPosition(position); - + public void onItemClicked(Genre genre) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, "genres"); diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java index 3d7e664f..2f2cfcc1 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java @@ -2,18 +2,19 @@ package github.daneren2005.dsub.fragments; import android.app.AlertDialog; import android.content.DialogInterface; +import android.content.res.Resources; import android.os.Bundle; import android.support.v4.app.FragmentTransaction; +import android.support.v7.widget.RecyclerView; import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.EditText; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.ServerInfo; @@ -31,9 +32,11 @@ import github.daneren2005.dsub.util.UserUtil; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.adapter.PlaylistAdapter; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -public class SelectPlaylistFragment extends SelectListFragment<Playlist> { +public class SelectPlaylistFragment extends SelectRecyclerFragment<Playlist> { private static final String TAG = SelectPlaylistFragment.class.getSimpleName(); @Override @@ -47,8 +50,7 @@ public class SelectPlaylistFragment extends SelectListFragment<Playlist> { else { inflater.inflate(R.menu.select_playlist_context, menu); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Playlist playlist = (Playlist) listView.getItemAtPosition(info.position); + Playlist playlist = adapter.getContextItem(); if(SyncUtil.isSyncedPlaylist(context, playlist.getId())) { menu.removeItem(R.id.playlist_menu_sync); } else { @@ -71,9 +73,8 @@ public class SelectPlaylistFragment extends SelectListFragment<Playlist> { if(menuItem.getGroupId() != getSupportTag()) { return false; } - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Playlist playlist = (Playlist) listView.getItemAtPosition(info.position); + + Playlist playlist = adapter.getContextItem(); SubsonicFragment fragment; Bundle args; @@ -130,8 +131,31 @@ public class SelectPlaylistFragment extends SelectListFragment<Playlist> { } @Override - public ArrayAdapter getAdapter(List<Playlist> playlists) { - return new PlaylistAdapter(context, playlists); + public SectionAdapter<Playlist> getAdapter(List<Playlist> playlists) { + List<Playlist> mine = new ArrayList<>(); + List<Playlist> shared = new ArrayList<>(); + + String currentUsername = UserUtil.getCurrentUsername(context); + for(Playlist playlist: playlists) { + if(playlist.getOwner() == null || playlist.getOwner().equals(currentUsername)) { + mine.add(playlist); + } else { + shared.add(playlist); + } + } + + if(shared.isEmpty()) { + return new PlaylistAdapter(context, playlists, this); + } else { + Resources res = context.getResources(); + List<String> headers = Arrays.asList(res.getString(R.string.playlist_mine), res.getString(R.string.playlist_shared)); + + List<List<Playlist>> sections = new ArrayList<>(); + sections.add(mine); + sections.add(shared); + + return new PlaylistAdapter(context, headers, sections, this); + } } @Override @@ -149,9 +173,7 @@ public class SelectPlaylistFragment extends SelectListFragment<Playlist> { } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Playlist playlist = (Playlist) parent.getItemAtPosition(position); - + public void onItemClicked(Playlist playlist) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); args.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); @@ -179,8 +201,7 @@ public class SelectPlaylistFragment extends SelectListFragment<Playlist> { @Override protected void done(Void result) { - adapter.remove(playlist); - adapter.notifyDataSetChanged(); + adapter.removeItem(playlist); Util.toast(context, context.getResources().getString(R.string.menu_deleted_playlist, playlist.getName())); } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java index 3a564f1c..eebb1a8a 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java @@ -1,21 +1,17 @@ /* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2010 (C) Sindre Mehus - */ + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2015 (C) Scott Jackson +*/ package github.daneren2005.dsub.fragments; import android.app.AlertDialog; @@ -24,10 +20,9 @@ import android.os.Bundle; import android.view.ContextMenu; import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; import android.widget.TextView; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PodcastChannel; import github.daneren2005.dsub.service.MusicService; @@ -46,11 +41,7 @@ import github.daneren2005.dsub.adapter.PodcastChannelAdapter; import java.util.ArrayList; import java.util.List; -/** - * - * @author Scott - */ -public class SelectPodcastsFragment extends SelectListFragment<PodcastChannel> { +public class SelectPodcastsFragment extends SelectRecyclerFragment<PodcastChannel> { private static final String TAG = SelectPodcastsFragment.class.getSimpleName(); @Override @@ -79,8 +70,7 @@ public class SelectPodcastsFragment extends SelectListFragment<PodcastChannel> { if(!Util.isOffline(context) && UserUtil.canPodcast()) { inflater.inflate(R.menu.select_podcasts_context, menu); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - PodcastChannel podcast = (PodcastChannel) listView.getItemAtPosition(info.position); + PodcastChannel podcast = adapter.getContextItem(); if(SyncUtil.isSyncedPodcast(context, podcast.getId())) { menu.removeItem(R.id.podcast_menu_sync); } else { @@ -98,10 +88,8 @@ public class SelectPodcastsFragment extends SelectListFragment<PodcastChannel> { if(menuItem.getGroupId() != getSupportTag()) { return false; } - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - PodcastChannel channel = (PodcastChannel) listView.getItemAtPosition(info.position); + PodcastChannel channel = adapter.getContextItem(); switch (menuItem.getItemId()) { case R.id.podcast_menu_sync: syncPodcast(channel); @@ -126,8 +114,8 @@ public class SelectPodcastsFragment extends SelectListFragment<PodcastChannel> { } @Override - public ArrayAdapter getAdapter(List<PodcastChannel> channels) { - return new PodcastChannelAdapter(context, channels); + public SectionAdapter getAdapter(List<PodcastChannel> channels) { + return new PodcastChannelAdapter(context, channels, this); } @Override @@ -141,9 +129,7 @@ public class SelectPodcastsFragment extends SelectListFragment<PodcastChannel> { } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - PodcastChannel channel = (PodcastChannel) parent.getItemAtPosition(position); - + public void onItemClicked(PodcastChannel channel) { if("error".equals(channel.getStatus())) { Util.toast(context, context.getResources().getString(R.string.select_podcasts_invalid_podcast_channel, channel.getErrorMessage() == null ? "error" : channel.getErrorMessage())); } else if("downloading".equals(channel.getStatus())) { @@ -258,8 +244,7 @@ public class SelectPodcastsFragment extends SelectListFragment<PodcastChannel> { @Override protected void done(Void result) { - adapter.remove(channel); - adapter.notifyDataSetChanged(); + adapter.removeItem(channel); Util.toast(context, context.getResources().getString(R.string.select_podcasts_deleted, channel.getName())); } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java new file mode 100644 index 00000000..526a4312 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java @@ -0,0 +1,169 @@ +/* + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2015 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.fragments; + +import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +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 java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.service.MusicServiceFactory; +import github.daneren2005.dsub.util.BackgroundTask; +import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.ProgressListener; +import github.daneren2005.dsub.util.TabBackgroundTask; + +public abstract class SelectRecyclerFragment<T> extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<T> { + private static final String TAG = SelectRecyclerFragment.class.getSimpleName(); + protected RecyclerView recyclerView; + protected SectionAdapter<T> adapter; + protected BackgroundTask<List<T>> currentTask; + protected List<T> objects; + protected boolean serialize = true; + protected boolean largeAlbums = false; + protected int columns; + protected boolean pullToRefresh = true; + + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + + if(bundle != null && serialize) { + objects = (List<T>) bundle.getSerializable(Constants.FRAGMENT_LIST); + } + columns = context.getResources().getInteger(R.integer.Grid_Columns); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if(serialize) { + outState.putSerializable(Constants.FRAGMENT_LIST, (Serializable) objects); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { + rootView = inflater.inflate(R.layout.abstract_recycler_fragment, container, false); + + refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout); + refreshLayout.setOnRefreshListener(this); + + recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler); + setupLayoutManager(); + + if(pullToRefresh) { + setupScrollList(recyclerView); + } else { + refreshLayout.setEnabled(false); + } + + if(objects == null) { + refresh(false); + } else { + recyclerView.setAdapter(adapter = getAdapter(objects)); + } + registerForContextMenu(recyclerView); + + return rootView; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { + if(!primaryFragment) { + return; + } + + menuInflater.inflate(getOptionsMenu(), menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return super.onOptionsItemSelected(item); + } + + @Override + protected void refresh(final boolean refresh) { + int titleRes = getTitleResource(); + if(titleRes != 0) { + setTitle(getTitleResource()); + } + recyclerView.setVisibility(View.GONE); + + // Cancel current running task before starting another one + if(currentTask != null) { + currentTask.cancel(); + } + + currentTask = new TabBackgroundTask<List<T>>(this) { + @Override + protected List<T> doInBackground() throws Throwable { + MusicService musicService = MusicServiceFactory.getMusicService(context); + + objects = new ArrayList<T>(); + + try { + objects = getObjects(musicService, refresh, this); + } catch (Exception x) { + Log.e(TAG, "Failed to load", x); + } + + return objects; + } + + @Override + protected void done(List<T> result) { + if (result != null && !result.isEmpty()) { + recyclerView.setAdapter(adapter = getAdapter(result)); + + onFinishRefresh(); + recyclerView.setVisibility(View.VISIBLE); + } else { + setEmpty(true); + } + + currentTask = null; + } + }; + currentTask.execute(); + } + + private void setupLayoutManager() { + setupLayoutManager(recyclerView, largeAlbums); + } + + public abstract int getOptionsMenu(); + public abstract SectionAdapter<T> getAdapter(List<T> objs); + public abstract List<T> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception; + public abstract int getTitleResource(); + + public void onFinishRefresh() { + + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java index 07cd3bef..87dd55b4 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java @@ -1,3 +1,18 @@ +/* + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2015 (C) Scott Jackson +*/ + package github.daneren2005.dsub.fragments; import android.app.AlertDialog; @@ -6,8 +21,6 @@ import android.os.Bundle; import android.view.ContextMenu; import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.DatePicker; @@ -17,6 +30,7 @@ import java.util.Date; import java.util.List; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Share; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; @@ -28,10 +42,7 @@ import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.adapter.ShareAdapter; -/** - * Created by Scott on 12/28/13. - */ -public class SelectShareFragment extends SelectListFragment<Share> { +public class SelectShareFragment extends SelectRecyclerFragment<Share> { private static final String TAG = SelectShareFragment.class.getSimpleName(); @Override @@ -48,9 +59,7 @@ public class SelectShareFragment extends SelectListFragment<Share> { return false; } - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Share share = (Share) listView.getItemAtPosition(info.position); - + Share share = adapter.getContextItem(); switch (menuItem.getItemId()) { case R.id.share_menu_share: shareExternal(share); @@ -75,8 +84,8 @@ public class SelectShareFragment extends SelectListFragment<Share> { } @Override - public ArrayAdapter getAdapter(List<Share> objs) { - return new ShareAdapter(context, objs); + public SectionAdapter getAdapter(List<Share> objs) { + return new ShareAdapter(context, objs, this); } @Override @@ -90,9 +99,7 @@ public class SelectShareFragment extends SelectListFragment<Share> { } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Share share = (Share) parent.getItemAtPosition(position); - + public void onItemClicked(Share share) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); args.putSerializable(Constants.INTENT_EXTRA_NAME_SHARE, share); @@ -193,8 +200,7 @@ public class SelectShareFragment extends SelectListFragment<Share> { @Override protected void done(Void result) { - adapter.remove(share); - adapter.notifyDataSetChanged(); + adapter.removeItem(share); Util.toast(context, context.getResources().getString(R.string.share_deleted, share.getName())); } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectVideoFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectVideoFragment.java index b4d34ff9..e91a163c 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectVideoFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectVideoFragment.java @@ -24,20 +24,25 @@ import android.widget.ArrayAdapter; import java.util.List; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.EntryGridAdapter; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.adapter.EntryAdapter; +import github.daneren2005.dsub.view.UpdateView; -public class SelectVideoFragment extends SelectListFragment<MusicDirectory.Entry> { +public class SelectVideoFragment extends SelectRecyclerFragment<MusicDirectory.Entry> { @Override public int getOptionsMenu() { return R.menu.empty; } @Override - public ArrayAdapter getAdapter(List<MusicDirectory.Entry> objs) { - return new EntryAdapter(context, null, objs, false); + public SectionAdapter getAdapter(List<MusicDirectory.Entry> objs) { + SectionAdapter adapter = new EntryGridAdapter(context, objs, null, false); + adapter.setOnItemClickedListener(this); + return adapter; } @Override @@ -52,18 +57,17 @@ public class SelectVideoFragment extends SelectListFragment<MusicDirectory.Entry } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position); + public void onItemClicked(MusicDirectory.Entry entry) { playVideo(entry); } @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); + UpdateView targetView = adapter.getContextView(); + menuInfo = new AdapterView.AdapterContextMenuInfo(targetView, 0, 0); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Object entry = listView.getItemAtPosition(info.position); - + MusicDirectory.Entry entry = adapter.getContextItem(); onCreateContextMenu(menu, view, menuInfo, entry); recreateContextMenu(menu); } @@ -74,9 +78,7 @@ public class SelectVideoFragment extends SelectListFragment<MusicDirectory.Entry return false; } - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Object entry = listView.getItemAtPosition(info.position); - + MusicDirectory.Entry entry = adapter.getContextItem(); return onContextItemSelected(menuItem, entry); } } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectYearFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectYearFragment.java index dc19acad..a89bc280 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectYearFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectYearFragment.java @@ -1,21 +1,18 @@ /* - This file is part of Subsonic. - + 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 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2010 (C) Sindre Mehus + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2015 (C) Scott Jackson */ + package github.daneren2005.dsub.fragments; import android.os.Bundle; @@ -27,14 +24,13 @@ import java.util.ArrayList; import java.util.List; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.BasicListAdapter; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.ProgressListener; -/** - * Created by Scott on 12/23/13. - */ -public class SelectYearFragment extends SelectListFragment<Integer> { +public class SelectYearFragment extends SelectRecyclerFragment<String> { @Override public int getOptionsMenu() { @@ -42,15 +38,15 @@ public class SelectYearFragment extends SelectListFragment<Integer> { } @Override - public ArrayAdapter getAdapter(List<Integer> objs) { - return new ArrayAdapter<Integer>(context, android.R.layout.simple_list_item_1, objs); + public SectionAdapter getAdapter(List<String> objs) { + return new BasicListAdapter(context, objs, this); } @Override - public List<Integer> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception { - List<Integer> decades = new ArrayList<Integer>(); + public List<String> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception { + List<String> decades = new ArrayList<>(); for(int i = 2010; i >= 1920; i -= 10) { - decades.add(i); + decades.add(String.valueOf(i)); } return decades; @@ -62,15 +58,13 @@ public class SelectYearFragment extends SelectListFragment<Integer> { } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Integer decade = (Integer) parent.getItemAtPosition(position); - + public void onItemClicked(String decade) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, "years"); args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20); args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); - args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA, Integer.toString(decade)); + args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA, decade); fragment.setArguments(args); replaceFragment(fragment); diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java index 79e759cc..088b6d00 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java @@ -17,12 +17,15 @@ package github.daneren2005.dsub.fragments; import android.os.Bundle; import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; -import android.widget.ArrayAdapter; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.ArtistAdapter; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.MusicDirectory; @@ -32,13 +35,13 @@ import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.Util; -import github.daneren2005.dsub.adapter.ArtistAdapter; +import github.daneren2005.dsub.view.UpdateView; import java.net.URLEncoder; import java.util.LinkedList; import java.util.List; -public class SimilarArtistFragment extends SelectListFragment<Artist> { +public class SimilarArtistFragment extends SelectRecyclerFragment<Artist> { private static final String TAG = SimilarArtistFragment.class.getSimpleName(); private ArtistInfo info; private String artistId; @@ -52,6 +55,18 @@ public class SimilarArtistFragment extends SelectListFragment<Artist> { } @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { + super.onCreateOptionsMenu(menu, menuInflater); + if(!primaryFragment) { + return; + } + + if(info.getMissingArtists().isEmpty()) { + menu.removeItem(R.id.menu_show_missing); + } + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_play_now: @@ -71,10 +86,11 @@ public class SimilarArtistFragment extends SelectListFragment<Artist> { @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); + UpdateView targetView = adapter.getContextView(); + menuInfo = new AdapterView.AdapterContextMenuInfo(targetView, 0, 0); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Object entry = listView.getItemAtPosition(info.position); - onCreateContextMenu(menu, view, menuInfo, entry); + Artist artist = adapter.getContextItem(); + onCreateContextMenu(menu, view, menuInfo, artist); recreateContextMenu(menu); } @@ -85,14 +101,12 @@ public class SimilarArtistFragment extends SelectListFragment<Artist> { return false; } - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Artist artist = (Artist) listView.getItemAtPosition(info.position); + Artist artist = adapter.getContextItem(); return onContextItemSelected(menuItem, artist); } @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Artist artist = (Artist) parent.getItemAtPosition(position); + public void onItemClicked(Artist artist) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); @@ -109,8 +123,8 @@ public class SimilarArtistFragment extends SelectListFragment<Artist> { } @Override - public ArrayAdapter getAdapter(List<Artist> objects) { - return new ArtistAdapter(context, objects); + public SectionAdapter getAdapter(List<Artist> objects) { + return new ArtistAdapter(context, objects, this); } @Override 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..cc9f8d62 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,9 @@ 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.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.ContextMenu; import android.view.GestureDetector; @@ -53,6 +56,7 @@ import android.widget.TextView; import github.daneren2005.dsub.R; import github.daneren2005.dsub.activity.SubsonicActivity; import github.daneren2005.dsub.activity.SubsonicFragmentActivity; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.Genre; @@ -76,10 +80,10 @@ 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; +import github.daneren2005.dsub.view.GridSpacingDecoration; import github.daneren2005.dsub.view.PlaylistSongView; import github.daneren2005.dsub.view.SongView; import github.daneren2005.dsub.view.UpdateView; @@ -308,14 +312,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 +632,68 @@ 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); + } + } + + public void setupLayoutManager(RecyclerView recyclerView, boolean largeAlbums) { + recyclerView.setLayoutManager(getLayoutManager(recyclerView, largeAlbums)); + } + public RecyclerView.LayoutManager getLayoutManager(RecyclerView recyclerView, boolean largeCells) { + if(largeCells) { + return getGridLayoutManager(recyclerView); + } else { + return getLinearLayoutManager(); + } + } + public GridLayoutManager getGridLayoutManager(RecyclerView recyclerView) { + final int columns = getRecyclerColumnCount(); + GridLayoutManager gridLayoutManager = new GridLayoutManager(context, columns); + + GridLayoutManager.SpanSizeLookup spanSizeLookup = getSpanSizeLookup(); + if(spanSizeLookup != null) { + gridLayoutManager.setSpanSizeLookup(spanSizeLookup); + } + RecyclerView.ItemDecoration itemDecoration = getItemDecoration(); + if(itemDecoration != null) { + recyclerView.addItemDecoration(itemDecoration); + } + return gridLayoutManager; + } + public LinearLayoutManager getLinearLayoutManager() { + LinearLayoutManager layoutManager = new LinearLayoutManager(context); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + return layoutManager; + } + public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() { + return null; + } + public RecyclerView.ItemDecoration getItemDecoration() { + return new GridSpacingDecoration(); + } + public int getRecyclerColumnCount() { + return context.getResources().getInteger(R.integer.Grid_Columns); + } protected void warnIfStorageUnavailable() { if (!Util.isExternalStoragePresent()) { @@ -1608,7 +1670,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR }.execute(); } - protected void deleteBookmark(final MusicDirectory.Entry entry, final ArrayAdapter adapter) { + protected void deleteBookmark(final MusicDirectory.Entry entry, final SectionAdapter adapter) { Util.confirmDialog(context, R.string.bookmark_delete_title, entry.getTitle(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -1634,8 +1696,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR @Override protected void done(Void result) { if (adapter != null) { - adapter.remove(entry); - adapter.notifyDataSetChanged(); + adapter.removeItem(entry); } Util.toast(context, context.getResources().getString(R.string.bookmark_deleted, entry.getTitle())); } @@ -1788,11 +1849,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 +1862,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/service/parser/PlaylistsParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/PlaylistsParser.java index 6f01d510..69e5af64 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/parser/PlaylistsParser.java +++ b/app/src/main/java/github/daneren2005/dsub/service/parser/PlaylistsParser.java @@ -22,7 +22,6 @@ import android.content.Context; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.util.ProgressListener; -import github.daneren2005.dsub.adapter.PlaylistAdapter; import org.xmlpull.v1.XmlPullParser; import java.io.Reader; @@ -64,7 +63,7 @@ public class PlaylistsParser extends AbstractParser { validate(); - return PlaylistAdapter.PlaylistComparator.sort(result); + return Playlist.PlaylistComparator.sort(result); } }
\ No newline at end of file diff --git a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java index 29618424..5c5f1543 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java +++ b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java @@ -28,6 +28,7 @@ import android.widget.ListView; import android.widget.TextView; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.User; import github.daneren2005.dsub.fragments.SubsonicFragment; import github.daneren2005.dsub.service.DownloadService; @@ -326,7 +327,7 @@ public final class UserUtil { }); } - public static void deleteUser(final Context context, final User user, final ArrayAdapter adapter) { + public static void deleteUser(final Context context, final User user, final SectionAdapter adapter) { Util.confirmDialog(context, R.string.common_delete, user.getUsername(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -341,8 +342,7 @@ public final class UserUtil { @Override protected void done(Void v) { if(adapter != null) { - adapter.remove(user); - adapter.notifyDataSetChanged(); + adapter.removeItem(user); } Util.toast(context, context.getResources().getString(R.string.admin_delete_user_success, user.getUsername())); 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..69f2ef13 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,52 @@ 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(); - } - }); + 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/BasicHeaderView.java b/app/src/main/java/github/daneren2005/dsub/view/BasicHeaderView.java new file mode 100644 index 00000000..d8111692 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/view/BasicHeaderView.java @@ -0,0 +1,37 @@ +/* + 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.content.Context; +import android.view.LayoutInflater; +import android.widget.TextView; + +import github.daneren2005.dsub.R; + +public class BasicHeaderView extends UpdateView { + TextView nameView; + + public BasicHeaderView(Context context) { + super(context, false); + + LayoutInflater.from(context).inflate(R.layout.basic_header, this, true); + nameView = (TextView) findViewById(R.id.item_name); + } + + protected void setObjectImpl(Object obj) { + nameView.setText((String) obj); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/view/BasicListView.java b/app/src/main/java/github/daneren2005/dsub/view/BasicListView.java new file mode 100644 index 00000000..3169f903 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/view/BasicListView.java @@ -0,0 +1,45 @@ +/* + 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.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import github.daneren2005.dsub.R; + +public class BasicListView extends UpdateView { + private TextView titleView; + + public BasicListView(Context context) { + super(context, false); + this.context = context; + LayoutInflater.from(context).inflate(R.layout.basic_list_item, this, true); + + titleView = (TextView) findViewById(R.id.item_name); + starButton = (ImageButton) findViewById(R.id.item_star); + starButton.setFocusable(false); + moreButton = (ImageView) findViewById(R.id.item_more); + moreButton.setVisibility(View.GONE); + } + + protected void setObjectImpl(Object obj) { + titleView.setText((String) obj); + } +} 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/HeaderGridView.java b/app/src/main/java/github/daneren2005/dsub/view/HeaderGridView.java deleted file mode 100644 index 8a82f353..00000000 --- a/app/src/main/java/github/daneren2005/dsub/view/HeaderGridView.java +++ /dev/null @@ -1,836 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package github.daneren2005.dsub.view; - -import android.annotation.TargetApi; -import android.content.Context; -import android.database.DataSetObservable; -import android.database.DataSetObserver; -import android.os.Build; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.*; - -import java.lang.reflect.Field; -import java.util.ArrayList; - -/** - * A {@link GridView} that supports adding header rows in a - * very similar way to {@link android.widget.ListView}. - * See {@link HeaderGridView#addHeaderView(View, Object, boolean)} - * See {@link HeaderGridView#addFooterView(View, Object, boolean)} - */ -public class HeaderGridView extends GridView { - private static final String TAG = HeaderGridView.class.getSimpleName(); - public static boolean DEBUG = false; - - /** - * A class that represents a fixed view in a list, for example a header at the top - * or a footer at the bottom. - */ - private static class FixedViewInfo { - /** - * The view to add to the grid - */ - public View view; - public ViewGroup viewContainer; - /** - * The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. - */ - public Object data; - /** - * <code>true</code> if the fixed view should be selectable in the grid - */ - public boolean isSelectable; - } - - private int mNumColumns = AUTO_FIT; - private View mViewForMeasureRowHeight = null; - private int mRowHeight = -1; - private static final String LOG_TAG = HeaderGridView.class.getSimpleName(); - - private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>(); - private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<FixedViewInfo>(); - - private void initHeaderGridView() { - } - - public HeaderGridView(Context context) { - super(context); - initHeaderGridView(); - } - - public HeaderGridView(Context context, AttributeSet attrs) { - super(context, attrs); - initHeaderGridView(); - } - - public HeaderGridView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initHeaderGridView(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - ListAdapter adapter = getAdapter(); - if (adapter != null && adapter instanceof HeaderViewGridAdapter) { - ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumnsCompatible()); - ((HeaderViewGridAdapter) adapter).setRowHeight(getRowHeight()); - } - } - - @Override - public void setClipChildren(boolean clipChildren) { - // Ignore, since the header rows depend on not being clipped - } - - /** - * Do not call this method unless you know how it works. - * - * @param clipChildren - */ - public void setClipChildrenSupper(boolean clipChildren) { - super.setClipChildren(false); - } - - /** - * Add a fixed view to appear at the top of the grid. If addHeaderView is - * called more than once, the views will appear in the order they were - * added. Views added using this call can take focus if they want. - * <p/> - * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap - * the supplied cursor with one that will also account for header views. - * - * @param v The view to add. - */ - public void addHeaderView(View v) { - addHeaderView(v, null, true); - } - - /** - * Add a fixed view to appear at the top of the grid. If addHeaderView is - * called more than once, the views will appear in the order they were - * added. Views added using this call can take focus if they want. - * <p/> - * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap - * the supplied cursor with one that will also account for header views. - * - * @param v The view to add. - * @param data Data to associate with this view - * @param isSelectable whether the item is selectable - */ - public void addHeaderView(View v, Object data, boolean isSelectable) { - ListAdapter adapter = getAdapter(); - if (adapter != null && !(adapter instanceof HeaderViewGridAdapter)) { - throw new IllegalStateException( - "Cannot add header view to grid -- setAdapter has already been called."); - } - - ViewGroup.LayoutParams lyp = v.getLayoutParams(); - - FixedViewInfo info = new FixedViewInfo(); - FrameLayout fl = new FullWidthFixedViewLayout(getContext()); - - if (lyp != null) { - v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height)); - fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height)); - } - fl.addView(v); - info.view = v; - info.viewContainer = fl; - info.data = data; - info.isSelectable = isSelectable; - mHeaderViewInfos.add(info); - // in the case of re-adding a header view, or adding one later on, - // we need to notify the observer - if (adapter != null) { - ((HeaderViewGridAdapter) adapter).notifyDataSetChanged(); - } - } - - public void addFooterView(View v) { - addFooterView(v, null, true); - } - - public void addFooterView(View v, Object data, boolean isSelectable) { - ListAdapter mAdapter = getAdapter(); - if (mAdapter != null && !(mAdapter instanceof HeaderViewGridAdapter)) { - throw new IllegalStateException( - "Cannot add header view to grid -- setAdapter has already been called."); - } - - ViewGroup.LayoutParams lyp = v.getLayoutParams(); - - FixedViewInfo info = new FixedViewInfo(); - FrameLayout fl = new FullWidthFixedViewLayout(getContext()); - - if (lyp != null) { - v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height)); - fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height)); - } - fl.addView(v); - info.view = v; - info.viewContainer = fl; - info.data = data; - info.isSelectable = isSelectable; - mFooterViewInfos.add(info); - - if (mAdapter != null) { - ((HeaderViewGridAdapter) mAdapter).notifyDataSetChanged(); - } - } - - public int getHeaderViewCount() { - return mHeaderViewInfos.size(); - } - - public int getFooterViewCount() { - return mFooterViewInfos.size(); - } - - /** - * Removes a previously-added header view. - * - * @param v The view to remove - * @return true if the view was removed, false if the view was not a header - * view - */ - public boolean removeHeaderView(View v) { - if (mHeaderViewInfos.size() > 0) { - boolean result = false; - ListAdapter adapter = getAdapter(); - if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) { - result = true; - } - removeFixedViewInfo(v, mHeaderViewInfos); - return result; - } - return false; - } - - /** - * Removes a previously-added footer view. - * - * @param v The view to remove - * @return true if the view was removed, false if the view was not a header - * view - */ - public boolean removeFooterView(View v) { - if (mFooterViewInfos.size() > 0) { - boolean result = false; - ListAdapter adapter = getAdapter(); - if (adapter != null && ((HeaderViewGridAdapter) adapter).removeFooter(v)) { - result = true; - } - removeFixedViewInfo(v, mFooterViewInfos); - return result; - } - return false; - } - - private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) { - int len = where.size(); - for (int i = 0; i < len; ++i) { - FixedViewInfo info = where.get(i); - if (info.view == v) { - where.remove(i); - break; - } - } - } - - @TargetApi(11) - private int getNumColumnsCompatible() { - if (Build.VERSION.SDK_INT >= 11) { - return super.getNumColumns(); - } else { - try { - Field numColumns = GridView.class.getSuperclass().getDeclaredField("mNumColumns"); - numColumns.setAccessible(true); - return numColumns.getInt(this); - } catch (Exception e) { - if (mNumColumns != -1) { - return mNumColumns; - } else { - return 2; - } - } - } - } - - @TargetApi(16) - private int getColumnWidthCompatible() { - if (Build.VERSION.SDK_INT >= 16) { - return super.getColumnWidth(); - } else { - try { - Field numColumns = getClass().getSuperclass().getDeclaredField("mColumnWidth"); - numColumns.setAccessible(true); - return numColumns.getInt(this); - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mViewForMeasureRowHeight = null; - } - - public void invalidateRowHeight() { - mRowHeight = -1; - } - - public int getHeaderHeight(int row) { - if (row >= 0) { - return mHeaderViewInfos.get(row).view.getMeasuredHeight(); - } - - return 0; - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public int getVerticalSpacing(){ - int value = 0; - - try { - int currentapiVersion = android.os.Build.VERSION.SDK_INT; - if (currentapiVersion < Build.VERSION_CODES.JELLY_BEAN){ - Field field = this.getClass().getSuperclass().getDeclaredField("mVerticalSpacing"); - field.setAccessible(true); - value = field.getInt(this); - } else{ - value = super.getVerticalSpacing(); - } - - }catch (Exception ex){ - - } - - return value; - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public int getHorizontalSpacing(){ - int value = 0; - - try { - int currentapiVersion = android.os.Build.VERSION.SDK_INT; - if (currentapiVersion < Build.VERSION_CODES.JELLY_BEAN){ - Field field = this.getClass().getSuperclass().getDeclaredField("mHorizontalSpacing"); - field.setAccessible(true); - value = field.getInt(this); - } else{ - value = super.getHorizontalSpacing(); - } - - }catch (Exception ex){ - - } - - return value; - } - - public int getRowHeight() { - if (mRowHeight > 0) { - // return mRowHeight; - } - ListAdapter adapter = getAdapter(); - int numColumns = getNumColumnsCompatible(); - - // adapter has not been set or has no views in it; - if (adapter == null || adapter.getCount() <= numColumns * (mHeaderViewInfos.size() + mFooterViewInfos.size()) || numColumns == -1) { - return -1; - } - int mColumnWidth = getColumnWidthCompatible(); - View view = getAdapter().getView(numColumns * mHeaderViewInfos.size(), mViewForMeasureRowHeight, this); - AbsListView.LayoutParams p = (AbsListView.LayoutParams) view.getLayoutParams(); - if (p == null) { - p = new AbsListView.LayoutParams(-1, -2, 0); - view.setLayoutParams(p); - } - int childHeightSpec = getChildMeasureSpec( - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height); - int childWidthSpec = getChildMeasureSpec( - MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width); - view.measure(childWidthSpec, childHeightSpec); - mViewForMeasureRowHeight = view; - mRowHeight = view.getMeasuredHeight(); - return mRowHeight; - } - - @TargetApi(11) - public void tryToScrollToBottomSmoothly() { - int lastPos = getAdapter().getCount() - 1; - if (Build.VERSION.SDK_INT >= 11) { - smoothScrollToPositionFromTop(lastPos, 0); - } else { - setSelection(lastPos); - } - } - - @TargetApi(11) - public void tryToScrollToBottomSmoothly(int duration) { - int lastPos = getAdapter().getCount() - 1; - if (Build.VERSION.SDK_INT >= 11) { - smoothScrollToPositionFromTop(lastPos, 0, duration); - } else { - setSelection(lastPos); - } - } - - @Override - public void setAdapter(ListAdapter adapter) { - if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) { - HeaderViewGridAdapter headerViewGridAdapter = new HeaderViewGridAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); - int numColumns = getNumColumnsCompatible(); - if (numColumns > 1) { - headerViewGridAdapter.setNumColumns(numColumns); - } - headerViewGridAdapter.setRowHeight(getRowHeight()); - super.setAdapter(headerViewGridAdapter); - } else { - super.setAdapter(adapter); - } - } - - /** - * full width - */ - private class FullWidthFixedViewLayout extends FrameLayout { - - public FullWidthFixedViewLayout(Context context) { - super(context); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int realLeft = HeaderGridView.this.getPaddingLeft() + getPaddingLeft(); - // Try to make where it should be, from left, full width - if (realLeft != left) { - offsetLeftAndRight(realLeft - left); - } - super.onLayout(changed, left, top, right, bottom); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int targetWidth = HeaderGridView.this.getMeasuredWidth() - - HeaderGridView.this.getPaddingLeft() - - HeaderGridView.this.getPaddingRight(); - widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth, - MeasureSpec.getMode(widthMeasureSpec)); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - @Override - public void setNumColumns(int numColumns) { - super.setNumColumns(numColumns); - mNumColumns = numColumns; - ListAdapter adapter = getAdapter(); - if (adapter != null && adapter instanceof HeaderViewGridAdapter) { - ((HeaderViewGridAdapter) adapter).setNumColumns(numColumns); - } - } - - /** - * ListAdapter used when a HeaderGridView has header views. This ListAdapter - * wraps another one and also keeps track of the header views and their - * associated data objects. - * <p>This is intended as a base class; you will probably not need to - * use this class directly in your own code. - */ - private static class HeaderViewGridAdapter extends BaseAdapter implements WrapperListAdapter, Filterable { - // This is used to notify the container of updates relating to number of columns - // or headers changing, which changes the number of placeholders needed - private final DataSetObservable mDataSetObservable = new DataSetObservable(); - private final ListAdapter mAdapter; - static final ArrayList<FixedViewInfo> EMPTY_INFO_LIST = - new ArrayList<FixedViewInfo>(); - - // This ArrayList is assumed to NOT be null. - ArrayList<FixedViewInfo> mHeaderViewInfos; - ArrayList<FixedViewInfo> mFooterViewInfos; - private int mNumColumns = 1; - private int mRowHeight = -1; - boolean mAreAllFixedViewsSelectable; - private final boolean mIsFilterable; - private boolean mCachePlaceHoldView = true; - // From Recycle Bin or calling getView, this a question... - private boolean mCacheFirstHeaderView = false; - - public HeaderViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ArrayList<FixedViewInfo> footViewInfos, ListAdapter adapter) { - mAdapter = adapter; - mIsFilterable = adapter instanceof Filterable; - if (headerViewInfos == null) { - mHeaderViewInfos = EMPTY_INFO_LIST; - } else { - mHeaderViewInfos = headerViewInfos; - } - - if (footViewInfos == null) { - mFooterViewInfos = EMPTY_INFO_LIST; - } else { - mFooterViewInfos = footViewInfos; - } - mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos) - && areAllListInfosSelectable(mFooterViewInfos); - } - - public void setNumColumns(int numColumns) { - if (numColumns < 1) { - return; - } - if (mNumColumns != numColumns) { - mNumColumns = numColumns; - notifyDataSetChanged(); - } - } - - public void setRowHeight(int height) { - mRowHeight = height; - } - - public int getHeadersCount() { - return mHeaderViewInfos.size(); - } - - public int getFootersCount() { - return mFooterViewInfos.size(); - } - - @Override - public boolean isEmpty() { - return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0 && getFootersCount() == 0; - } - - private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) { - if (infos != null) { - for (FixedViewInfo info : infos) { - if (!info.isSelectable) { - return false; - } - } - } - return true; - } - - public boolean removeHeader(View v) { - for (int i = 0; i < mHeaderViewInfos.size(); i++) { - FixedViewInfo info = mHeaderViewInfos.get(i); - if (info.view == v) { - mHeaderViewInfos.remove(i); - mAreAllFixedViewsSelectable = - areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos); - mDataSetObservable.notifyChanged(); - return true; - } - } - return false; - } - - public boolean removeFooter(View v) { - for (int i = 0; i < mFooterViewInfos.size(); i++) { - FixedViewInfo info = mFooterViewInfos.get(i); - if (info.view == v) { - mFooterViewInfos.remove(i); - mAreAllFixedViewsSelectable = - areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos); - mDataSetObservable.notifyChanged(); - return true; - } - } - return false; - } - - @Override - public int getCount() { - if (mAdapter != null) { - return (getFootersCount() + getHeadersCount()) * mNumColumns + getAdapterAndPlaceHolderCount(); - } else { - return (getFootersCount() + getHeadersCount()) * mNumColumns; - } - } - - @Override - public boolean areAllItemsEnabled() { - if (mAdapter != null) { - return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled(); - } else { - return true; - } - } - - private int getAdapterAndPlaceHolderCount() { - final int adapterCount = (int) (Math.ceil(1f * mAdapter.getCount() / mNumColumns) * mNumColumns); - return adapterCount; - } - - @Override - public boolean isEnabled(int position) { - // Header (negative positions will throw an IndexOutOfBoundsException) - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (position < numHeadersAndPlaceholders) { - return position % mNumColumns == 0 - && mHeaderViewInfos.get(position / mNumColumns).isSelectable; - } - - // Adapter - final int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = 0; - if (mAdapter != null) { - adapterCount = getAdapterAndPlaceHolderCount(); - if (adjPosition < adapterCount) { - return adjPosition < mAdapter.getCount() && mAdapter.isEnabled(adjPosition); - } - } - - // Footer (off-limits positions will throw an IndexOutOfBoundsException) - final int footerPosition = adjPosition - adapterCount; - return footerPosition % mNumColumns == 0 - && mFooterViewInfos.get(footerPosition / mNumColumns).isSelectable; - } - - @Override - public Object getItem(int position) { - // Header (negative positions will throw an ArrayIndexOutOfBoundsException) - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (position < numHeadersAndPlaceholders) { - if (position % mNumColumns == 0) { - return mHeaderViewInfos.get(position / mNumColumns).data; - } - return null; - } - - // Adapter - final int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = 0; - if (mAdapter != null) { - adapterCount = getAdapterAndPlaceHolderCount(); - if (adjPosition < adapterCount) { - if (adjPosition < mAdapter.getCount()) { - return mAdapter.getItem(adjPosition); - } else { - return null; - } - } - } - - // Footer (off-limits positions will throw an IndexOutOfBoundsException) - final int footerPosition = adjPosition - adapterCount; - if (footerPosition % mNumColumns == 0) { - return mFooterViewInfos.get(footerPosition).data; - } else { - return null; - } - } - - @Override - public long getItemId(int position) { - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (mAdapter != null && position >= numHeadersAndPlaceholders) { - int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = mAdapter.getCount(); - if (adjPosition < adapterCount) { - return mAdapter.getItemId(adjPosition); - } - } - return -1; - } - - @Override - public boolean hasStableIds() { - if (mAdapter != null) { - return mAdapter.hasStableIds(); - } - return false; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (DEBUG) { - Log.d(LOG_TAG, String.format("getView: %s, reused: %s", position, convertView == null)); - } - // Header (negative positions will throw an ArrayIndexOutOfBoundsException) - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (position < numHeadersAndPlaceholders) { - View headerViewContainer = mHeaderViewInfos - .get(position / mNumColumns).viewContainer; - if (position % mNumColumns == 0) { - return headerViewContainer; - } else { - if (convertView == null) { - convertView = new View(parent.getContext()); - } - // We need to do this because GridView uses the height of the last item - // in a row to determine the height for the entire row. - convertView.setVisibility(View.INVISIBLE); - convertView.setMinimumHeight(headerViewContainer.getHeight()); - return convertView; - } - } - // Adapter - final int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = 0; - if (mAdapter != null) { - adapterCount = getAdapterAndPlaceHolderCount(); - if (adjPosition < adapterCount) { - if (adjPosition < mAdapter.getCount()) { - View view = mAdapter.getView(adjPosition, convertView, parent); - return view; - } else { - if (convertView == null) { - convertView = new View(parent.getContext()); - } - convertView.setVisibility(View.INVISIBLE); - convertView.setMinimumHeight(mRowHeight); - return convertView; - } - } - } - // Footer - final int footerPosition = adjPosition - adapterCount; - if (footerPosition < getCount()) { - View footViewContainer = mFooterViewInfos - .get(footerPosition / mNumColumns).viewContainer; - if (position % mNumColumns == 0) { - return footViewContainer; - } else { - if (convertView == null) { - convertView = new View(parent.getContext()); - } - // We need to do this because GridView uses the height of the last item - // in a row to determine the height for the entire row. - convertView.setVisibility(View.INVISIBLE); - convertView.setMinimumHeight(footViewContainer.getHeight()); - return convertView; - } - } - throw new ArrayIndexOutOfBoundsException(position); - } - - @Override - public int getItemViewType(int position) { - - final int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - final int adapterViewTypeStart = mAdapter == null ? 0 : mAdapter.getViewTypeCount() - 1; - int type = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; - if (mCachePlaceHoldView) { - // Header - if (position < numHeadersAndPlaceholders) { - if (position == 0) { - if (mCacheFirstHeaderView) { - type = adapterViewTypeStart + mHeaderViewInfos.size() + mFooterViewInfos.size() + 1 + 1; - } - } - if (position % mNumColumns != 0) { - type = adapterViewTypeStart + (position / mNumColumns + 1); - } - } - } - - // Adapter - final int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = 0; - if (mAdapter != null) { - adapterCount = getAdapterAndPlaceHolderCount(); - if (adjPosition >= 0 && adjPosition < adapterCount) { - if (adjPosition < mAdapter.getCount()) { - type = mAdapter.getItemViewType(adjPosition); - } else { - if (mCachePlaceHoldView) { - type = adapterViewTypeStart + mHeaderViewInfos.size() + 1; - } - } - } - } - - if (mCachePlaceHoldView) { - // Footer - final int footerPosition = adjPosition - adapterCount; - if (footerPosition >= 0 && footerPosition < getCount() && (footerPosition % mNumColumns) != 0) { - type = adapterViewTypeStart + mHeaderViewInfos.size() + 1 + (footerPosition / mNumColumns + 1); - } - } - if (DEBUG) { - Log.d(LOG_TAG, String.format("getItemViewType: pos: %s, result: %s", position, type, mCachePlaceHoldView, mCacheFirstHeaderView)); - } - return type; - } - - /** - * content view, content view holder, header[0], header and footer placeholder(s) - * - * @return - */ - @Override - public int getViewTypeCount() { - int count = mAdapter == null ? 1 : mAdapter.getViewTypeCount(); - if (mCachePlaceHoldView) { - int offset = mHeaderViewInfos.size() + 1 + mFooterViewInfos.size(); - if (mCacheFirstHeaderView) { - offset += 1; - } - count += offset; - } - if (DEBUG) { - Log.d(LOG_TAG, String.format("getViewTypeCount: %s", count)); - } - return count; - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - mDataSetObservable.registerObserver(observer); - if (mAdapter != null) { - mAdapter.registerDataSetObserver(observer); - } - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - mDataSetObservable.unregisterObserver(observer); - if (mAdapter != null) { - mAdapter.unregisterDataSetObserver(observer); - } - } - - @Override - public Filter getFilter() { - if (mIsFilterable) { - return ((Filterable) mAdapter).getFilter(); - } - return null; - } - - @Override - public ListAdapter getWrappedAdapter() { - return mAdapter; - } - - public void notifyDataSetChanged() { - mDataSetObservable.notifyChanged(); - } - } -} diff --git a/app/src/main/java/github/daneren2005/dsub/view/PlaylistView.java b/app/src/main/java/github/daneren2005/dsub/view/PlaylistView.java index 25613984..29d5cc1c 100644 --- a/app/src/main/java/github/daneren2005/dsub/view/PlaylistView.java +++ b/app/src/main/java/github/daneren2005/dsub/view/PlaylistView.java @@ -50,11 +50,6 @@ public class PlaylistView extends UpdateView { starButton = (ImageButton) findViewById(R.id.item_star); starButton.setFocusable(false); moreButton = (ImageView) findViewById(R.id.item_more); - moreButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - v.showContextMenu(); - } - }); } protected void setObjectImpl(Object obj) { 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..49a09578 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); @@ -145,7 +140,7 @@ public class SongView extends UpdateView implements Checkable { titleTextView.setText(title); artistTextView.setText(artist); - checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE); + checkedTextView.setVisibility(checkable ? View.VISIBLE : View.GONE); this.setBackgroundColor(0x00000000); ratingBar.setVisibility(View.GONE); 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..62d1672d 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,7 @@ public class UpdateView extends LinearLayout { protected SilentBackgroundTask<Void> imageTask = null; protected final boolean autoUpdate; + protected boolean checkable; public UpdateView(Context context) { this(context, true); @@ -75,8 +77,8 @@ public class UpdateView extends LinearLayout { this.autoUpdate = autoUpdate; setLayoutParams(new AbsListView.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); if(autoUpdate) { INSTANCES.put(this, null); @@ -224,8 +226,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 +283,40 @@ public class UpdateView extends LinearLayout { rating = isRated; } } + + public boolean isCheckable() { + return checkable; + } + + public static class UpdateViewHolder<T> extends RecyclerView.ViewHolder { + private UpdateView updateView; + private View view; + private T item; + + public UpdateViewHolder(UpdateView itemView) { + super(itemView); + + this.updateView = itemView; + this.view = itemView; + } + + // Different is so that call is not ambiguous + public UpdateViewHolder(View view, boolean different) { + super(view); + this.view = view; + } + + public UpdateView getUpdateView() { + return updateView; + } + public View getView() { + return view; + } + public void setItem(T item) { + this.item = item; + } + public T getItem() { + return item; + } + } } diff --git a/app/src/main/res/layout/abstract_recycler_fragment.xml b/app/src/main/res/layout/abstract_recycler_fragment.xml new file mode 100644 index 00000000..4c7dd6b8 --- /dev/null +++ b/app/src/main/res/layout/abstract_recycler_fragment.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/refresh_layout" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <LinearLayout + android:id="@+id/fragment_list_layout" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <View + android:layout_width="fill_parent" + android:layout_height="1px" + android:background="@color/dividerColor"/> + + <android.support.v7.widget.RecyclerView + android:id="@+id/fragment_recycler" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1.0" + android:scrollbars="vertical"/> + + <include layout="@layout/tab_progress" /> + </LinearLayout> +</android.support.v4.widget.SwipeRefreshLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/album_cell_item.xml b/app/src/main/res/layout/album_cell_item.xml index 3f708e63..fe634c13 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="?attr/selectableItemBackground"> <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/basic_header.xml b/app/src/main/res/layout/basic_header.xml new file mode 100644 index 00000000..b5ae900a --- /dev/null +++ b/app/src/main/res/layout/basic_header.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/item_name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:background="@android:color/transparent" + android:textColor="@color/cyan" + android:textStyle="bold" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="8dp" + android:paddingBottom="8dp"/>
\ No newline at end of file diff --git a/app/src/main/res/layout/basic_list_item.xml b/app/src/main/res/layout/basic_list_item.xml index 2338f7e0..88d8ca20 100644 --- a/app/src/main/res/layout/basic_list_item.xml +++ b/app/src/main/res/layout/basic_list_item.xml @@ -3,7 +3,7 @@ android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:background="@android:color/transparent"> + android:background="?attr/selectableItemBackground"> <TextView android:id="@+id/item_name" diff --git a/app/src/main/res/layout/genre_list_item.xml b/app/src/main/res/layout/genre_list_item.xml index 6affa24c..a6a34c52 100644 --- a/app/src/main/res/layout/genre_list_item.xml +++ b/app/src/main/res/layout/genre_list_item.xml @@ -3,7 +3,7 @@ android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:background="@android:color/transparent"> + android:background="?attr/selectableItemBackground"> <TextView android:id="@+id/genre_name" 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/select_artist_header.xml b/app/src/main/res/layout/select_artist_header.xml index 2821ce43..0ac9e6a6 100644 --- a/app/src/main/res/layout/select_artist_header.xml +++ b/app/src/main/res/layout/select_artist_header.xml @@ -2,7 +2,9 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground"> + <LinearLayout android:id="@+id/select_artist_folder" android:orientation="horizontal" diff --git a/app/src/main/res/layout/song_list_item.xml b/app/src/main/res/layout/song_list_item.xml index 86f77869..9be27192 100644 --- a/app/src/main/res/layout/song_list_item.xml +++ b/app/src/main/res/layout/song_list_item.xml @@ -3,7 +3,8 @@ android:id="@id/drag_handle" android:orientation="horizontal" android:layout_width="fill_parent" - android:layout_height="?android:attr/listPreferredItemHeight"> + android:layout_height="?android:attr/listPreferredItemHeight" + android:background="?attr/selectableItemBackground"> <CheckedTextView android:id="@+id/song_check" @@ -117,7 +118,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 diff --git a/app/src/main/res/layout/user_list_item.xml b/app/src/main/res/layout/user_list_item.xml index dc2bdab9..aa2d13c9 100644 --- a/app/src/main/res/layout/user_list_item.xml +++ b/app/src/main/res/layout/user_list_item.xml @@ -3,7 +3,7 @@ android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:background="@android:color/transparent"> + android:background="?attr/selectableItemBackground"> <github.daneren2005.dsub.view.RecyclingImageView android:id="@+id/item_avatar" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6e19b4c..bb0a997b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -236,7 +236,9 @@ <string name="starring_content_starred">Starred \"%s\"</string> <string name="starring_content_unstarred">Unstarred \"%s\"</string> <string name="starring_content_error">Failed to update \"%s\", please try later.</string> - + + <string name="playlist.mine">My Playlists</string> + <string name="playlist.shared">Shared Playlists</string> <string name="playlist_error">Failed to grab list of playlists</string> <string name="updated_playlist">Added %1$s songs to \"%2$s\"</string> <string name="updated_playlist_error">Failed to update \"%s\", please try later.</string> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 061cfae7..c4a53b99 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <style name="BasicButton"> - <item name="android:background">@drawable/abc_item_background_holo_light</item> + <item name="android:background">?attr/selectableItemBackground</item> </style> <style name="MoreButton" parent="BasicButton"> |