Skip to content

Commit 83dc3e8

Browse files
committed
Async SongAdapter, improved fragment loading & cleanup
Refactored SongAdapter to support async initialization via CreateAsync, clarified adapter modes with enums, and improved resource management with proper disposal patterns. Fragments now use loading indicators, subscribe to async adapter creation, and clean up subscriptions and adapters. UI responsiveness is improved with smooth scrolling and expanded song details. Updated IArtistActions for null safety and ensured artist-album DB relationships. Misc UI and error handling tweaks included.
1 parent 4b478e0 commit 83dc3e8

File tree

9 files changed

+548
-199
lines changed

9 files changed

+548
-199
lines changed

Dimmer/Dimmer.Droid/ViewsAndPages/NativeViews/AlbumSection/AlbumFragment.cs

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Reactive.Disposables;
5-
using System.Reactive.Disposables.Fluent;
6-
using System.Text;
7-
using System.Threading.Tasks;
8-
using AndroidX.CoordinatorLayout.Widget;
1+
using AndroidX.CoordinatorLayout.Widget;
92
using AndroidX.Core.Widget;
103
using AndroidX.RecyclerView.Widget;
114
using Bumptech.Glide;
125
using Google.Android.Material.Behavior;
136
using Google.Android.Material.Chip;
147
using Google.Android.Material.Floatingtoolbar;
158
using Google.Android.Material.Loadingindicator;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
using System.Reactive.Disposables;
13+
using System.Reactive.Disposables.Fluent;
14+
using System.Text;
15+
using System.Threading.Tasks;
1616
using static Android.Provider.DocumentsContract;
17+
using static Dimmer.ViewsAndPages.NativeViews.SongAdapter;
1718

1819
namespace Dimmer.ViewsAndPages.NativeViews.AlbumSection;
1920

