Finishing playlisttracks rework for local and youtube playlists.

This commit is contained in:
Tristan Roux
2019-04-23 00:02:09 +02:00
parent c40294b3d0
commit 51b9712696
13 changed files with 359 additions and 262 deletions

View File

@@ -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;

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Opus.DataStructure
{
[Serializable]
public class SearchableList<T> : List<T>
{
private List<T> cachedList = null;
private List<T> CachedList
{
get
{
return cachedList ?? this;
}
set
{
cachedList = value;
}
}
private Func<T, bool> filter = x => true;
public SearchableList() { }
public SearchableList(IEnumerable<T> collection) : base(collection) { AddRange(collection); }
public void SetFilter(Func<T, bool> 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();
}
}
}

View File

@@ -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;
}

View File

@@ -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.
/// </summary>
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);

View File

@@ -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<Song, int> clickAction;
private Action<Song, int> longClickAction;
private Action<int> headerAction;
private readonly Action<Song, int> clickAction;
private readonly Action<Song, int> longClickAction;
private readonly Action<int> headerAction;
public BrowseAdapter(Action<Song, int> clickAction, Action<Song, int> longClickAction, Action<int> headerAction = null)
{

View File

@@ -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<Song>, IItemTouchAdapter
{
public List<Song> songList;
public event EventHandler<int> ItemClick;
public event EventHandler<int> ItemLongClick;
public int listPadding;
public bool IsEmpty = false;
public SearchableList<Song> tracks;
public bool IsSliding { get; set; }
public PlaylistTrackAdapter(List<Song> songList)
public PlaylistTrackAdapter() { }
public PlaylistTrackAdapter(SearchableList<Song> 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<TextView>(Resource.Id.headerNumber).Text = songList.Count + " " + (songList.Count < 2 ? MainActivity.instance.GetString(Resource.String.element) : MainActivity.instance.GetString(Resource.String.elements));
header.FindViewById<TextView>(Resource.Id.headerNumber).Text = tracks.Count + " " + (tracks.Count < 2 ? MainActivity.instance.GetString(Resource.String.element) : MainActivity.instance.GetString(Resource.String.elements));
if (!header.FindViewById<ImageButton>(Resource.Id.headerPlay).HasOnClickListeners)
{
header.FindViewById<ImageButton>(Resource.Id.headerPlay).Click += (sender, e0) => { PlaylistManager.PlayInOrder(PlaylistTracks.instance.item); };
@@ -82,32 +90,34 @@ namespace Opus.Adapter
header.FindViewById<ImageButton>(Resource.Id.headerShuffle).ImageTintList = ColorStateList.ValueOf(Color.Black);
header.FindViewById<ImageButton>(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);
}
}
}

View File

@@ -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<RecyclerView>(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;

View File

@@ -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<PlaylistItem> LocalPlaylists = new List<PlaylistItem>();
@@ -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<RecyclerView>(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);

View File

@@ -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<Song> 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<Song> tracks = new List<Song>();
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<RecyclerView>(Resource.Id.recycler);
ListView.SetLayoutManager(new Android.Support.V7.Widget.LinearLayoutManager(MainActivity.instance));
ListView.SetAdapter(new PlaylistTrackAdapter(new List<Song>()));
ListView.SetAdapter(new PlaylistTrackAdapter(new SearchableList<Song>()));
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<TextView>(Resource.Id.headerTitle).Text = item.Name;
if (!Activity.FindViewById<ImageButton>(Resource.Id.headerPlay).HasOnClickListeners)
Activity.FindViewById<ImageButton>(Resource.Id.headerPlay).Click += (sender, e0) => { PlaylistManager.PlayInOrder(item); };
Activity.FindViewById<ImageButton>(Resource.Id.headerPlay).Click += HeaderPlay;
if (!Activity.FindViewById<ImageButton>(Resource.Id.headerShuffle).HasOnClickListeners)
Activity.FindViewById<ImageButton>(Resource.Id.headerShuffle).Click += (sender, e0) => { PlaylistManager.Shuffle(item); };
Activity.FindViewById<ImageButton>(Resource.Id.headerShuffle).Click += HeaderShuffle;
if (!Activity.FindViewById<ImageButton>(Resource.Id.headerMore).HasOnClickListeners)
Activity.FindViewById<ImageButton>(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<ImageButton>(Resource.Id.headerPlay).Click -= HeaderPlay;
Activity.FindViewById<ImageButton>(Resource.Id.headerShuffle).Click -= HeaderShuffle;
Activity.FindViewById<ImageButton>(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<Song>();
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<TextView>(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<ImageView>(Resource.Id.headerArt));
LoaderManager.GetInstance(this).InitLoader(0, null, this);
}
else if (item.YoutubeID != null)
{
fullyLoadded = false;
SearchableList<Song> tracks = new SearchableList<Song>();
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<Song>();
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<TextView>(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<ImageView>(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<Song>();
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<TextView>(Resource.Id.bsTitle).Text = item.Title;
bottomView.FindViewById<TextView>(Resource.Id.bsArtist).Text = item.Artist;
if (item.Album == null)
bottomView.FindViewById<TextView>(Resource.Id.bsTitle).Text = song.Title;
bottomView.FindViewById<TextView>(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<ImageView>(Resource.Id.bsArt));
}
else
{
Picasso.With(MainActivity.instance).Load(item.Album).Placeholder(Resource.Drawable.noAlbum).Transform(new RemoveBlackBorder(true)).Into(bottomView.FindViewById<ImageView>(Resource.Id.bsArt));
Picasso.With(MainActivity.instance).Load(song.Album).Placeholder(Resource.Drawable.noAlbum).Transform(new RemoveBlackBorder(true)).Into(bottomView.FindViewById<ImageView>(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();
})
});

View File

@@ -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<RecyclerView>(Resource.Id.recycler);
ListView.SetLayoutManager(new LinearLayoutManager(Application.Context));

View File

@@ -322,6 +322,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Code\DataStructure\SearchableList.cs" />
<Compile Include="Code\UI\Adapter\BaseCursor.cs" />
<Compile Include="Code\Api\LocalManager.cs" />
<Compile Include="Code\Api\PlaylistManager.cs" />
@@ -515,7 +516,7 @@
<AndroidResource Include="Resources\drawable\needProcessing.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\RecyclerFragment.xml">
<AndroidResource Include="Resources\layout\LonelyRecycler.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>

View File

@@ -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;