From 51b971269688e481b5925bbc09b1eb425d3b874e Mon Sep 17 00:00:00 2001 From: Tristan Roux Date: Tue, 23 Apr 2019 00:02:09 +0200 Subject: [PATCH] Finishing playlisttracks rework for local and youtube playlists. --- Opus/Code/DataStructure/PlaylistItem.cs | 1 + Opus/Code/DataStructure/SearchableList.cs | 56 ++++ Opus/Code/Others/ItemTouchCallback.cs | 2 +- Opus/Code/UI/Adapter/BaseCursor.cs | 18 +- Opus/Code/UI/Adapter/BrowseAdapter.cs | 7 +- Opus/Code/UI/Adapter/PlaylistTrackAdapter.cs | 134 +++++---- Opus/Code/UI/Fragments/Home.cs | 4 +- Opus/Code/UI/Fragments/Playlist.cs | 6 +- Opus/Code/UI/Fragments/PlaylistTracks.cs | 258 +++++++++--------- Opus/Code/UI/Fragments/Queue.cs | 2 +- Opus/Opus.csproj | 3 +- Opus/Resources/Resource.Designer.cs | 130 ++++----- ...ecyclerFragment.xml => LonelyRecycler.xml} | 0 13 files changed, 359 insertions(+), 262 deletions(-) create mode 100644 Opus/Code/DataStructure/SearchableList.cs rename Opus/Resources/layout/{RecyclerFragment.xml => LonelyRecycler.xml} (100%) diff --git a/Opus/Code/DataStructure/PlaylistItem.cs b/Opus/Code/DataStructure/PlaylistItem.cs index 55ffe63..10ef731 100644 --- a/Opus/Code/DataStructure/PlaylistItem.cs +++ b/Opus/Code/DataStructure/PlaylistItem.cs @@ -33,6 +33,7 @@ namespace Opus.DataStructure public PlaylistItem(string Name, string YoutubeID, Google.Apis.YouTube.v3.Data.Playlist Snippet = null, int Count = 0) { this.Name = Name; + LocalID = -1; this.YoutubeID = YoutubeID; this.Snippet = Snippet; this.Count = Count; diff --git a/Opus/Code/DataStructure/SearchableList.cs b/Opus/Code/DataStructure/SearchableList.cs new file mode 100644 index 0000000..9d99931 --- /dev/null +++ b/Opus/Code/DataStructure/SearchableList.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Opus.DataStructure +{ + [Serializable] + public class SearchableList : List + { + private List cachedList = null; + private List CachedList + { + get + { + return cachedList ?? this; + } + set + { + cachedList = value; + } + } + + private Func filter = x => true; + + public SearchableList() { } + public SearchableList(IEnumerable collection) : base(collection) { AddRange(collection); } + + public void SetFilter(Func filter) + { + this.filter = filter; + cachedList = this.Where(filter).ToList(); + } + + + public new T this[int index] + { + get + { + return CachedList[index]; + } + } + + public new int Count + { + get + { + return CachedList.Count; + } + } + + public void Invalidate() + { + cachedList = this.Where(filter).ToList(); + } + } +} \ No newline at end of file diff --git a/Opus/Code/Others/ItemTouchCallback.cs b/Opus/Code/Others/ItemTouchCallback.cs index f9e484f..9845df7 100644 --- a/Opus/Code/Others/ItemTouchCallback.cs +++ b/Opus/Code/Others/ItemTouchCallback.cs @@ -23,7 +23,7 @@ namespace Opus.Others if (alwaysAllowSwap) return true; - if (PlaylistTracks.instance != null && (!PlaylistTracks.instance.item.HasWritePermission || ((PlaylistTrackAdapter)adapter)?.IsEmpty == true)) + if (PlaylistTracks.instance != null && (!PlaylistTracks.instance.item.HasWritePermission || ((PlaylistTrackAdapter)adapter)?.BaseCount == 0)) return false; return true; } diff --git a/Opus/Code/UI/Adapter/BaseCursor.cs b/Opus/Code/UI/Adapter/BaseCursor.cs index 559bf0f..4da4a9d 100644 --- a/Opus/Code/UI/Adapter/BaseCursor.cs +++ b/Opus/Code/UI/Adapter/BaseCursor.cs @@ -11,7 +11,7 @@ namespace Opus.Adapter /// This is the number of items (for example headers) there is before the list represented by the cursor. /// public abstract int ItemBefore { get; } - public int BaseCount { get { return cursor != null ? cursor.Count : 0; } } + public virtual int BaseCount { get { return cursor != null ? cursor.Count : 0; } } public override int ItemCount => BaseCount + ItemBefore; public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position) @@ -21,14 +21,16 @@ namespace Opus.Adapter } public abstract void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, T item); - public void SwapCursor(ICursor newCursor) + public void SwapCursor(ICursor newCursor, bool loadingHandling = true) { if (newCursor == cursor) return; if (newCursor != null) { - MainActivity.instance.FindViewById(Resource.Id.loading).Visibility = ViewStates.Gone; + if(loadingHandling) + MainActivity.instance.FindViewById(Resource.Id.loading).Visibility = ViewStates.Gone; + cursor = newCursor; NotifyDataSetChanged(); } @@ -36,7 +38,9 @@ namespace Opus.Adapter { NotifyItemRangeRemoved(0, ItemCount); cursor = null; - MainActivity.instance.FindViewById(Resource.Id.loading).Visibility = ViewStates.Visible; + + if(loadingHandling) + MainActivity.instance.FindViewById(Resource.Id.loading).Visibility = ViewStates.Visible; } } @@ -46,7 +50,7 @@ namespace Opus.Adapter return Convert(cursor); } - public void OnClick(int position) + public virtual void OnClick(int position) { if (position >= ItemBefore) { @@ -59,7 +63,7 @@ namespace Opus.Adapter public abstract void Clicked(T item, int position); public virtual void HeaderClicked(int position) { } - public void OnLongClick(int position) + public virtual void OnLongClick(int position) { if (position >= ItemBefore) { @@ -69,7 +73,7 @@ namespace Opus.Adapter else HeaderLongClicked(position); } - public abstract void LongClicked(T item, int positon); + public abstract void LongClicked(T item, int position); public virtual void HeaderLongClicked(int position) { } public abstract T Convert(ICursor cursor); diff --git a/Opus/Code/UI/Adapter/BrowseAdapter.cs b/Opus/Code/UI/Adapter/BrowseAdapter.cs index 99e50d8..815efc5 100644 --- a/Opus/Code/UI/Adapter/BrowseAdapter.cs +++ b/Opus/Code/UI/Adapter/BrowseAdapter.cs @@ -4,7 +4,6 @@ using Android.Database; using Android.Graphics; using Android.Support.V7.Widget; using Android.Views; -using Opus.Api; using Opus.DataStructure; using Opus.Fragments; using Square.Picasso; @@ -17,9 +16,9 @@ namespace Opus.Adapter public bool displayShuffle; public override int ItemBefore => (displayShuffle && BaseCount != 0) ? 1 : 0; - private Action clickAction; - private Action longClickAction; - private Action headerAction; + private readonly Action clickAction; + private readonly Action longClickAction; + private readonly Action headerAction; public BrowseAdapter(Action clickAction, Action longClickAction, Action headerAction = null) { diff --git a/Opus/Code/UI/Adapter/PlaylistTrackAdapter.cs b/Opus/Code/UI/Adapter/PlaylistTrackAdapter.cs index 17e6a2a..7c404cd 100644 --- a/Opus/Code/UI/Adapter/PlaylistTrackAdapter.cs +++ b/Opus/Code/UI/Adapter/PlaylistTrackAdapter.cs @@ -1,6 +1,7 @@ using Android.App; using Android.Content; using Android.Content.Res; +using Android.Database; using Android.Graphics; using Android.Support.V7.Widget; using Android.Views; @@ -10,55 +11,62 @@ using Opus.DataStructure; using Opus.Fragments; using Opus.Others; using Square.Picasso; -using System; -using System.Collections.Generic; namespace Opus.Adapter { - public class PlaylistTrackAdapter : RecyclerView.Adapter, IItemTouchAdapter + public class PlaylistTrackAdapter : BaseCursor, IItemTouchAdapter { - public List songList; - public event EventHandler ItemClick; - public event EventHandler ItemLongClick; - public int listPadding; - public bool IsEmpty = false; + public SearchableList tracks; public bool IsSliding { get; set; } - public PlaylistTrackAdapter(List songList) + + public PlaylistTrackAdapter() { } + public PlaylistTrackAdapter(SearchableList tracks) { - this.songList = songList; + this.tracks = tracks; + cursor = null; } - public override int ItemCount + public override int ItemBefore { get { - int count = songList.Count + (PlaylistTracks.instance.fullyLoadded ? 0 : 1) + (PlaylistTracks.instance.useHeader ? 0 : 1); - if (count == 0 || (count == 1 && !PlaylistTracks.instance.useHeader)) - { - IsEmpty = true; + int count = PlaylistTracks.instance.useHeader ? 0 : 1; //Display the smallheader if the playlist doesnt use the big header + if (BaseCount == 0 && PlaylistTracks.instance.fullyLoadded) //Display an empty view if the playlist is fully loaded and there is no tracks count++; - } return count; } } + private int ItemAfter + { + get + { + return PlaylistTracks.instance.fullyLoadded ? 0 : 1; //Display a loading bar if the playlist is not fully loaded + } + } + + public override int BaseCount => tracks == null ? base.BaseCount : tracks.Count; + public override int ItemCount => base.ItemCount + ItemAfter; + + public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { - if(IsEmpty && position + 1 == ItemCount) + if (position == ItemCount - 1 && !PlaylistTracks.instance.fullyLoadded) + { + int pad = MainActivity.instance.DpToPx(30); + ((RecyclerView.LayoutParams)viewHolder.ItemView.LayoutParameters).TopMargin = pad; + ((RecyclerView.LayoutParams)viewHolder.ItemView.LayoutParameters).BottomMargin = pad; + } + else if (BaseCount == 0 && position == 0) { ((TextView)viewHolder.ItemView).Text = MainActivity.instance.GetString(Resource.String.playlist_empty); - return; } - - if (!PlaylistTracks.instance.useHeader) - position--; - - if(position == -1 && !PlaylistTracks.instance.useHeader) + else if (position == 0 && !PlaylistTracks.instance.useHeader) { View header = viewHolder.ItemView; - header.FindViewById(Resource.Id.headerNumber).Text = songList.Count + " " + (songList.Count < 2 ? MainActivity.instance.GetString(Resource.String.element) : MainActivity.instance.GetString(Resource.String.elements)); + header.FindViewById(Resource.Id.headerNumber).Text = tracks.Count + " " + (tracks.Count < 2 ? MainActivity.instance.GetString(Resource.String.element) : MainActivity.instance.GetString(Resource.String.elements)); if (!header.FindViewById(Resource.Id.headerPlay).HasOnClickListeners) { header.FindViewById(Resource.Id.headerPlay).Click += (sender, e0) => { PlaylistManager.PlayInOrder(PlaylistTracks.instance.item); }; @@ -82,32 +90,34 @@ namespace Opus.Adapter header.FindViewById(Resource.Id.headerShuffle).ImageTintList = ColorStateList.ValueOf(Color.Black); header.FindViewById(Resource.Id.headerMore).ImageTintList = ColorStateList.ValueOf(Color.Black); } - - return; } + else if (tracks != null) + OnBindViewHolder(viewHolder, tracks[position - ItemBefore]); + else + base.OnBindViewHolder(viewHolder, position); + } - if(position >= songList.Count) - return; - + public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, Song item) + { SongHolder holder = (SongHolder)viewHolder; - holder.Title.Text = songList[position].Title; - holder.Artist.Text = songList[position].Artist; + holder.Title.Text = item.Title; + holder.Artist.Text = item.Artist; - if (songList[position].AlbumArt == -1 || songList[position].IsYt) + if (item.AlbumArt == -1 || item.IsYt) { - var songAlbumArtUri = Android.Net.Uri.Parse(songList[position].Album); - Picasso.With(Application.Context).Load(songAlbumArtUri).Placeholder(Resource.Color.background_material_dark).Transform(new RemoveBlackBorder(true)).Into(holder.AlbumArt); + var songAlbumArtUri = Android.Net.Uri.Parse(item.Album); + Picasso.With(Application.Context).Load(songAlbumArtUri).Placeholder(Resource.Color.placeholder).Transform(new RemoveBlackBorder(true)).Into(holder.AlbumArt); } else { var songCover = Android.Net.Uri.Parse("content://media/external/audio/albumart"); - var songAlbumArtUri = ContentUris.WithAppendedId(songCover, songList[position].AlbumArt); + var songAlbumArtUri = ContentUris.WithAppendedId(songCover, item.AlbumArt); - Picasso.With(Application.Context).Load(songAlbumArtUri).Placeholder(Resource.Color.background_material_dark).Resize(400, 400).CenterCrop().Into(holder.AlbumArt); + Picasso.With(Application.Context).Load(songAlbumArtUri).Placeholder(Resource.Color.placeholder).Resize(400, 400).CenterCrop().Into(holder.AlbumArt); } - if (songList[position].IsLiveStream) + if (item.IsLiveStream) holder.Live.Visibility = ViewStates.Visible; else holder.Live.Visibility = ViewStates.Gone; @@ -116,7 +126,7 @@ namespace Opus.Adapter { holder.more.Click += (sender, e) => { - PlaylistTracks.instance.More(holder.AdapterPosition); + PlaylistTracks.instance.More(tracks == null ? GetItem(holder.AdapterPosition) : tracks[holder.AdapterPosition], holder.AdapterPosition); }; } @@ -131,6 +141,37 @@ namespace Opus.Adapter } } + public override void OnClick(int position) + { + if (tracks != null && position >= ItemBefore) + Clicked(tracks[position - ItemBefore], position - ItemBefore); + else + base.OnClick(position); + } + + public override void Clicked(Song item, int position) + { + PlaylistManager.PlayInOrder(PlaylistTracks.instance.item, position); + } + + public override void OnLongClick(int position) + { + if (tracks != null && position >= ItemBefore) + LongClicked(tracks[position - ItemBefore], position - ItemBefore); + else + base.OnLongClick(position); + } + + public override void LongClicked(Song item, int position) + { + PlaylistTracks.instance.More(item, position); + } + + public override Song Convert(ICursor cursor) + { + return Song.FromCursor(cursor); + } + public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { if(viewType == 0) @@ -157,25 +198,16 @@ namespace Opus.Adapter public override int GetItemViewType(int position) { - if (IsEmpty && position + 1 == ItemCount) + if (position == ItemCount - 1 && !PlaylistTracks.instance.fullyLoadded) + return 1; + else if (BaseCount == 0 && position == 0) return 3; if (position == 0 && !PlaylistTracks.instance.useHeader) return 2; - else if (position - (PlaylistTracks.instance.useHeader ? 0 : 1) >= songList.Count) - return 1; return 0; } - void OnClick(int position) - { - ItemClick?.Invoke(this, position); - } - - void OnLongClick(int position) - { - ItemLongClick?.Invoke(this, position); - } public void ItemMoved(int fromPosition, int toPosition) { } @@ -183,7 +215,7 @@ namespace Opus.Adapter public void ItemDismissed(int position) { - PlaylistTracks.instance.RemoveFromPlaylist(songList[position], position); + PlaylistTracks.instance.RemoveFromPlaylist(tracks[position], position); } } } \ No newline at end of file diff --git a/Opus/Code/UI/Fragments/Home.cs b/Opus/Code/UI/Fragments/Home.cs index b290753..8db1493 100644 --- a/Opus/Code/UI/Fragments/Home.cs +++ b/Opus/Code/UI/Fragments/Home.cs @@ -48,7 +48,8 @@ namespace Opus.Fragments #pragma warning disable CS4014 public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - view = inflater.Inflate(Resource.Layout.RecyclerFragment, container, false); + view = inflater.Inflate(Resource.Layout.CompleteRecycler, container, false); + view.FindViewById(Resource.Id.loading).Visibility = ViewStates.Visible; ListView = view.FindViewById(Resource.Id.recycler); ListView.SetLayoutManager(new LinearLayoutManager(Android.App.Application.Context)); @@ -122,6 +123,7 @@ namespace Opus.Fragments } } + view.FindViewById(Resource.Id.loading).Visibility = ViewStates.Gone; adapter = new HomeAdapter(adapterItems); ListView.SetAdapter(adapter); adapter.ItemClick += ListView_ItemClick; diff --git a/Opus/Code/UI/Fragments/Playlist.cs b/Opus/Code/UI/Fragments/Playlist.cs index cae70cd..8d94a9c 100644 --- a/Opus/Code/UI/Fragments/Playlist.cs +++ b/Opus/Code/UI/Fragments/Playlist.cs @@ -25,6 +25,7 @@ namespace Opus.Fragments public static Playlist instance; public RecyclerView ListView; private PlaylistAdapter adapter; + private View LoadingView; private bool populating = false; private List LocalPlaylists = new List(); @@ -46,7 +47,9 @@ namespace Opus.Fragments public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.Inflate(Resource.Layout.RecyclerFragment, container, false); + View view = inflater.Inflate(Resource.Layout.CompleteRecycler, container, false); + LoadingView = view.FindViewById(Resource.Id.loading); + LoadingView.Visibility = ViewStates.Visible; ListView = view.FindViewById(Resource.Id.recycler); ListView.SetLayoutManager(new LinearLayoutManager(Application.Context)); instance = this; @@ -92,6 +95,7 @@ namespace Opus.Fragments YoutubePlaylists.AddRange(SyncedPlaylists); //Display this for now, we'll load non synced youtube playlist in the background. + LoadingView.Visibility = ViewStates.Gone; YoutubePlaylists.Add(Loading); adapter = new PlaylistAdapter(LocalPlaylists, YoutubePlaylists); ListView.SetAdapter(adapter); diff --git a/Opus/Code/UI/Fragments/PlaylistTracks.cs b/Opus/Code/UI/Fragments/PlaylistTracks.cs index 845e020..3536f12 100644 --- a/Opus/Code/UI/Fragments/PlaylistTracks.cs +++ b/Opus/Code/UI/Fragments/PlaylistTracks.cs @@ -6,6 +6,7 @@ using Android.Support.Design.Widget; using Android.Support.V4.App; using Android.Views; using Android.Widget; +using Java.Lang; using Opus.Adapter; using Opus.Api; using Opus.DataStructure; @@ -22,24 +23,26 @@ using Toolbar = Android.Support.V7.Widget.Toolbar; namespace Opus.Fragments { - public class PlaylistTracks : Fragment, PopupMenu.IOnMenuItemClickListener, AppBarLayout.IOnOffsetChangedListener + public class PlaylistTracks : Fragment, PopupMenu.IOnMenuItemClickListener, AppBarLayout.IOnOffsetChangedListener, LoaderManager.ILoaderCallbacks { public static PlaylistTracks instance; public PlaylistItem item; + + public TextView EmptyView; public RecyclerView ListView; public PlaylistTrackAdapter adapter; private Android.Support.V7.Widget.Helper.ItemTouchHelper itemTouchHelper; - public List result = null; + + private string query; private string nextPageToken = null; public bool fullyLoadded = true; public bool forked; public bool lastVisible = false; public bool useHeader = true; public bool navigating = false; - - public List tracks = new List(); private bool loading = false; + public override void OnActivityCreated(Bundle savedInstanceState) { base.OnActivityCreated(savedInstanceState); @@ -55,10 +58,10 @@ namespace Opus.Fragments public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.Inflate(Resource.Layout.RecyclerFragment, container, false); + View view = inflater.Inflate(Resource.Layout.LonelyRecycler, container, false); ListView = view.FindViewById(Resource.Id.recycler); ListView.SetLayoutManager(new Android.Support.V7.Widget.LinearLayoutManager(MainActivity.instance)); - ListView.SetAdapter(new PlaylistTrackAdapter(new List())); + ListView.SetAdapter(new PlaylistTrackAdapter(new SearchableList())); ListView.ScrollChange += ListView_ScrollChange; #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed @@ -72,9 +75,7 @@ namespace Opus.Fragments private void ListView_ScrollChange(object sender, View.ScrollChangeEventArgs e) { if (((Android.Support.V7.Widget.LinearLayoutManager)ListView?.GetLayoutManager())?.FindLastVisibleItemPosition() == adapter?.ItemCount - 1) -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed LoadMore(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } void CreateHeader() @@ -85,9 +86,9 @@ namespace Opus.Fragments Activity.FindViewById(Resource.Id.headerTitle).Text = item.Name; if (!Activity.FindViewById(Resource.Id.headerPlay).HasOnClickListeners) - Activity.FindViewById(Resource.Id.headerPlay).Click += (sender, e0) => { PlaylistManager.PlayInOrder(item); }; + Activity.FindViewById(Resource.Id.headerPlay).Click += HeaderPlay; if (!Activity.FindViewById(Resource.Id.headerShuffle).HasOnClickListeners) - Activity.FindViewById(Resource.Id.headerShuffle).Click += (sender, e0) => { PlaylistManager.Shuffle(item); }; + Activity.FindViewById(Resource.Id.headerShuffle).Click += HeaderShuffle; if (!Activity.FindViewById(Resource.Id.headerMore).HasOnClickListeners) Activity.FindViewById(Resource.Id.headerMore).Click += PlaylistMore; @@ -107,8 +108,20 @@ namespace Opus.Fragments Activity.FindViewById(Resource.Id.collapsingToolbar).RequestLayout(); } + void HeaderPlay(object sender, System.EventArgs e) + { + PlaylistManager.PlayInOrder(item); + } + + void HeaderShuffle(object sender, System.EventArgs e) + { + PlaylistManager.Shuffle(item); + } + public override void OnDestroyView() { + Activity.FindViewById(Resource.Id.headerPlay).Click -= HeaderPlay; + Activity.FindViewById(Resource.Id.headerShuffle).Click -= HeaderShuffle; Activity.FindViewById(Resource.Id.headerMore).Click -= PlaylistMore; if (!MainActivity.instance.Paused) @@ -158,7 +171,6 @@ namespace Opus.Fragments break; case Resource.Id.fork: -#pragma warning disable CS4014 PlaylistManager.ForkPlaylist(item.YoutubeID); break; @@ -174,7 +186,10 @@ namespace Opus.Fragments break; case Resource.Id.sync: - PlaylistManager.StopSyncingDialog(item, () => { /*SHOULD RECREATE CURSOR LOADER HERE*/ }); + PlaylistManager.StopSyncingDialog(item, () => + { + MainActivity.instance.SupportFragmentManager.PopBackStack(); + }); break; case Resource.Id.delete: @@ -231,73 +246,40 @@ namespace Opus.Fragments async Task PopulateList() { - if (item.LocalID == 0 && item.YoutubeID == "" && tracks.Count == 0) + if (item.LocalID == -1 && item.YoutubeID == ""/* && tracks.Count == 0*/) return; - if (item.LocalID != 0) + if (item.LocalID != -1) { - Uri musicUri = Playlists.Members.GetContentUri("external", item.LocalID); - - CursorLoader cursorLoader = new CursorLoader(Android.App.Application.Context, musicUri, null, null, null, null); - ICursor musicCursor = (ICursor)cursorLoader.LoadInBackground(); - - tracks = new List(); - - if (musicCursor != null && musicCursor.MoveToFirst()) + if (await MainActivity.instance.GetReadPermission() == false) { - int titleID = musicCursor.GetColumnIndex(Media.InterfaceConsts.Title); - int artistID = musicCursor.GetColumnIndex(Media.InterfaceConsts.Artist); - int albumID = musicCursor.GetColumnIndex(Media.InterfaceConsts.Album); - int albumArtID = musicCursor.GetColumnIndex(Albums.InterfaceConsts.AlbumId); - int thisID = musicCursor.GetColumnIndex(Playlists.Members.AudioId); - int pathID = musicCursor.GetColumnIndex(Media.InterfaceConsts.Data); - do - { - string Artist = musicCursor.GetString(artistID); - string Title = musicCursor.GetString(titleID); - string Album = musicCursor.GetString(albumID); - long AlbumArt = musicCursor.GetLong(albumArtID); - long id = musicCursor.GetLong(thisID); - string path = musicCursor.GetString(pathID); - - if (Title == null) - Title = "Unknown Title"; - if (Artist == null) - Artist = "Unknow Artist"; - if (Album == null) - Album = "Unknow Album"; - - Song song = new Song(Title, Artist, Album, null, AlbumArt, id, path); - if (item.SyncState == SyncState.True) - song = LocalManager.CompleteItem(song); - - tracks.Add(song); - } - while (musicCursor.MoveToNext()); - musicCursor.Close(); + MainActivity.instance.FindViewById(Resource.Id.loading).Visibility = ViewStates.Gone; + //adapter.ErrorMSG = GetString(Resource.String.no_permission); + return; } - adapter = new PlaylistTrackAdapter(tracks); - adapter.ItemClick += ListView_ItemClick; - adapter.ItemLongClick += ListView_ItemLongClick; + adapter = new PlaylistTrackAdapter(); ListView.SetAdapter(adapter); Android.Support.V7.Widget.Helper.ItemTouchHelper.Callback callback = new ItemTouchCallback(adapter, false); itemTouchHelper = new Android.Support.V7.Widget.Helper.ItemTouchHelper(callback); itemTouchHelper.AttachToRecyclerView(ListView); - Activity.FindViewById(Resource.Id.headerNumber).Text = tracks.Count.ToString() + " songs"; - var songCover = Uri.Parse("content://media/external/audio/albumart"); - var songAlbumArtUri = ContentUris.WithAppendedId(songCover, tracks[0].AlbumArt); - - if(item.ImageURL == null) - Picasso.With(Android.App.Application.Context).Load(songAlbumArtUri).Placeholder(Resource.Drawable.noAlbum).Resize(1080, 1080).CenterCrop().Into(Activity.FindViewById(Resource.Id.headerArt)); + LoaderManager.GetInstance(this).InitLoader(0, null, this); } else if (item.YoutubeID != null) { + fullyLoadded = false; + SearchableList tracks = new SearchableList(); + adapter = new PlaylistTrackAdapter(tracks); + ListView.SetAdapter(adapter); + + Android.Support.V7.Widget.Helper.ItemTouchHelper.Callback callback = new ItemTouchCallback(adapter, false); + itemTouchHelper = new Android.Support.V7.Widget.Helper.ItemTouchHelper(callback); + itemTouchHelper.AttachToRecyclerView(ListView); + try { - tracks = new List(); var ytPlaylistRequest = YoutubeSearch.youtubeService.PlaylistItems.List("snippet, contentDetails"); ytPlaylistRequest.PlaylistId = item.YoutubeID; ytPlaylistRequest.MaxResults = 50; @@ -317,32 +299,59 @@ namespace Opus.Fragments } nextPageToken = ytPlaylist.NextPageToken; - if(nextPageToken == null) + if (nextPageToken == null) fullyLoadded = true; - adapter = new PlaylistTrackAdapter(tracks); - adapter.ItemClick += ListView_ItemClick; - adapter.ItemLongClick += ListView_ItemLongClick; - ListView.SetAdapter(adapter); + tracks.Invalidate(); + adapter.NotifyDataSetChanged(); - Android.Support.V7.Widget.Helper.ItemTouchHelper.Callback callback = new ItemTouchCallback(adapter, false); - itemTouchHelper = new Android.Support.V7.Widget.Helper.ItemTouchHelper(callback); - itemTouchHelper.AttachToRecyclerView(ListView); } catch (System.Net.Http.HttpRequestException) { MainActivity.instance.Timout(); } } - else if(tracks.Count != 0) + //else if(tracks.Count != 0) + //{ + // adapter = new PlaylistTrackAdapter(tracks); + // adapter.ItemClick += ListView_ItemClick; + // adapter.ItemLongClick += ListView_ItemLongClick; + // ListView.SetAdapter(adapter); + //} + } + + public Android.Support.V4.Content.Loader OnCreateLoader(int id, Bundle args) + { + Uri musicUri = Playlists.Members.GetContentUri("external", item.LocalID); + string selection; + if (query != null) + selection = Media.InterfaceConsts.Title + " LIKE \"%" + query + "%\" OR " + Media.InterfaceConsts.Artist + " LIKE \"%" + query + "%\""; + else + selection = null; + + return new CursorLoader(Android.App.Application.Context, musicUri, null, selection, null, null); + } + + public void OnLoadFinished(Android.Support.V4.Content.Loader loader, Object data) + { + adapter.SwapCursor((ICursor)data, false); + + if (item.LocalID != -1) { - adapter = new PlaylistTrackAdapter(tracks); - adapter.ItemClick += ListView_ItemClick; - adapter.ItemLongClick += ListView_ItemLongClick; - ListView.SetAdapter(adapter); + Activity.FindViewById(Resource.Id.headerNumber).Text = item.Count.ToString() + " " + GetString(Resource.String.elements); + var songCover = Uri.Parse("content://media/external/audio/albumart"); + var songAlbumArtUri = ContentUris.WithAppendedId(songCover, adapter.GetItem(0).AlbumArt); + + if (item.ImageURL == null) + Picasso.With(Android.App.Application.Context).Load(songAlbumArtUri).Placeholder(Resource.Drawable.noAlbum).Resize(1080, 1080).CenterCrop().Into(Activity.FindViewById(Resource.Id.headerArt)); } } + public void OnLoaderReset(Android.Support.V4.Content.Loader loader) + { + adapter.SwapCursor(null, false); + } + string SongToName(Song song) { return song.Title; @@ -359,7 +368,7 @@ namespace Opus.Fragments MainActivity.instance.contentRefresh.Refreshing = false; } - public async Task LoadMore() + public async void LoadMore() { if (nextPageToken == null || loading) return; @@ -377,6 +386,8 @@ namespace Opus.Fragments if (instance == null) return; + int countBefore = adapter.BaseCount; + foreach (var item in ytPlaylist.Items) { if (item.Snippet.Title != "[Deleted video]" && item.Snippet.Title != "Private video" && item.Snippet.Title != "Deleted video") @@ -385,80 +396,67 @@ namespace Opus.Fragments { TrackID = item.Id }; - tracks.Add(song); + adapter.tracks.Add(song); } } + adapter.tracks.Invalidate(); + adapter.NotifyItemRangeInserted(countBefore, adapter.tracks.Count - countBefore); + nextPageToken = ytPlaylist.NextPageToken; if (nextPageToken == null) + { fullyLoadded = true; - adapter.NotifyDataSetChanged(); + adapter.NotifyItemRemoved(adapter.ItemCount); + } } catch (System.Net.Http.HttpRequestException) { MainActivity.instance.Timout(); } loading = false; + + //We are still at the end of the list, should load the rest (normaly because of the search). + if (!fullyLoadded && ((Android.Support.V7.Widget.LinearLayoutManager)ListView?.GetLayoutManager())?.FindLastVisibleItemPosition() == adapter?.ItemCount - 1) + LoadMore(); } public void Search(string search) { - result = new List(); - for (int i = 0; i < tracks.Count; i++) + if (search == "") + query = null; + else + query = search.ToLower(); + + if(item.LocalID != -1) + LoaderManager.GetInstance(this).RestartLoader(0, null, this); + else { - Song item = tracks[i]; - if (item.Title.ToLower().Contains(search.ToLower()) || item.Artist.ToLower().Contains(search.ToLower())) - { - result.Add(item); - } + if (query == null) + adapter.tracks.SetFilter(x => true); + else + adapter.tracks.SetFilter(x => x.Title.ToLower().Contains(query) || x.Artist.ToLower().Contains(query)); + + adapter.NotifyDataSetChanged(); } - adapter = new PlaylistTrackAdapter(result); - adapter.ItemClick += ListView_ItemClick; - adapter.ItemLongClick += ListView_ItemLongClick; - - Android.Support.V7.Widget.Helper.ItemTouchHelper.Callback callback = new ItemTouchCallback(adapter, false); - itemTouchHelper = new Android.Support.V7.Widget.Helper.ItemTouchHelper(callback); - itemTouchHelper.AttachToRecyclerView(ListView); - - ListView.SetAdapter(adapter); } - private void ListView_ItemClick(object sender, int Position) + public void More(Song song, int position) { - if (!useHeader) - Position--; - - PlaylistManager.PlayInOrder(item, Position); - } - - private void ListView_ItemLongClick(object sender, int Position) - { - More(Position); - } - - public void More(int position) - { - if (!useHeader) - position--; - - Song item = tracks[position]; - if (result != null && result.Count > position) - item = result[position]; - BottomSheetDialog bottomSheet = new BottomSheetDialog(MainActivity.instance); View bottomView = LayoutInflater.Inflate(Resource.Layout.BottomSheet, null); - bottomView.FindViewById(Resource.Id.bsTitle).Text = item.Title; - bottomView.FindViewById(Resource.Id.bsArtist).Text = item.Artist; - if (item.Album == null) + bottomView.FindViewById(Resource.Id.bsTitle).Text = song.Title; + bottomView.FindViewById(Resource.Id.bsArtist).Text = song.Artist; + if (song.Album == null) { var songCover = Uri.Parse("content://media/external/audio/albumart"); - var nextAlbumArtUri = ContentUris.WithAppendedId(songCover, item.AlbumArt); + var nextAlbumArtUri = ContentUris.WithAppendedId(songCover, song.AlbumArt); Picasso.With(MainActivity.instance).Load(nextAlbumArtUri).Placeholder(Resource.Drawable.noAlbum).Resize(400, 400).CenterCrop().Into(bottomView.FindViewById(Resource.Id.bsArt)); } else { - Picasso.With(MainActivity.instance).Load(item.Album).Placeholder(Resource.Drawable.noAlbum).Transform(new RemoveBlackBorder(true)).Into(bottomView.FindViewById(Resource.Id.bsArt)); + Picasso.With(MainActivity.instance).Load(song.Album).Placeholder(Resource.Drawable.noAlbum).Transform(new RemoveBlackBorder(true)).Into(bottomView.FindViewById(Resource.Id.bsArt)); } bottomSheet.SetContentView(bottomView); @@ -466,40 +464,40 @@ namespace Opus.Fragments { new BottomSheetAction(Resource.Drawable.Play, Resources.GetString(Resource.String.play), (sender, eventArg) => { - PlaylistManager.PlayInOrder(this.item, position); + PlaylistManager.PlayInOrder(item, position); bottomSheet.Dismiss(); }), new BottomSheetAction(Resource.Drawable.PlaylistPlay, Resources.GetString(Resource.String.play_next), (sender, eventArg) => { - SongManager.PlayNext(item); + SongManager.PlayNext(song); bottomSheet.Dismiss(); }), new BottomSheetAction(Resource.Drawable.Queue, Resources.GetString(Resource.String.play_last), (sender, eventArg) => { - SongManager.PlayLast(item); + SongManager.PlayLast(song); bottomSheet.Dismiss(); }), new BottomSheetAction(Resource.Drawable.PlaylistAdd, Resources.GetString(Resource.String.add_to_playlist), (sender, eventArg) => { - PlaylistManager.AddSongToPlaylistDialog(item); + PlaylistManager.AddSongToPlaylistDialog(song); bottomSheet.Dismiss(); }) }; - if (this.item.HasWritePermission) + if (item.HasWritePermission) { actions.Add(new BottomSheetAction(Resource.Drawable.Close, Resources.GetString(Resource.String.remove_track_from_playlist), (sender, eventArg) => { - RemoveFromPlaylist(item, position); + RemoveFromPlaylist(song, position); bottomSheet.Dismiss(); })); } - if (!item.IsYt) + if (!song.IsYt) { actions.Add(new BottomSheetAction(Resource.Drawable.Edit, Resources.GetString(Resource.String.edit_metadata), (sender, eventArg) => { - LocalManager.EditMetadata(item); + LocalManager.EditMetadata(song); bottomSheet.Dismiss(); })); } @@ -509,12 +507,12 @@ namespace Opus.Fragments { new BottomSheetAction(Resource.Drawable.PlayCircle, Resources.GetString(Resource.String.create_mix_from_song), (sender, eventArg) => { - YoutubeManager.CreateMixFromSong(item); + YoutubeManager.CreateMixFromSong(song); bottomSheet.Dismiss(); }), new BottomSheetAction(Resource.Drawable.Download, Resources.GetString(Resource.String.download), (sender, eventArg) => { - YoutubeManager.Download(new[] { item }, null); + YoutubeManager.Download(new[] { song }, null); bottomSheet.Dismiss(); }) }); diff --git a/Opus/Code/UI/Fragments/Queue.cs b/Opus/Code/UI/Fragments/Queue.cs index 756b8e1..2911aaa 100644 --- a/Opus/Code/UI/Fragments/Queue.cs +++ b/Opus/Code/UI/Fragments/Queue.cs @@ -36,7 +36,7 @@ public class Queue : Fragment, RecyclerView.IOnItemTouchListener, PopupMenu.IOnM public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.Inflate(Resource.Layout.RecyclerFragment, container, false); + View view = inflater.Inflate(Resource.Layout.LonelyRecycler, container, false); instance = this; ListView = view.FindViewById(Resource.Id.recycler); ListView.SetLayoutManager(new LinearLayoutManager(Application.Context)); diff --git a/Opus/Opus.csproj b/Opus/Opus.csproj index f5b7b72..f9a1e0d 100644 --- a/Opus/Opus.csproj +++ b/Opus/Opus.csproj @@ -322,6 +322,7 @@ + @@ -515,7 +516,7 @@ - + Designer diff --git a/Opus/Resources/Resource.Designer.cs b/Opus/Resources/Resource.Designer.cs index 3ba124c..4d81fc9 100644 --- a/Opus/Resources/Resource.Designer.cs +++ b/Opus/Resources/Resource.Designer.cs @@ -6756,199 +6756,199 @@ namespace Opus public const int LogOutButton = 2130903117; // aapt resource value: 0x7f03004e - public const int Main = 2130903118; + public const int LonelyRecycler = 2130903118; // aapt resource value: 0x7f03004f - public const int mr_cast_dialog = 2130903119; + public const int Main = 2130903119; // aapt resource value: 0x7f030050 - public const int mr_cast_group_item = 2130903120; + public const int mr_cast_dialog = 2130903120; // aapt resource value: 0x7f030051 - public const int mr_cast_group_volume_item = 2130903121; + public const int mr_cast_group_item = 2130903121; // aapt resource value: 0x7f030052 - public const int mr_cast_media_metadata = 2130903122; + public const int mr_cast_group_volume_item = 2130903122; // aapt resource value: 0x7f030053 - public const int mr_cast_route_item = 2130903123; + public const int mr_cast_media_metadata = 2130903123; // aapt resource value: 0x7f030054 - public const int mr_chooser_dialog = 2130903124; + public const int mr_cast_route_item = 2130903124; // aapt resource value: 0x7f030055 - public const int mr_chooser_list_item = 2130903125; + public const int mr_chooser_dialog = 2130903125; // aapt resource value: 0x7f030056 - public const int mr_controller_material_dialog_b = 2130903126; + public const int mr_chooser_list_item = 2130903126; // aapt resource value: 0x7f030057 - public const int mr_controller_volume_item = 2130903127; + public const int mr_controller_material_dialog_b = 2130903127; // aapt resource value: 0x7f030058 - public const int mr_dialog_header_item = 2130903128; + public const int mr_controller_volume_item = 2130903128; // aapt resource value: 0x7f030059 - public const int mr_picker_dialog = 2130903129; + public const int mr_dialog_header_item = 2130903129; // aapt resource value: 0x7f03005a - public const int mr_picker_route_item = 2130903130; + public const int mr_picker_dialog = 2130903130; // aapt resource value: 0x7f03005b - public const int mr_playback_control = 2130903131; + public const int mr_picker_route_item = 2130903131; // aapt resource value: 0x7f03005c - public const int mr_volume_control = 2130903132; + public const int mr_playback_control = 2130903132; // aapt resource value: 0x7f03005d - public const int mtrl_layout_snackbar = 2130903133; + public const int mr_volume_control = 2130903133; // aapt resource value: 0x7f03005e - public const int mtrl_layout_snackbar_include = 2130903134; + public const int mtrl_layout_snackbar = 2130903134; // aapt resource value: 0x7f03005f - public const int MusicLayout = 2130903135; + public const int mtrl_layout_snackbar_include = 2130903135; // aapt resource value: 0x7f030060 - public const int NoSong = 2130903136; + public const int MusicLayout = 2130903136; // aapt resource value: 0x7f030061 - public const int notification_action = 2130903137; + public const int NoSong = 2130903137; // aapt resource value: 0x7f030062 - public const int notification_action_tombstone = 2130903138; + public const int notification_action = 2130903138; // aapt resource value: 0x7f030063 - public const int notification_media_action = 2130903139; + public const int notification_action_tombstone = 2130903139; // aapt resource value: 0x7f030064 - public const int notification_media_cancel_action = 2130903140; + public const int notification_media_action = 2130903140; // aapt resource value: 0x7f030065 - public const int notification_template_big_media = 2130903141; + public const int notification_media_cancel_action = 2130903141; // aapt resource value: 0x7f030066 - public const int notification_template_big_media_custom = 2130903142; + public const int notification_template_big_media = 2130903142; // aapt resource value: 0x7f030067 - public const int notification_template_big_media_narrow = 2130903143; + public const int notification_template_big_media_custom = 2130903143; // aapt resource value: 0x7f030068 - public const int notification_template_big_media_narrow_custom = 2130903144; + public const int notification_template_big_media_narrow = 2130903144; // aapt resource value: 0x7f030069 - public const int notification_template_custom_big = 2130903145; + public const int notification_template_big_media_narrow_custom = 2130903145; // aapt resource value: 0x7f03006a - public const int notification_template_icon_group = 2130903146; + public const int notification_template_custom_big = 2130903146; // aapt resource value: 0x7f03006b - public const int notification_template_lines_media = 2130903147; + public const int notification_template_icon_group = 2130903147; // aapt resource value: 0x7f03006c - public const int notification_template_media = 2130903148; + public const int notification_template_lines_media = 2130903148; // aapt resource value: 0x7f03006d - public const int notification_template_media_custom = 2130903149; + public const int notification_template_media = 2130903149; // aapt resource value: 0x7f03006e - public const int notification_template_part_chronometer = 2130903150; + public const int notification_template_media_custom = 2130903150; // aapt resource value: 0x7f03006f - public const int notification_template_part_time = 2130903151; + public const int notification_template_part_chronometer = 2130903151; // aapt resource value: 0x7f030070 - public const int NoYtPlaylist = 2130903152; + public const int notification_template_part_time = 2130903152; // aapt resource value: 0x7f030071 - public const int NumberPicker = 2130903153; + public const int NoYtPlaylist = 2130903153; // aapt resource value: 0x7f030072 - public const int player = 2130903154; + public const int NumberPicker = 2130903154; // aapt resource value: 0x7f030073 - public const int playerInfo = 2130903155; + public const int player = 2130903155; // aapt resource value: 0x7f030074 - public const int PlaylistHeader = 2130903156; + public const int playerInfo = 2130903156; // aapt resource value: 0x7f030075 - public const int PlaylistItem = 2130903157; + public const int PlaylistHeader = 2130903157; // aapt resource value: 0x7f030076 - public const int PlaylistList = 2130903158; + public const int PlaylistItem = 2130903158; // aapt resource value: 0x7f030077 - public const int PlaylistSmallHeader = 2130903159; + public const int PlaylistList = 2130903159; // aapt resource value: 0x7f030078 - public const int preference = 2130903160; + public const int PlaylistSmallHeader = 2130903160; // aapt resource value: 0x7f030079 - public const int preference_category = 2130903161; + public const int preference = 2130903161; // aapt resource value: 0x7f03007a - public const int preference_category_material = 2130903162; + public const int preference_category = 2130903162; // aapt resource value: 0x7f03007b - public const int preference_dialog_edittext = 2130903163; + public const int preference_category_material = 2130903163; // aapt resource value: 0x7f03007c - public const int preference_dropdown = 2130903164; + public const int preference_dialog_edittext = 2130903164; // aapt resource value: 0x7f03007d - public const int preference_dropdown_material = 2130903165; + public const int preference_dropdown = 2130903165; // aapt resource value: 0x7f03007e - public const int preference_information = 2130903166; + public const int preference_dropdown_material = 2130903166; // aapt resource value: 0x7f03007f - public const int preference_information_material = 2130903167; + public const int preference_information = 2130903167; // aapt resource value: 0x7f030080 - public const int preference_list_fragment = 2130903168; + public const int preference_information_material = 2130903168; // aapt resource value: 0x7f030081 - public const int preference_material = 2130903169; + public const int preference_list_fragment = 2130903169; // aapt resource value: 0x7f030082 - public const int preference_recyclerview = 2130903170; + public const int preference_material = 2130903170; // aapt resource value: 0x7f030083 - public const int preference_widget_checkbox = 2130903171; + public const int preference_recyclerview = 2130903171; // aapt resource value: 0x7f030084 - public const int preference_widget_seekbar = 2130903172; + public const int preference_widget_checkbox = 2130903172; // aapt resource value: 0x7f030085 - public const int preference_widget_seekbar_material = 2130903173; + public const int preference_widget_seekbar = 2130903173; // aapt resource value: 0x7f030086 - public const int preference_widget_switch = 2130903174; + public const int preference_widget_seekbar_material = 2130903174; // aapt resource value: 0x7f030087 - public const int preference_widget_switch_compat = 2130903175; + public const int preference_widget_switch = 2130903175; // aapt resource value: 0x7f030088 - public const int PreferenceCategory = 2130903176; + public const int preference_widget_switch_compat = 2130903176; // aapt resource value: 0x7f030089 - public const int PreferenceRoot = 2130903177; + public const int PreferenceCategory = 2130903177; // aapt resource value: 0x7f03008a - public const int Preferences = 2130903178; + public const int PreferenceRoot = 2130903178; // aapt resource value: 0x7f03008b - public const int QueueCurrent = 2130903179; + public const int Preferences = 2130903179; // aapt resource value: 0x7f03008c - public const int QueueFooter = 2130903180; + public const int QueueCurrent = 2130903180; // aapt resource value: 0x7f03008d - public const int QueueHeader = 2130903181; + public const int QueueFooter = 2130903181; // aapt resource value: 0x7f03008e - public const int RecyclerFragment = 2130903182; + public const int QueueHeader = 2130903182; // aapt resource value: 0x7f03008f public const int SaveAPlaylist = 2130903183; diff --git a/Opus/Resources/layout/RecyclerFragment.xml b/Opus/Resources/layout/LonelyRecycler.xml similarity index 100% rename from Opus/Resources/layout/RecyclerFragment.xml rename to Opus/Resources/layout/LonelyRecycler.xml