2021
public partial class AlbumFragment : Fragment, IOnBackInvokedCallback
2122
{
2223
private TextView songsLabel;
23-
private RecyclerView _recyclerView;
24-
private SongAdapter MyRecycleViewAdapter;
24+
private RecyclerView _songListRecycler;
25+
private SongAdapter? MyRecycleViewAdapter;
2526
private TextView artistsLabel;
2627
private TextView nameTxt;
2728
private NestedScrollView myScrollView;
2829
private LinearLayout root;
2930
private string _albumName;
3031
private string _albumId;
3132
private ChipGroup _artistChipGroup;
32-
private LoadingIndicator progressIndic;
33+
private LoadingIndicator loadingIndic;
3334

3435
public AlbumFragment()
3536
{
@@ -144,10 +145,10 @@ public override View OnCreateView(LayoutInflater inflater, ViewGroup? container,
144145

145146

146147

147-
progressIndic = new LoadingIndicator(ctx);
148-
progressIndic.IndicatorSize = AppUtil.DpToPx(40);
149-
progressIndic.SetForegroundGravity(GravityFlags.CenterHorizontal);
150-
root.AddView(progressIndic);
148+
loadingIndic = new LoadingIndicator(ctx);
149+
loadingIndic.IndicatorSize = AppUtil.DpToPx(40);
150+
loadingIndic.SetForegroundGravity(GravityFlags.CenterHorizontal);
151+
root.AddView(loadingIndic);
151152

152153

153154

@@ -156,15 +157,11 @@ public override View OnCreateView(LayoutInflater inflater, ViewGroup? container,
156157
songsLabel = new MaterialTextView(ctx) { Text = "Songs " + SelectedAlbum.SongsInAlbum.Count, TextSize = 20 }; // Update count later
157158
recyclerContainer.AddView(songsLabel);
158159

159-
_recyclerView = new RecyclerView(ctx);
160-
_recyclerView.NestedScrollingEnabled = false; // LET THE SCROLLVIEW HANDLE SCROLLING
161-
_recyclerView.SetLayoutManager(new LinearLayoutManager(ctx));
160+
_songListRecycler = new RecyclerView(ctx);
161+
_songListRecycler.NestedScrollingEnabled = false; // LET THE SCROLLVIEW HANDLE SCROLLING
162+
_songListRecycler.SetLayoutManager(new LinearLayoutManager(ctx));
162163

163-
// Set Adapter immediately with empty data or loading state to prevent UI pop-in
164-
MyRecycleViewAdapter = new SongAdapter(ctx, MyViewModel, this, "artist");
165-
_recyclerView.SetAdapter(MyRecycleViewAdapter);
166-
167-
recyclerContainer.AddView(_recyclerView);
164+
recyclerContainer.AddView(_songListRecycler);
168165
root.AddView(recyclerContainer);
169166

170167
// Add root to ScrollView
@@ -243,21 +240,65 @@ private Chip CreateToolbarButton(Context ctx, int iconRes, string desc)
243240
return btn;
244241
}
245242

243+
protected override void Dispose(bool disposing)
244+
{
245+
if (disposing)
246+
{
247+
_disposables.Dispose();
248+
}
249+
base.Dispose(disposing);
250+
}
246251
public override void OnResume()
247252
{
253+
MyViewModel.CurrentFragment = this;
248254
base.OnResume();
249255
if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu)
250256
{
251257
Activity?.OnBackInvokedDispatcher.RegisterOnBackInvokedCallback(
252258
(int)IOnBackInvokedDispatcher.PriorityDefault, this);
253259
}
260+
loadingIndic.Visibility = ViewStates.Visible;
261+
262+
SongAdapter.CreateAsync(this.View?.Context, MyViewModel, this, SongsToWatchSource.AlbumPage)?
263+
.Subscribe(adapter =>
264+
{
265+
MyRecycleViewAdapter = adapter;
266+
_songListRecycler?.SetAdapter(MyRecycleViewAdapter);
267+
268+
int currentlyPlayingIndex = 0;
269+
if (MyViewModel.SelectedSong is not null)
270+
{
271+
currentlyPlayingIndex = MyViewModel.SearchResults.IndexOf(MyViewModel.SelectedSong);
272+
273+
}
274+
else
275+
{
276+
277+
currentlyPlayingIndex = MyViewModel.SearchResults.IndexOf(MyViewModel.CurrentPlayingSongView);
278+
}
279+
if (currentlyPlayingIndex >= 0)
280+
_songListRecycler?.SmoothScrollToPosition(currentlyPlayingIndex);
281+
282+
loadingIndic.Visibility = ViewStates.Gone;
254283

255-
MyRecycleViewAdapter.IsSourceCleared.
284+
},
285+
error =>
286+
{
287+
Debug.WriteLine(error.Message);
288+
loadingIndic.Visibility = ViewStates.Gone;
289+
290+
});
291+
292+
293+
MyRecycleViewAdapter?.IsSourceCleared.
256294
ObserveOn(RxSchedulers.UI)
257295
.Subscribe(observer =>
258296
{
259-
progressIndic.Visibility = ViewStates.Gone;
297+
loadingIndic.Visibility = ViewStates.Gone;
260298
}).DisposeWith(_disposables);
299+
300+
301+
261302
}
262303
private readonly CompositeDisposable _disposables = new();
263304
public void OnBackInvoked()

Dimmer/Dimmer.Droid/ViewsAndPages/NativeViews/ArtistSection/ArtistFragment.cs

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Reactive.Disposables;
2-
using System.Reactive.Disposables.Fluent;
3-
using Android.Nfc;
1+
using Android.Nfc;
42
using Android.Text;
53
using AndroidX.CoordinatorLayout.Widget;
64
using AndroidX.Core.Widget;
@@ -14,6 +12,9 @@
1412
using Google.Android.Material.Floatingtoolbar;
1513
using Google.Android.Material.Loadingindicator;
1614
using Google.Android.Material.ProgressIndicator;
15+
using System.Reactive.Disposables;
16+
using System.Reactive.Disposables.Fluent;
17+
using static Dimmer.ViewsAndPages.NativeViews.SongAdapter;
1718
using ScrollView = Android.Widget.ScrollView;
1819

1920
namespace Dimmer.ViewsAndPages.NativeViews.ArtistSection;
@@ -24,12 +25,11 @@ public partial class ArtistFragment : Fragment, IOnBackInvokedCallback
2425
private BaseViewModelAnd MyViewModel;
2526
private string _artistName;
2627
private string _artistId;
27-
private RecyclerView albumsRecycler;
2828
private NestedScrollView myScrollView;
2929
private FrameLayout container;
3030
private LinearLayout root;
3131
private TextView songsLabel;
32-
private LoadingIndicator progressIndic;
32+
private LoadingIndicator loadingIndic;
3333

3434
public ArtistFragment()
3535
{
@@ -47,7 +47,7 @@ public ArtistFragment(BaseViewModelAnd vm, string artistName, string artistId)
4747
private ChipGroup _albumChipGroup;
4848
private TextView nameTxt;
4949
private TextView albumLabel;
50-
private RecyclerView _recyclerView;
50+
private RecyclerView _songListRecycler;
5151
private LinearLayout totalPlayStats;
5252
private LinearLayout totalSkipsStats;
5353
private LinearLayout libTracks;
@@ -60,7 +60,7 @@ public void OnBackInvoked()
6060
//myAct.MoveTaskToBack
6161
}
6262
public ArtistModelView SelectedArtist { get; private set; }
63-
internal SongAdapter MyRecycleViewAdapter { get; private set; }
63+
internal SongAdapter? MyRecycleViewAdapter { get; private set; }
6464
public override View OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState)
6565
{
6666
var ctx = Context;
@@ -153,19 +153,19 @@ public override View OnCreateView(LayoutInflater inflater, ViewGroup? container,
153153
statsLayout.AddView(totalSkipsStats);
154154

155155
libTracks = CreateStatRow(ctx, "Library Tracks", SelectedArtist.SongsByArtist.Count.ToString());
156-
}
157156
statsLayout.AddView(libTracks);
157+
}
158158

159159
statsCard.AddView(statsLayout);
160160
root.AddView(statsCard);
161161

162162

163163

164164

165-
progressIndic = new LoadingIndicator(ctx);
166-
progressIndic.IndicatorSize = AppUtil.DpToPx(40);
167-
progressIndic.SetForegroundGravity(GravityFlags.CenterHorizontal);
168-
root.AddView(progressIndic);
165+
loadingIndic = new LoadingIndicator(ctx);
166+
loadingIndic.IndicatorSize = AppUtil.DpToPx(40);
167+
loadingIndic.SetForegroundGravity(GravityFlags.CenterHorizontal);
168+
root.AddView(loadingIndic);
169169

170170

171171

@@ -174,15 +174,11 @@ public override View OnCreateView(LayoutInflater inflater, ViewGroup? container,
174174
songsLabel = new MaterialTextView(ctx) { Text = "Songs "+SelectedArtist.SongsByArtist.Count, TextSize = 20 }; // Update count later
175175
recyclerContainer.AddView(songsLabel);
176176

177-
_recyclerView = new RecyclerView(ctx);
178-
_recyclerView.NestedScrollingEnabled = false; // LET THE SCROLLVIEW HANDLE SCROLLING
179-
_recyclerView.SetLayoutManager(new LinearLayoutManager(ctx));
177+
_songListRecycler = new RecyclerView(ctx);
178+
_songListRecycler.NestedScrollingEnabled = false; // LET THE SCROLLVIEW HANDLE SCROLLING
179+
_songListRecycler.SetLayoutManager(new LinearLayoutManager(ctx));
180180

181-
// Set Adapter immediately with empty data or loading state to prevent UI pop-in
182-
MyRecycleViewAdapter = new SongAdapter(ctx, MyViewModel, this, "artist");
183-
_recyclerView.SetAdapter(MyRecycleViewAdapter);
184-
185-
recyclerContainer.AddView(_recyclerView);
181+
recyclerContainer.AddView(_songListRecycler);
186182
root.AddView(recyclerContainer);
187183

188184
// Add root to ScrollView
@@ -252,6 +248,14 @@ private Chip CreateToolbarButton(Context ctx, int iconRes, string desc)
252248
return btn;
253249
}
254250

251+
protected override void Dispose(bool disposing)
252+
{
253+
if (disposing)
254+
{
255+
_disposables.Dispose();
256+
}
257+
base.Dispose(disposing);
258+
}
255259
private LinearLayout CreateStatRow(Context ctx, string label, string value)
256260
{
257261
var row = new LinearLayout(ctx) { Orientation = Orientation.Horizontal };
@@ -273,18 +277,18 @@ public async override void OnResume()
273277
_albumChipGroup.SetChipSpacing(5);
274278

275279
var albuInArtist = SelectedArtist.AlbumsByArtist;
276-
albuInArtist ??= SelectedArtist.AlbumsInDB(MyViewModel.RealmFactory).ToObservableCollection();
280+
albuInArtist ??= SelectedArtist.AlbumsInDB(MyViewModel.RealmFactory)?.ToObservableCollection();
277281
if (albuInArtist is not null)
278282
{
279-
foreach (var album in albuInArtist)
283+
foreach (AlbumModelView album in albuInArtist)
280284
{
281285
var chip = new Chip(Context) {
282286
Typeface = Typeface.DefaultBold,
283287

284288

285289
HorizontalFadingEdgeEnabled = true,
286290

287-
Text = album.Name };
291+
Text = album!.Name };
288292

289293
chip.TooltipText = album.Name;
290294
chip.Click += (s, e) =>
@@ -311,16 +315,49 @@ public async override void OnResume()
311315
var ctx = Context;
312316

313317

314-
MyRecycleViewAdapter.IsSourceCleared.
318+
loadingIndic.Visibility = ViewStates.Visible;
319+
320+
SongAdapter.CreateAsync(this.View?.Context, MyViewModel, this, SongsToWatchSource.ArtistPage)?
321+
.Subscribe(adapter =>
322+
{
323+
MyRecycleViewAdapter = adapter;
324+
_songListRecycler?.SetAdapter(MyRecycleViewAdapter);
325+
326+
int currentlyPlayingIndex = 0;
327+
if (MyViewModel.SelectedSong is not null)
328+
{
329+
currentlyPlayingIndex = MyViewModel.SearchResults.IndexOf(MyViewModel.SelectedSong);
330+
331+
}
332+
else
333+
{
334+
335+
currentlyPlayingIndex = MyViewModel.SearchResults.IndexOf(MyViewModel.CurrentPlayingSongView);
336+
}
337+
if (currentlyPlayingIndex >= 0)
338+
_songListRecycler?.SmoothScrollToPosition(currentlyPlayingIndex);
339+
340+
loadingIndic.Visibility = ViewStates.Gone;
341+
342+
},
343+
error =>
344+
{
345+
Debug.WriteLine(error.Message);
346+
loadingIndic.Visibility = ViewStates.Gone;
347+
348+
});
349+
350+
MyRecycleViewAdapter?.IsSourceCleared.
315351
ObserveOn(RxSchedulers.UI)
316352
.Subscribe(observer =>
317353
{
318-
progressIndic.Visibility = ViewStates.Gone;
354+
loadingIndic.Visibility = ViewStates.Gone;
319355
}).DisposeWith(_disposables);
320-
356+
357+
358+
321359

322360
}
323-
324361
// --- Simple Adapters for this View ---
325362
class ArtistsFromAlbumAdapter : RecyclerView.Adapter
326363
{

0 commit comments

Comments
 (0)