Skip to content

Commit

Permalink
Added profile tabs order setting
Browse files Browse the repository at this point in the history
  • Loading branch information
nrubin29 committed Mar 1, 2022
1 parent a94a097 commit d5ddaf8
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 86 deletions.
27 changes: 27 additions & 0 deletions lib/util/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:finale/services/auth.dart';
import 'package:finale/services/lastfm/period.dart';
import 'package:finale/util/profile_tab.dart';
import 'package:finale/util/theme.dart';
import 'package:shared_preferences/shared_preferences.dart';

Expand Down Expand Up @@ -266,6 +267,32 @@ class Preferences {
Stream<bool> get showAlbumArtistFieldChanged =>
_showAlbumArtistFieldChanged.stream;

List<ProfileTab> get profileTabsOrder {
final order = _preferences.getStringList('profileTabsOrder');

if (order == null) {
return ProfileTab.values;
}

return order
.map((item) => ProfileTab.values[int.parse(item)])
.toList(growable: false);
}

set profileTabsOrder(List<ProfileTab> profileTabOrder) {
_preferences.setStringList('profileTabsOrder',
profileTabOrder.map((e) => e.index.toString()).toList(growable: false));
_profileTabsOrderChanged.add(profileTabOrder);
}

// This stream needs to be open for the entire lifetime of the app.
// ignore: close_sinks
final _profileTabsOrderChanged =
StreamController<List<ProfileTab>>.broadcast();

Stream<List<ProfileTab>> get profileTabsOrderChanged =>
_profileTabsOrderChanged.stream;

void clear() {
_preferences.clear();
}
Expand Down
36 changes: 36 additions & 0 deletions lib/util/profile_tab.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';

enum ProfileTab {
recentScrobbles,
topArtists,
topAlbums,
topTracks,
friends,
charts,
}

extension ProfileTabDisplayName on ProfileTab {
String get displayName {
switch (this) {
case ProfileTab.recentScrobbles: return 'Recent Scrobbles';
case ProfileTab.topArtists: return 'Top Artists';
case ProfileTab.topAlbums: return 'Top Albums';
case ProfileTab.topTracks: return 'Top Tracks';
case ProfileTab.friends: return 'Friends';
case ProfileTab.charts: return 'Charts';
}
}
}

extension ProfileTabIcon on ProfileTab {
IconData get icon {
switch (this) {
case ProfileTab.recentScrobbles: return Icons.queue_music;
case ProfileTab.topArtists: return Icons.people;
case ProfileTab.topAlbums: return Icons.album;
case ProfileTab.topTracks: return Icons.audiotrack;
case ProfileTab.friends: return Icons.person;
case ProfileTab.charts: return Icons.access_time;
}
}
}
13 changes: 7 additions & 6 deletions lib/util/quick_actions_manager.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:finale/services/generic.dart';
import 'package:finale/util/profile_tab.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:rxdart/rxdart.dart';
import 'package:uni_links/uni_links.dart';
Expand Down Expand Up @@ -28,7 +29,7 @@ class QuickAction {
: type = QuickActionType.viewTrack,
value = track;

QuickAction.viewTab(EntityType tab)
QuickAction.viewTab(ProfileTab tab)
: type = QuickActionType.viewTab,
value = tab;
}
Expand Down Expand Up @@ -116,20 +117,20 @@ class QuickActionsManager {
.add(QuickAction.viewTrack(BasicConcreteTrack(name, artist, null)));
} else if (uri.host == 'profileTab') {
final tabString = uri.queryParameters['tab'];
EntityType tab;
ProfileTab tab;

switch (tabString) {
case 'scrobble':
tab = EntityType.playlist;
tab = ProfileTab.recentScrobbles;
break;
case 'artist':
tab = EntityType.artist;
tab = ProfileTab.topArtists;
break;
case 'album':
tab = EntityType.album;
tab = ProfileTab.topAlbums;
break;
case 'track':
tab = EntityType.track;
tab = ProfileTab.topTracks;
break;
default:
throw ArgumentError.value(tabString, 'tab', 'Unknown tab');
Expand Down
158 changes: 79 additions & 79 deletions lib/widgets/profile/profile_view.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import 'dart:async';

import 'package:finale/services/generic.dart';
import 'package:finale/services/lastfm/album.dart';
import 'package:finale/services/lastfm/artist.dart';
import 'package:finale/services/lastfm/lastfm.dart';
import 'package:finale/services/lastfm/track.dart';
import 'package:finale/services/lastfm/user.dart';
import 'package:finale/util/constants.dart';
import 'package:finale/util/preferences.dart';
import 'package:finale/util/profile_tab.dart';
import 'package:finale/util/quick_actions_manager.dart';
import 'package:finale/widgets/base/app_bar.dart';
import 'package:finale/widgets/base/future_builder_view.dart';
Expand Down Expand Up @@ -39,52 +40,93 @@ class ProfileView extends StatefulWidget {
class _ProfileViewState extends State<ProfileView>
with SingleTickerProviderStateMixin, WidgetsBindingObserver {
late TabController _tabController;
late StreamSubscription _subscription;
final _recentScrobblesKey = GlobalKey<EntityDisplayState>();
late List<ProfileTab> _tabOrder;
var _tab = 0;

final _recentScrobblesKey = GlobalKey<EntityDisplayState>();
late final StreamSubscription _profileTabsOrderSubscription;
StreamSubscription? _quickActionsSubscription;

@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);

_tabController = TabController(length: 6, vsync: this);
_tabOrder = Preferences().profileTabsOrder;
_profileTabsOrderSubscription =
Preferences().profileTabsOrderChanged.listen((tabOrder) {
setState(() {
_tabOrder = tabOrder;
});
});

_tabController = TabController(length: _tabOrder.length, vsync: this);
_tabController.addListener(() {
setState(() {
_tab = _tabController.index;
});
});

_subscription =
QuickActionsManager().quickActionStream.listen((action) async {
await Future.delayed(const Duration(milliseconds: 250));
if (action.type == QuickActionType.viewTab) {
final tab = action.value as EntityType;
int index;
if (!widget.isTab) {
_quickActionsSubscription =
QuickActionsManager().quickActionStream.listen((action) async {
await Future.delayed(const Duration(milliseconds: 250));
if (action.type == QuickActionType.viewTab) {
final tab = action.value as ProfileTab;
final index = _tabOrder.indexOf(tab);

switch (tab) {
case EntityType.playlist:
index = 0;
break;
case EntityType.artist:
index = 1;
break;
case EntityType.album:
index = 2;
break;
case EntityType.track:
index = 3;
break;
default:
assert(false);
return;
setState(() {
_tabController.index = index;
});
}
});
}
}

setState(() {
_tabController.index = index;
});
}
});
Widget _widgetForTab(ProfileTab tab, LUser user) {
switch (tab) {
case ProfileTab.recentScrobbles:
return EntityDisplay<LRecentTracksResponseTrack>(
key: _recentScrobblesKey,
request: GetRecentTracksRequest(widget.username,
includeCurrentScrobble: true, extended: true),
badgeWidgetBuilder: (track) =>
track.isLoved ? const OutlinedLoveIcon() : const SizedBox(),
trailingWidgetBuilder: (track) => track.timestamp != null
? const SizedBox()
: const NowPlayingAnimation(),
detailWidgetBuilder: (track) => TrackView(track: track),
);
case ProfileTab.topArtists:
return PeriodSelector<LTopArtistsResponseArtist>(
displayType: DisplayType.grid,
request: GetTopArtistsRequest(widget.username),
detailWidgetBuilder: (artist) => ArtistView(artist: artist),
subtitleWidgetBuilder: (item, items) => PlayCountBar(item, items),
);
case ProfileTab.topAlbums:
return PeriodSelector<LTopAlbumsResponseAlbum>(
displayType: DisplayType.grid,
request: GetTopAlbumsRequest(widget.username),
detailWidgetBuilder: (album) => AlbumView(album: album),
subtitleWidgetBuilder: (item, items) => PlayCountBar(item, items),
);
case ProfileTab.topTracks:
return PeriodSelector<LTopAlbumsResponseAlbum>(
displayType: DisplayType.grid,
request: GetTopAlbumsRequest(widget.username),
detailWidgetBuilder: (album) => AlbumView(album: album),
subtitleWidgetBuilder: (item, items) => PlayCountBar(item, items),
);
case ProfileTab.friends:
return EntityDisplay<LUser>(
displayCircularImages: true,
request: GetFriendsRequest(widget.username),
detailWidgetBuilder: (user) => ProfileView(username: user.name),
);
case ProfileTab.charts:
return WeeklyChartSelectorView(user: user);
}
}

@override
Expand Down Expand Up @@ -137,7 +179,7 @@ class _ProfileViewState extends State<ProfileView>
),
),
SliverVisibility(
visible: _tab != 5,
visible: _tab != _tabOrder.indexOf(ProfileTab.charts),
maintainState: true,
sliver: SliverToBoxAdapter(
child: Column(children: [
Expand All @@ -158,59 +200,16 @@ class _ProfileViewState extends State<ProfileView>
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Colors.grey,
indicatorColor: Theme.of(context).primaryColor,
tabs: const [
Tab(icon: Icon(Icons.queue_music)),
Tab(icon: Icon(Icons.people)),
Tab(icon: Icon(Icons.album)),
Tab(icon: Icon(Icons.audiotrack)),
Tab(icon: Icon(Icons.person)),
Tab(icon: Icon(Icons.access_time)),
tabs: [
for (final tab in _tabOrder) Tab(icon: Icon(tab.icon)),
],
),
),
],
body: TabBarView(
controller: _tabController,
children: [
EntityDisplay<LRecentTracksResponseTrack>(
key: _recentScrobblesKey,
request: GetRecentTracksRequest(widget.username,
includeCurrentScrobble: true, extended: true),
badgeWidgetBuilder: (track) => track.isLoved
? const OutlinedLoveIcon()
: const SizedBox(),
trailingWidgetBuilder: (track) => track.timestamp != null
? const SizedBox()
: const NowPlayingAnimation(),
detailWidgetBuilder: (track) => TrackView(track: track),
),
PeriodSelector<LTopArtistsResponseArtist>(
displayType: DisplayType.grid,
request: GetTopArtistsRequest(widget.username),
detailWidgetBuilder: (artist) => ArtistView(artist: artist),
subtitleWidgetBuilder: (item, items) =>
PlayCountBar(item, items),
),
PeriodSelector<LTopAlbumsResponseAlbum>(
displayType: DisplayType.grid,
request: GetTopAlbumsRequest(widget.username),
detailWidgetBuilder: (album) => AlbumView(album: album),
subtitleWidgetBuilder: (item, items) =>
PlayCountBar(item, items),
),
PeriodSelector<LTopTracksResponseTrack>(
request: GetTopTracksRequest(widget.username),
detailWidgetBuilder: (track) => TrackView(track: track),
subtitleWidgetBuilder: (item, items) =>
PlayCountBar(item, items),
),
EntityDisplay<LUser>(
displayCircularImages: true,
request: GetFriendsRequest(widget.username),
detailWidgetBuilder: (user) =>
ProfileView(username: user.name),
),
WeeklyChartSelectorView(user: user),
for (final tab in _tabOrder) _widgetForTab(tab, user),
],
),
),
Expand All @@ -227,7 +226,8 @@ class _ProfileViewState extends State<ProfileView>
@override
void dispose() {
_tabController.dispose();
_subscription.cancel();
_profileTabsOrderSubscription.cancel();
_quickActionsSubscription?.cancel();
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
Expand Down
17 changes: 16 additions & 1 deletion lib/widgets/settings/general_settings_view.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:finale/util/preferences.dart';
import 'package:finale/widgets/base/app_bar.dart';
import 'package:finale/widgets/base/captioned_list_tile.dart';
import 'package:finale/widgets/settings/profile_tabs_settings_view.dart';
import 'package:finale/widgets/settings/settings_list_tile.dart';
import 'package:flutter/material.dart';

Expand All @@ -25,7 +27,7 @@ class _GeneralSettingsViewState extends State<GeneralSettingsView> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsListTile(
title: 'Show Album Artist field',
title: 'Show album artist field',
description:
'Enable to show the album artist field in the scrobbler.',
icon: Icons.people,
Expand All @@ -37,6 +39,19 @@ class _GeneralSettingsViewState extends State<GeneralSettingsView> {
});
},
),
CaptionedListTile(
title: 'Profile tabs order',
caption: 'Reorder the tabs on the profile page.',
icon: Icons.list,
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const ProfileTabsSettingsView()),
);
},
),
],
),
);
Expand Down
Loading

0 comments on commit d5ddaf8

Please sign in to comment.