mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: show placeholder images where there is no item or on empty page
This commit is contained in:
parent
a8694a7a8b
commit
b8ffb9b75f
@ -20,6 +20,17 @@ class $AssetsBackgroundsGen {
|
|||||||
List<AssetGenImage> get values => [xmasEffect];
|
List<AssetGenImage> get values => [xmasEffect];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class $AssetsIllustrationsGen {
|
||||||
|
const $AssetsIllustrationsGen();
|
||||||
|
|
||||||
|
/// File path: assets/illustrations/fixing_bugs.png
|
||||||
|
AssetGenImage get fixingBugs =>
|
||||||
|
const AssetGenImage('assets/illustrations/fixing_bugs.png');
|
||||||
|
|
||||||
|
/// List of all assets
|
||||||
|
List<AssetGenImage> get values => [fixingBugs];
|
||||||
|
}
|
||||||
|
|
||||||
class $AssetsLogosGen {
|
class $AssetsLogosGen {
|
||||||
const $AssetsLogosGen();
|
const $AssetsLogosGen();
|
||||||
|
|
||||||
@ -140,6 +151,8 @@ class Assets {
|
|||||||
AssetGenImage('assets/bengali-patterns-bg.jpg');
|
AssetGenImage('assets/bengali-patterns-bg.jpg');
|
||||||
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
|
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
|
||||||
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
|
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
|
||||||
|
static const $AssetsIllustrationsGen illustrations =
|
||||||
|
$AssetsIllustrationsGen();
|
||||||
static const AssetGenImage invidious = AssetGenImage('assets/invidious.jpg');
|
static const AssetGenImage invidious = AssetGenImage('assets/invidious.jpg');
|
||||||
static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png');
|
static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png');
|
||||||
static const AssetGenImage likedTracks =
|
static const AssetGenImage likedTracks =
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/pages/settings/settings.dart';
|
import 'package:spotube/pages/settings/settings.dart';
|
||||||
|
|
||||||
import 'package:spotube/provider/authentication/authentication.dart';
|
import 'package:spotube/provider/authentication/authentication.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
class AnonymousFallback extends ConsumerWidget {
|
class AnonymousFallback extends ConsumerWidget {
|
||||||
@ -25,9 +28,16 @@ class AnonymousFallback extends ConsumerWidget {
|
|||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
|
Undraw(
|
||||||
|
illustration: kIsMobile
|
||||||
|
? UndrawIllustration.accessDenied
|
||||||
|
: UndrawIllustration.secureLogin,
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
color: context.theme.colorScheme.primary,
|
||||||
|
),
|
||||||
Text(context.l10n.not_logged_in),
|
Text(context.l10n.not_logged_in),
|
||||||
const SizedBox(height: 10),
|
|
||||||
Button.primary(
|
Button.primary(
|
||||||
child: Text(context.l10n.login_with_spotify),
|
child: Text(context.l10n.login_with_spotify),
|
||||||
onPressed: () => ServiceUtils.pushNamed(context, SettingsPage.name),
|
onPressed: () => ServiceUtils.pushNamed(context, SettingsPage.name),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
@ -7,6 +8,7 @@ import 'package:spotube/components/playbutton_view/playbutton_card.dart';
|
|||||||
import 'package:spotube/components/playbutton_view/playbutton_tile.dart';
|
import 'package:spotube/components/playbutton_view/playbutton_tile.dart';
|
||||||
import 'package:spotube/components/waypoint.dart';
|
import 'package:spotube/components/waypoint.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
const _dummyPlaybuttonCard = PlaybuttonCard(
|
const _dummyPlaybuttonCard = PlaybuttonCard(
|
||||||
@ -99,38 +101,59 @@ class PlaybuttonView extends StatelessWidget {
|
|||||||
const SliverGap(10),
|
const SliverGap(10),
|
||||||
// Toggle between grid and list view
|
// Toggle between grid and list view
|
||||||
switch ((isGrid.value, isLoading)) {
|
switch ((isGrid.value, isLoading)) {
|
||||||
(true, _) => SliverGrid.builder(
|
(true, _) => !isLoading && itemCount == 0
|
||||||
itemCount: isLoading ? 6 : itemCount + 1,
|
? SliverPadding(
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
maxCrossAxisExtent: 150 * scale,
|
sliver: SliverToBoxAdapter(
|
||||||
mainAxisExtent: 225 * scale,
|
child: Column(
|
||||||
crossAxisSpacing: 12 * scale,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSpacing: 12 * scale,
|
spacing: 10,
|
||||||
),
|
children: [
|
||||||
itemBuilder: (context, index) {
|
Undraw(
|
||||||
if (isLoading) {
|
height: 200 * context.theme.scaling,
|
||||||
return const Skeletonizer(
|
illustration: UndrawIllustration.taken,
|
||||||
enabled: true,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
child: _dummyPlaybuttonCard,
|
),
|
||||||
);
|
Text(
|
||||||
}
|
context.l10n.nothing_found,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
if (index == itemCount) {
|
).muted().small()
|
||||||
if (!hasMore) return const SizedBox.shrink();
|
],
|
||||||
return Waypoint(
|
|
||||||
controller: controller,
|
|
||||||
isGrid: true,
|
|
||||||
onTouchEdge: onRequestMore,
|
|
||||||
child: const Skeletonizer(
|
|
||||||
enabled: true,
|
|
||||||
child: _dummyPlaybuttonCard,
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
)
|
||||||
|
: SliverGrid.builder(
|
||||||
|
itemCount: isLoading ? 6 : itemCount + 1,
|
||||||
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 150 * scale,
|
||||||
|
mainAxisExtent: 225 * scale,
|
||||||
|
crossAxisSpacing: 12 * scale,
|
||||||
|
mainAxisSpacing: 12 * scale,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (isLoading) {
|
||||||
|
return const Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
child: _dummyPlaybuttonCard,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return gridItemBuilder(context, index);
|
if (index == itemCount) {
|
||||||
},
|
if (!hasMore) return const SizedBox.shrink();
|
||||||
),
|
return Waypoint(
|
||||||
|
controller: controller,
|
||||||
|
isGrid: true,
|
||||||
|
onTouchEdge: onRequestMore,
|
||||||
|
child: const Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
child: _dummyPlaybuttonCard,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gridItemBuilder(context, index);
|
||||||
|
},
|
||||||
|
),
|
||||||
(false, true) => Skeletonizer.sliver(
|
(false, true) => Skeletonizer.sliver(
|
||||||
enabled: true,
|
enabled: true,
|
||||||
child: SliverList(
|
child: SliverList(
|
||||||
@ -150,6 +173,23 @@ class PlaybuttonView extends StatelessWidget {
|
|||||||
onFetchData: onRequestMore,
|
onFetchData: onRequestMore,
|
||||||
hasReachedMax: !hasMore,
|
hasReachedMax: !hasMore,
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
|
emptyBuilder: (context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Undraw(
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
illustration: UndrawIllustration.taken,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
context.l10n.nothing_found,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).muted().small()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/components/track_presentation/presentation_props.dart';
|
import 'package:spotube/components/track_presentation/presentation_props.dart';
|
||||||
@ -8,6 +10,7 @@ import 'package:spotube/components/track_presentation/presentation_state.dart';
|
|||||||
import 'package:spotube/components/track_presentation/use_track_tile_play_callback.dart';
|
import 'package:spotube/components/track_presentation/use_track_tile_play_callback.dart';
|
||||||
import 'package:spotube/components/track_tile/track_tile.dart';
|
import 'package:spotube/components/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/components/track_presentation/use_is_user_playlist.dart';
|
import 'package:spotube/components/track_presentation/use_is_user_playlist.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/audio_player/audio_player.dart';
|
import 'package:spotube/provider/audio_player/audio_player.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
@ -25,6 +28,30 @@ class PresentationListSection extends HookConsumerWidget {
|
|||||||
|
|
||||||
final onTileTap = useTrackTilePlayCallback(ref);
|
final onTileTap = useTrackTilePlayCallback(ref);
|
||||||
|
|
||||||
|
if (state.presentationTracks.isEmpty && !options.pagination.isLoading) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Undraw(
|
||||||
|
illustration: UndrawIllustration.dreamer,
|
||||||
|
color: context.theme.colorScheme.primary,
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
isUserPlaylist
|
||||||
|
? context.l10n.no_tracks_added_yet
|
||||||
|
: context.l10n.no_tracks,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).muted().small(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return SliverInfiniteList(
|
return SliverInfiniteList(
|
||||||
isLoading: options.pagination.isLoading,
|
isLoading: options.pagination.isLoading,
|
||||||
onFetchData: options.pagination.onFetchMore,
|
onFetchData: options.pagination.onFetchMore,
|
||||||
|
@ -409,5 +409,10 @@
|
|||||||
"add_all_to_queue": "Add all to queue",
|
"add_all_to_queue": "Add all to queue",
|
||||||
"play_all_next": "Play all next",
|
"play_all_next": "Play all next",
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"view_all": "View all"
|
"view_all": "View all",
|
||||||
|
"no_tracks_added_yet": "Looks like you haven't added any tracks yet",
|
||||||
|
"no_tracks": "Looks like there are no tracks here",
|
||||||
|
"no_tracks_listened_yet": "Looks like you haven't listened to anything yet",
|
||||||
|
"not_following_artists": "You're not following any artists",
|
||||||
|
"no_favorite_albums_yet": "Looks like you haven't added any albums to your favorites yet"
|
||||||
}
|
}
|
@ -1,7 +1,5 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
class BlurCard extends HookConsumerWidget {
|
class BlurCard extends HookConsumerWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@ -18,8 +16,7 @@ class BlurCard extends HookConsumerWidget {
|
|||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: BackdropFilter(
|
child: SurfaceCard(
|
||||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
@ -15,6 +17,21 @@ class HomeFeaturedSection extends HookConsumerWidget {
|
|||||||
final featuredPlaylistsNotifier =
|
final featuredPlaylistsNotifier =
|
||||||
ref.watch(featuredPlaylistsProvider.notifier);
|
ref.watch(featuredPlaylistsProvider.notifier);
|
||||||
|
|
||||||
|
if (featuredPlaylists.hasError) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Undraw(
|
||||||
|
illustration: UndrawIllustration.fixingBugs,
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
color: context.theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
Text(context.l10n.something_went_wrong).small().muted(),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Skeletonizer(
|
return Skeletonizer(
|
||||||
enabled: featuredPlaylists.isLoading,
|
enabled: featuredPlaylists.isLoading,
|
||||||
child: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
child: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||||
|
@ -89,23 +89,24 @@ class GenreSectionCard extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
if (playlists?.hasError != true)
|
||||||
child: Skeleton.ignore(
|
Expanded(
|
||||||
child: Skeletonizer(
|
child: Skeleton.ignore(
|
||||||
enabled: playlists?.isLoading ?? false,
|
child: Skeletonizer(
|
||||||
child: ListView.separated(
|
enabled: playlists?.isLoading ?? false,
|
||||||
scrollDirection: Axis.horizontal,
|
child: ListView.separated(
|
||||||
itemCount: playlistsData.length,
|
scrollDirection: Axis.horizontal,
|
||||||
separatorBuilder: (context, index) => const Gap(12),
|
itemCount: playlistsData.length,
|
||||||
itemBuilder: (context, index) {
|
separatorBuilder: (context, index) => const Gap(12),
|
||||||
final playlist = playlistsData.elementAt(index);
|
itemBuilder: (context, index) {
|
||||||
|
final playlist = playlistsData.elementAt(index);
|
||||||
|
|
||||||
return GenreSectionCardPlaylistCard(playlist: playlist);
|
return GenreSectionCardPlaylistCard(playlist: playlist);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image;
|
import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/playbutton_view/playbutton_view.dart';
|
import 'package:spotube/components/playbutton_view/playbutton_view.dart';
|
||||||
@ -75,21 +77,45 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(10),
|
const SliverGap(10),
|
||||||
SliverPadding(
|
if (albums.isEmpty &&
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
!albumsQuery.isLoading &&
|
||||||
sliver: PlaybuttonView(
|
searchText.value.isEmpty)
|
||||||
controller: controller,
|
SliverPadding(
|
||||||
itemCount: albums.length,
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
hasMore: albumsQuery.asData?.value.hasMore == true,
|
sliver: SliverToBoxAdapter(
|
||||||
isLoading: albumsQuery.isLoading,
|
child: Column(
|
||||||
onRequestMore: albumsQueryNotifier.fetchMore,
|
mainAxisSize: MainAxisSize.min,
|
||||||
gridItemBuilder: (context, index) => AlbumCard(
|
spacing: 10,
|
||||||
albums[index],
|
children: [
|
||||||
|
Undraw(
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
illustration: UndrawIllustration.followMeDrone,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
context.l10n.not_following_artists,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).muted().small()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
sliver: PlaybuttonView(
|
||||||
|
controller: controller,
|
||||||
|
itemCount: albums.length,
|
||||||
|
hasMore: albumsQuery.asData?.value.hasMore == true,
|
||||||
|
isLoading: albumsQuery.isLoading,
|
||||||
|
onRequestMore: albumsQueryNotifier.fetchMore,
|
||||||
|
gridItemBuilder: (context, index) => AlbumCard(
|
||||||
|
albums[index],
|
||||||
|
),
|
||||||
|
listItemBuilder: (context, index) =>
|
||||||
|
AlbumCard.tile(albums[index]),
|
||||||
),
|
),
|
||||||
listItemBuilder: (context, index) =>
|
|
||||||
AlbumCard.tile(albums[index]),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SliverSafeArea(sliver: SliverGap(10)),
|
const SliverSafeArea(sliver: SliverGap(10)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
|
|
||||||
@ -79,45 +81,82 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(10),
|
const SliverGap(10),
|
||||||
SliverLayoutBuilder(builder: (context, constrains) {
|
if (filteredArtists.isNotEmpty)
|
||||||
return SliverGrid.builder(
|
SliverLayoutBuilder(builder: (context, constrains) {
|
||||||
itemCount: filteredArtists.isEmpty
|
return SliverGrid.builder(
|
||||||
? 6
|
itemCount: filteredArtists.length + 1,
|
||||||
: filteredArtists.length + 1,
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
maxCrossAxisExtent: 200,
|
||||||
maxCrossAxisExtent: 200,
|
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
||||||
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
crossAxisSpacing: 8,
|
||||||
crossAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
mainAxisSpacing: 8,
|
),
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
itemBuilder: (context, index) {
|
if (filteredArtists.isNotEmpty &&
|
||||||
if (filteredArtists.isNotEmpty &&
|
index == filteredArtists.length) {
|
||||||
index == filteredArtists.length) {
|
if (artistQuery.asData?.value.hasMore != true) {
|
||||||
if (artistQuery.asData?.value.hasMore != true) {
|
return const SizedBox.shrink();
|
||||||
return const SizedBox.shrink();
|
}
|
||||||
|
|
||||||
|
return Waypoint(
|
||||||
|
controller: controller,
|
||||||
|
isGrid: true,
|
||||||
|
onTouchEdge: artistQueryNotifier.fetchMore,
|
||||||
|
child: Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
child: ArtistCard(FakeData.artist),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Waypoint(
|
return Skeletonizer(
|
||||||
controller: controller,
|
enabled: artistQuery.isLoading,
|
||||||
isGrid: true,
|
child: ArtistCard(
|
||||||
onTouchEdge: artistQueryNotifier.fetchMore,
|
filteredArtists.elementAtOrNull(index) ??
|
||||||
child: Skeletonizer(
|
FakeData.artist,
|
||||||
enabled: true,
|
|
||||||
child: ArtistCard(FakeData.artist),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
);
|
||||||
return Skeletonizer(
|
})
|
||||||
enabled: artistQuery.isLoading,
|
else if (filteredArtists.isEmpty &&
|
||||||
child: ArtistCard(
|
searchText.value.isEmpty &&
|
||||||
filteredArtists.elementAtOrNull(index) ??
|
!artistQuery.isLoading)
|
||||||
FakeData.artist,
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Undraw(
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
illustration: UndrawIllustration.followMeDrone,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
);
|
Text(
|
||||||
},
|
context.l10n.not_following_artists,
|
||||||
);
|
textAlign: TextAlign.center,
|
||||||
}),
|
).muted().small()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Undraw(
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
illustration: UndrawIllustration.taken,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
context.l10n.nothing_found,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).muted().small()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
const SliverSafeArea(sliver: SliverGap(10)),
|
const SliverSafeArea(sliver: SliverGap(10)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/modules/stats/common/album_item.dart';
|
import 'package:spotube/modules/stats/common/album_item.dart';
|
||||||
@ -31,6 +33,24 @@ class TopAlbums extends HookConsumerWidget {
|
|||||||
isLoading: topAlbums.isLoading && !topAlbums.isLoadingNextPage,
|
isLoading: topAlbums.isLoading && !topAlbums.isLoadingNextPage,
|
||||||
hasReachedMax: topAlbums.asData?.value.hasMore ?? true,
|
hasReachedMax: topAlbums.asData?.value.hasMore ?? true,
|
||||||
itemCount: albumsData.length,
|
itemCount: albumsData.length,
|
||||||
|
emptyBuilder: (context) => Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Gap(50),
|
||||||
|
Undraw(
|
||||||
|
illustration: UndrawIllustration.happyMusic,
|
||||||
|
color: context.theme.colorScheme.primary,
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
context.l10n.no_tracks_listened_yet,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).muted().small(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final album = albumsData[index];
|
final album = albumsData[index];
|
||||||
return StatsAlbumItem(
|
return StatsAlbumItem(
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/modules/stats/common/artist_item.dart';
|
import 'package:spotube/modules/stats/common/artist_item.dart';
|
||||||
@ -35,6 +37,24 @@ class TopArtists extends HookConsumerWidget {
|
|||||||
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
|
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
|
||||||
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
|
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
|
||||||
itemCount: artistsData.length,
|
itemCount: artistsData.length,
|
||||||
|
emptyBuilder: (context) => Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Gap(50),
|
||||||
|
Undraw(
|
||||||
|
illustration: UndrawIllustration.happyMusic,
|
||||||
|
color: context.theme.colorScheme.primary,
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
context.l10n.no_tracks_listened_yet,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).muted().small(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final artist = artistsData[index];
|
final artist = artistsData[index];
|
||||||
return StatsArtistItem(
|
return StatsArtistItem(
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/modules/stats/common/track_item.dart';
|
import 'package:spotube/modules/stats/common/track_item.dart';
|
||||||
@ -33,6 +35,24 @@ class TopTracks extends HookConsumerWidget {
|
|||||||
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
|
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
|
||||||
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
|
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
|
||||||
itemCount: tracksData.length,
|
itemCount: tracksData.length,
|
||||||
|
emptyBuilder: (context) => Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Gap(50),
|
||||||
|
Undraw(
|
||||||
|
illustration: UndrawIllustration.happyMusic,
|
||||||
|
color: context.theme.colorScheme.primary,
|
||||||
|
height: 200 * context.theme.scaling,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
context.l10n.no_tracks_listened_yet,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).muted().small(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final track = tracksData[index];
|
final track = tracksData[index];
|
||||||
return StatsTrackItem(
|
return StatsTrackItem(
|
||||||
|
@ -35,7 +35,7 @@ class AlbumPage extends HookConsumerWidget {
|
|||||||
tracks: tracks.asData?.value.items ?? [],
|
tracks: tracks.asData?.value.items ?? [],
|
||||||
pagination: PaginationProps(
|
pagination: PaginationProps(
|
||||||
hasNextPage: tracks.asData?.value.hasMore ?? false,
|
hasNextPage: tracks.asData?.value.hasMore ?? false,
|
||||||
isLoading: tracks.isLoadingNextPage,
|
isLoading: tracks.isLoading || tracks.isLoadingNextPage,
|
||||||
onFetchMore: () async {
|
onFetchMore: () async {
|
||||||
await tracksNotifier.fetchMore();
|
await tracksNotifier.fetchMore();
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
@ -8,8 +8,6 @@ import 'package:spotube/pages/getting_started/sections/greeting.dart';
|
|||||||
import 'package:spotube/pages/getting_started/sections/playback.dart';
|
import 'package:spotube/pages/getting_started/sections/playback.dart';
|
||||||
import 'package:spotube/pages/getting_started/sections/region.dart';
|
import 'package:spotube/pages/getting_started/sections/region.dart';
|
||||||
import 'package:spotube/pages/getting_started/sections/support.dart';
|
import 'package:spotube/pages/getting_started/sections/support.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
import 'package:spotube/themes/theme.dart';
|
|
||||||
|
|
||||||
class GettingStarting extends HookConsumerWidget {
|
class GettingStarting extends HookConsumerWidget {
|
||||||
static const name = "getting_started";
|
static const name = "getting_started";
|
||||||
@ -18,12 +16,6 @@ class GettingStarting extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
|
||||||
final themeData = theme(
|
|
||||||
preferences.accentColorScheme,
|
|
||||||
Brightness.dark,
|
|
||||||
preferences.amoledDarkTheme,
|
|
||||||
);
|
|
||||||
final pageController = usePageController();
|
final pageController = usePageController();
|
||||||
|
|
||||||
final onNext = useCallback(() {
|
final onNext = useCallback(() {
|
||||||
@ -40,11 +32,11 @@ class GettingStarting extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}, [pageController]);
|
}, [pageController]);
|
||||||
|
|
||||||
return Theme(
|
return Scaffold(
|
||||||
data: themeData,
|
headers: [
|
||||||
child: Scaffold(
|
TitleBar(
|
||||||
appBar: TitleBar(
|
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
|
surfaceBlur: 0,
|
||||||
trailing: [
|
trailing: [
|
||||||
ListenableBuilder(
|
ListenableBuilder(
|
||||||
listenable: pageController,
|
listenable: pageController,
|
||||||
@ -54,7 +46,7 @@ class GettingStarting extends HookConsumerWidget {
|
|||||||
child: pageController.hasClients &&
|
child: pageController.hasClients &&
|
||||||
(pageController.page == 0 || pageController.page == 3)
|
(pageController.page == 0 || pageController.page == 3)
|
||||||
? const SizedBox()
|
? const SizedBox()
|
||||||
: TextButton(
|
: Button.secondary(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pageController.animateToPage(
|
pageController.animateToPage(
|
||||||
3,
|
3,
|
||||||
@ -62,43 +54,33 @@ class GettingStarting extends HookConsumerWidget {
|
|||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(context.l10n.skip_this_nonsense),
|
||||||
context.l10n.skip_this_nonsense,
|
|
||||||
style: TextStyle(
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
decorationColor: themeData.colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
extendBodyBehindAppBar: true,
|
],
|
||||||
body: DecoratedBox(
|
floatingHeader: true,
|
||||||
decoration: BoxDecoration(
|
child: DecoratedBox(
|
||||||
image: DecorationImage(
|
decoration: BoxDecoration(
|
||||||
image: Assets.bengaliPatternsBg.provider(),
|
image: DecorationImage(
|
||||||
fit: BoxFit.cover,
|
image: Assets.bengaliPatternsBg.provider(),
|
||||||
colorFilter: const ColorFilter.mode(
|
fit: BoxFit.cover,
|
||||||
Colors.black38,
|
),
|
||||||
BlendMode.srcOver,
|
),
|
||||||
),
|
child: PageView(
|
||||||
|
controller: pageController,
|
||||||
|
children: [
|
||||||
|
GettingStartedPageGreetingSection(onNext: onNext),
|
||||||
|
GettingStartedPageLanguageRegionSection(onNext: onNext),
|
||||||
|
GettingStartedPagePlaybackSection(
|
||||||
|
onNext: onNext,
|
||||||
|
onPrevious: onPrevious,
|
||||||
),
|
),
|
||||||
),
|
const GettingStartedScreenSupportSection(),
|
||||||
child: PageView(
|
],
|
||||||
controller: pageController,
|
|
||||||
children: [
|
|
||||||
GettingStartedPageGreetingSection(onNext: onNext),
|
|
||||||
GettingStartedPageLanguageRegionSection(onNext: onNext),
|
|
||||||
GettingStartedPagePlaybackSection(
|
|
||||||
onNext: onNext,
|
|
||||||
onPrevious: onPrevious,
|
|
||||||
),
|
|
||||||
const GettingStartedScreenSupportSection(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/modules/getting_started/blur_card.dart';
|
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/modules/getting_started/blur_card.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class GettingStartedPageGreetingSection extends HookConsumerWidget {
|
class GettingStartedPageGreetingSection extends HookConsumerWidget {
|
||||||
@ -13,8 +12,6 @@ class GettingStartedPageGreetingSection extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme) = Theme.of(context);
|
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: BlurCard(
|
child: BlurCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -22,30 +19,19 @@ class GettingStartedPageGreetingSection extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Assets.spotubeLogoPng.image(height: 200),
|
Assets.spotubeLogoPng.image(height: 200),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
Text(
|
const Text("Spotube").semiBold().h4(),
|
||||||
"Spotube",
|
|
||||||
style:
|
|
||||||
textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
kIsMobile
|
kIsMobile
|
||||||
? context.l10n.freedom_of_music_palm
|
? context.l10n.freedom_of_music_palm
|
||||||
: context.l10n.freedom_of_music,
|
: context.l10n.freedom_of_music,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: textTheme.titleMedium?.copyWith(
|
).light().large().italic(),
|
||||||
fontWeight: FontWeight.w300,
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(84),
|
const Gap(84),
|
||||||
Directionality(
|
Button.primary(
|
||||||
textDirection: TextDirection.rtl,
|
onPressed: onNext,
|
||||||
child: FilledButton.icon(
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
onPressed: onNext,
|
child: Text(context.l10n.get_started),
|
||||||
icon: const Icon(SpotubeIcons.angleRight),
|
|
||||||
label: Text(context.l10n.get_started),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/ui/button_tile.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/modules/getting_started/blur_card.dart';
|
import 'package:spotube/modules/getting_started/blur_card.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
@ -14,14 +14,14 @@ final audioSourceToIconMap = {
|
|||||||
AudioSource.youtube: const Icon(
|
AudioSource.youtube: const Icon(
|
||||||
SpotubeIcons.youtube,
|
SpotubeIcons.youtube,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
size: 30,
|
size: 20,
|
||||||
),
|
),
|
||||||
AudioSource.piped: const Icon(SpotubeIcons.piped, size: 30),
|
AudioSource.piped: const Icon(SpotubeIcons.piped, size: 20),
|
||||||
AudioSource.invidious: ClipRRect(
|
AudioSource.invidious: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(48),
|
borderRadius: BorderRadius.circular(26),
|
||||||
child: Assets.invidious.image(width: 48, height: 48),
|
child: Assets.invidious.image(width: 26, height: 26),
|
||||||
),
|
),
|
||||||
AudioSource.jiosaavn: Assets.jiosaavn.image(width: 48, height: 48),
|
AudioSource.jiosaavn: Assets.jiosaavn.image(width: 20, height: 20),
|
||||||
};
|
};
|
||||||
|
|
||||||
class GettingStartedPagePlaybackSection extends HookConsumerWidget {
|
class GettingStartedPagePlaybackSection extends HookConsumerWidget {
|
||||||
@ -36,8 +36,6 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme, :colorScheme, :dividerColor) =
|
|
||||||
Theme.of(context);
|
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
final preferencesNotifier = ref.read(userPreferencesProvider.notifier);
|
final preferencesNotifier = ref.read(userPreferencesProvider.notifier);
|
||||||
|
|
||||||
@ -62,76 +60,56 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(SpotubeIcons.album, size: 16),
|
const Icon(SpotubeIcons.album, size: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(context.l10n.playback, style: textTheme.titleMedium),
|
Text(context.l10n.playback).semiBold().large(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
ListTile(
|
Align(
|
||||||
title: Text(
|
alignment: Alignment.centerLeft,
|
||||||
context.l10n.select_audio_source,
|
child: Text(context.l10n.select_audio_source).semiBold().large(),
|
||||||
style: textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
ToggleButtons(
|
Select<AudioSource>(
|
||||||
isSelected: [
|
value: preferences.audioSource,
|
||||||
for (final source in AudioSource.values)
|
onChanged: (value) {
|
||||||
preferences.audioSource == source,
|
if (value == null) return;
|
||||||
],
|
preferencesNotifier.setAudioSource(value);
|
||||||
onPressed: (index) {
|
|
||||||
preferencesNotifier.setAudioSource(AudioSource.values[index]);
|
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(8),
|
placeholder: Text(preferences.audioSource.name.capitalize()),
|
||||||
|
itemBuilder: (context, value) => Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
audioSourceToIconMap[value]!,
|
||||||
|
Text(value.name.capitalize()),
|
||||||
|
],
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
for (final source in AudioSource.values)
|
for (final source in AudioSource.values)
|
||||||
SizedBox.square(
|
SelectItemButton(
|
||||||
dimension: 84,
|
value: source,
|
||||||
child: Column(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
spacing: 6,
|
||||||
children: [
|
children: [
|
||||||
audioSourceToIconMap[source]!,
|
audioSourceToIconMap[source]!,
|
||||||
const Gap(8),
|
Text(source.name.capitalize()),
|
||||||
Text(
|
|
||||||
source.name.capitalize(),
|
|
||||||
style: textTheme.bodySmall!.copyWith(
|
|
||||||
color: preferences.audioSource == source
|
|
||||||
? colorScheme.primary
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: Align(
|
|
||||||
alignment: switch (preferences.audioSource) {
|
|
||||||
AudioSource.youtube => Alignment.centerLeft,
|
|
||||||
AudioSource.piped ||
|
|
||||||
AudioSource.invidious =>
|
|
||||||
Alignment.center,
|
|
||||||
AudioSource.jiosaavn => Alignment.centerRight,
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
audioSourceToDescription[preferences.audioSource]!,
|
|
||||||
style: textTheme.bodySmall?.copyWith(
|
|
||||||
color: dividerColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
ListTile(
|
Text(
|
||||||
|
audioSourceToDescription[preferences.audioSource]!,
|
||||||
|
).small().muted(),
|
||||||
|
const Gap(16),
|
||||||
|
ButtonTile(
|
||||||
title: Text(context.l10n.endless_playback),
|
title: Text(context.l10n.endless_playback),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
context.l10n.endless_playback_description,
|
context.l10n.endless_playback_description,
|
||||||
style: textTheme.bodySmall?.copyWith(
|
).small().muted(),
|
||||||
color: dividerColor,
|
onPressed: () {
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
preferencesNotifier
|
preferencesNotifier
|
||||||
.setEndlessPlayback(!preferences.endlessPlayback);
|
.setEndlessPlayback(!preferences.endlessPlayback);
|
||||||
},
|
},
|
||||||
@ -146,17 +124,17 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
Button.secondary(
|
||||||
icon: const Icon(SpotubeIcons.angleLeft),
|
leading: const Icon(SpotubeIcons.angleLeft),
|
||||||
label: Text(context.l10n.previous),
|
|
||||||
onPressed: onPrevious,
|
onPressed: onPrevious,
|
||||||
|
child: Text(context.l10n.previous),
|
||||||
),
|
),
|
||||||
Directionality(
|
Directionality(
|
||||||
textDirection: TextDirection.rtl,
|
textDirection: TextDirection.rtl,
|
||||||
child: FilledButton.icon(
|
child: Button.primary(
|
||||||
icon: const Icon(SpotubeIcons.angleRight),
|
leading: const Icon(SpotubeIcons.angleRight),
|
||||||
label: Text(context.l10n.next),
|
|
||||||
onPressed: onNext,
|
onPressed: onNext,
|
||||||
|
child: Text(context.l10n.next),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/language_codes.dart';
|
import 'package:spotube/collections/language_codes.dart';
|
||||||
import 'package:spotube/collections/spotify_markets.dart';
|
import 'package:spotube/collections/spotify_markets.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
@ -16,7 +16,6 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme, :dividerColor) = Theme.of(context);
|
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
@ -32,92 +31,119 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(context.l10n.language_region).semiBold(),
|
||||||
context.l10n.language_region,
|
|
||||||
style: textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Gap(48),
|
const Gap(30),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(context.l10n.choose_your_region).semiBold(),
|
||||||
context.l10n.choose_your_region,
|
|
||||||
style: textTheme.titleSmall,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
context.l10n.choose_your_region_description,
|
context.l10n.choose_your_region_description,
|
||||||
style: textTheme.bodySmall?.copyWith(
|
).small().muted(),
|
||||||
color: dividerColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
DropdownMenu(
|
Text(context.l10n.market_place_region).small(),
|
||||||
initialSelection: preferences.market,
|
const Gap(8),
|
||||||
onSelected: (value) {
|
SizedBox(
|
||||||
if (value == null) return;
|
width: double.infinity,
|
||||||
ref
|
child: Select<Market>(
|
||||||
.read(userPreferencesProvider.notifier)
|
value: preferences.market,
|
||||||
.setRecommendationMarket(value);
|
onChanged: (value) {
|
||||||
},
|
if (value == null) return;
|
||||||
hintText: preferences.market.name,
|
ref
|
||||||
label: Text(context.l10n.market_place_region),
|
.read(userPreferencesProvider.notifier)
|
||||||
inputDecorationTheme:
|
.setRecommendationMarket(value);
|
||||||
const InputDecorationTheme(isDense: true),
|
},
|
||||||
dropdownMenuEntries: [
|
placeholder: Text(preferences.market.name),
|
||||||
for (final market in spotifyMarkets)
|
itemBuilder: (context, value) => Text(
|
||||||
DropdownMenuEntry(
|
spotifyMarkets
|
||||||
value: market.$1,
|
.firstWhere((element) => element.$1 == value)
|
||||||
label: market.$2,
|
.$2,
|
||||||
),
|
),
|
||||||
],
|
searchPlaceholder: Text(context.l10n.search),
|
||||||
|
searchFilter: (item, query) {
|
||||||
|
final market = spotifyMarkets
|
||||||
|
.firstWhere((element) => element.$1 == item)
|
||||||
|
.$2
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
return market.contains(query.toLowerCase()) ? 1 : 0;
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
for (final market in spotifyMarkets)
|
||||||
|
SelectItemButton(
|
||||||
|
value: market.$1,
|
||||||
|
child: Text(market.$2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Gap(36),
|
const Gap(36),
|
||||||
Text(
|
Text(
|
||||||
context.l10n.choose_your_language,
|
context.l10n.choose_your_language,
|
||||||
style: textTheme.titleSmall,
|
).semiBold(),
|
||||||
),
|
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
DropdownMenu(
|
Text(context.l10n.language).small(),
|
||||||
initialSelection: preferences.locale,
|
const Gap(8),
|
||||||
onSelected: (locale) {
|
SizedBox(
|
||||||
if (locale == null) return;
|
width: double.infinity,
|
||||||
ref
|
child: Select<Locale>(
|
||||||
.read(userPreferencesProvider.notifier)
|
value: preferences.locale,
|
||||||
.setLocale(locale);
|
onChanged: (locale) {
|
||||||
},
|
if (locale == null) return;
|
||||||
hintText: context.l10n.system_default,
|
ref
|
||||||
label: Text(context.l10n.language),
|
.read(userPreferencesProvider.notifier)
|
||||||
inputDecorationTheme:
|
.setLocale(locale);
|
||||||
const InputDecorationTheme(isDense: true),
|
},
|
||||||
dropdownMenuEntries: [
|
placeholder: Text(context.l10n.system_default),
|
||||||
DropdownMenuEntry(
|
itemBuilder: (context, value) =>
|
||||||
value: const Locale("system", "system"),
|
value.languageCode == "system"
|
||||||
label: context.l10n.system_default,
|
? Text(context.l10n.system_default)
|
||||||
),
|
: Text(
|
||||||
for (final locale in L10n.all)
|
LanguageLocals.getDisplayLanguage(
|
||||||
DropdownMenuEntry(
|
value.languageCode)
|
||||||
value: locale,
|
.toString(),
|
||||||
label: LanguageLocals.getDisplayLanguage(
|
),
|
||||||
locale.languageCode)
|
searchPlaceholder: Text(context.l10n.search),
|
||||||
.toString(),
|
searchFilter: (locale, query) {
|
||||||
|
final language = LanguageLocals.getDisplayLanguage(
|
||||||
|
locale.languageCode)
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
return language
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(query.toLowerCase())
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
SelectItemButton(
|
||||||
|
value: const Locale("system", "system"),
|
||||||
|
child: Text(context.l10n.system_default),
|
||||||
),
|
),
|
||||||
],
|
for (final locale in L10n.all)
|
||||||
|
SelectItemButton(
|
||||||
|
value: locale,
|
||||||
|
child: Text(
|
||||||
|
LanguageLocals.getDisplayLanguage(
|
||||||
|
locale.languageCode)
|
||||||
|
.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Gap(48),
|
const Gap(48),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Directionality(
|
child: Button.primary(
|
||||||
textDirection: TextDirection.rtl,
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
child: FilledButton.icon(
|
onPressed: onNext,
|
||||||
icon: const Icon(SpotubeIcons.angleRight),
|
child: Text(context.l10n.next),
|
||||||
label: Text(context.l10n.next),
|
|
||||||
onPressed: onNext,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/env.dart';
|
import 'package:spotube/collections/env.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/modules/getting_started/blur_card.dart';
|
import 'package:spotube/modules/getting_started/blur_card.dart';
|
||||||
@ -16,7 +15,6 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme, :colorScheme) = Theme.of(context);
|
|
||||||
final onLogin = useLoginCallback(ref);
|
final onLogin = useLoginCallback(ref);
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
@ -34,9 +32,8 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
context.l10n.help_project_grow,
|
context.l10n.help_project_grow,
|
||||||
style:
|
style: const TextStyle(color: Colors.pink),
|
||||||
textTheme.titleMedium?.copyWith(color: Colors.pink),
|
).semiBold(),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
@ -46,38 +43,57 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
Button(
|
||||||
icon: const Icon(SpotubeIcons.github),
|
leading: const Icon(SpotubeIcons.github),
|
||||||
label: Text(context.l10n.contribute_on_github),
|
style: ButtonVariance.primary.copyWith(
|
||||||
style: FilledButton.styleFrom(
|
decoration: (context, states, value) {
|
||||||
backgroundColor: Colors.black,
|
if (states.isNotEmpty) {
|
||||||
foregroundColor: Colors.white,
|
return ButtonVariance.primary
|
||||||
shape: RoundedRectangleBorder(
|
.decoration(context, states);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BoxDecoration(
|
||||||
|
color: Colors.black,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await launchUrlString(
|
await launchUrlString(
|
||||||
"https://github.com/KRTirtho/spotube",
|
"https://github.com/KRTirtho/spotube",
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: Text(
|
||||||
|
context.l10n.contribute_on_github,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (!Env.hideDonations) ...[
|
if (!Env.hideDonations) ...[
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
FilledButton.icon(
|
Button(
|
||||||
icon: const Icon(SpotubeIcons.openCollective),
|
leading: const Icon(SpotubeIcons.openCollective),
|
||||||
label: Text(context.l10n.donate_on_open_collective),
|
style: ButtonVariance.primary.copyWith(
|
||||||
style: FilledButton.styleFrom(
|
decoration: (context, states, value) {
|
||||||
backgroundColor: const Color(0xff4cb7f6),
|
if (states.isNotEmpty) {
|
||||||
foregroundColor: Colors.white,
|
return ButtonVariance.primary
|
||||||
),
|
.decoration(context, states);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BoxDecoration(
|
||||||
|
color: const Color(0xff4cb7f6),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
);
|
||||||
|
}),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await launchUrlString(
|
await launchUrlString(
|
||||||
"https://opencollective.com/spotube",
|
"https://opencollective.com/spotube",
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: Text(
|
||||||
|
context.l10n.donate_on_open_collective,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@ -91,42 +107,40 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
DecoratedBox(
|
Button.secondary(
|
||||||
decoration: BoxDecoration(
|
leading: const Icon(SpotubeIcons.anonymous),
|
||||||
borderRadius: BorderRadius.circular(8),
|
onPressed: () async {
|
||||||
gradient: LinearGradient(
|
await KVStoreService.setDoneGettingStarted(true);
|
||||||
colors: [
|
if (context.mounted) {
|
||||||
colorScheme.primary,
|
context.goNamed(HomePage.name);
|
||||||
colorScheme.secondary,
|
}
|
||||||
],
|
},
|
||||||
),
|
child: Text(context.l10n.browse_anonymously),
|
||||||
),
|
|
||||||
child: TextButton.icon(
|
|
||||||
icon: const Icon(SpotubeIcons.anonymous),
|
|
||||||
label: Text(context.l10n.browse_anonymously),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
await KVStoreService.setDoneGettingStarted(true);
|
|
||||||
if (context.mounted) {
|
|
||||||
context.goNamed(HomePage.name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
FilledButton.icon(
|
Button.primary(
|
||||||
icon: const Icon(SpotubeIcons.spotify),
|
leading: const Icon(SpotubeIcons.spotify),
|
||||||
label: Text(context.l10n.connect_with_spotify),
|
style: ButtonVariance.primary.copyWith(
|
||||||
style: FilledButton.styleFrom(
|
decoration: (context, states, value) {
|
||||||
backgroundColor: const Color(0xff1db954),
|
if (states.isNotEmpty) {
|
||||||
foregroundColor: Colors.white,
|
return ButtonVariance.primary
|
||||||
|
.decoration(context, states);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BoxDecoration(
|
||||||
|
color: const Color(0xff1db954),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await KVStoreService.setDoneGettingStarted(true);
|
await KVStoreService.setDoneGettingStarted(true);
|
||||||
await onLogin();
|
await onLogin();
|
||||||
},
|
},
|
||||||
|
child: Text(
|
||||||
|
context.l10n.connect_with_spotify,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -53,7 +53,7 @@ class PlaylistPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
pagination: PaginationProps(
|
pagination: PaginationProps(
|
||||||
hasNextPage: tracks.asData?.value.hasMore ?? false,
|
hasNextPage: tracks.asData?.value.hasMore ?? false,
|
||||||
isLoading: tracks.isLoadingNextPage,
|
isLoading: tracks.isLoading || tracks.isLoadingNextPage,
|
||||||
onFetchMore: tracksNotifier.fetchMore,
|
onFetchMore: tracksNotifier.fetchMore,
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
ref.invalidate(playlistTracksProvider(playlist.id!));
|
ref.invalidate(playlistTracksProvider(playlist.id!));
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_undraw/flutter_undraw.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
@ -159,21 +160,13 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: mediaQuery.height * 0.2,
|
height: mediaQuery.height * 0.2,
|
||||||
),
|
),
|
||||||
Icon(
|
Undraw(
|
||||||
SpotubeIcons.web,
|
illustration: UndrawIllustration.explore,
|
||||||
size: 120,
|
color: theme.colorScheme.primary,
|
||||||
color: theme.colorScheme.foreground
|
height: 200 * theme.scaling,
|
||||||
.withOpacity(0.7),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(
|
Text(context.l10n.search_to_get_results).large(),
|
||||||
context.l10n.search_to_get_results,
|
|
||||||
style: theme.typography.h3.copyWith(
|
|
||||||
fontWeight: FontWeight.w900,
|
|
||||||
color: theme.colorScheme.foreground
|
|
||||||
.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(false, true) => Container(
|
(false, true) => Container(
|
||||||
|
30
pubspec.lock
30
pubspec.lock
@ -963,18 +963,26 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
flutter_svg:
|
flutter_svg:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2"
|
sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.6"
|
version: "2.0.17"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_undraw:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_undraw
|
||||||
|
sha256: "17fe2738231c502171f984c003f6e40979de1a2550ef2debdd29fec27ae006ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -1662,14 +1670,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
path_drawing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_drawing
|
|
||||||
sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
path_parsing:
|
path_parsing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2525,6 +2525,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.1"
|
version: "4.5.1"
|
||||||
|
vector_graphics:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics
|
||||||
|
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.15"
|
||||||
vector_graphics_codec:
|
vector_graphics_codec:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
10
pubspec.yaml
10
pubspec.yaml
@ -63,6 +63,7 @@ dependencies:
|
|||||||
flutter_riverpod: ^2.5.1
|
flutter_riverpod: ^2.5.1
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
flutter_sharing_intent: ^1.1.0
|
flutter_sharing_intent: ^1.1.0
|
||||||
|
flutter_undraw: ^0.2.0
|
||||||
form_builder_validators: ^11.1.1
|
form_builder_validators: ^11.1.1
|
||||||
form_validator: ^2.1.1
|
form_validator: ^2.1.1
|
||||||
freezed_annotation: ^2.4.1
|
freezed_annotation: ^2.4.1
|
||||||
@ -163,6 +164,7 @@ dependency_overrides:
|
|||||||
path: packages/bonsoir_android
|
path: packages/bonsoir_android
|
||||||
web: ^1.1.0
|
web: ^1.1.0
|
||||||
meta: 1.16.0
|
meta: 1.16.0
|
||||||
|
flutter_svg: ^2.0.17
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
generate: true
|
generate: true
|
||||||
@ -174,6 +176,14 @@ flutter:
|
|||||||
- assets/backgrounds/
|
- assets/backgrounds/
|
||||||
- assets/patterns/
|
- assets/patterns/
|
||||||
- LICENSE
|
- LICENSE
|
||||||
|
- packages/flutter_undraw/assets/undraw/access_denied.svg
|
||||||
|
- packages/flutter_undraw/assets/undraw/fixing_bugs.svg
|
||||||
|
- packages/flutter_undraw/assets/undraw/secure_login.svg
|
||||||
|
- packages/flutter_undraw/assets/undraw/explore.svg
|
||||||
|
- packages/flutter_undraw/assets/undraw/dreamer.svg
|
||||||
|
- packages/flutter_undraw/assets/undraw/happy_music.svg
|
||||||
|
- packages/flutter_undraw/assets/undraw/follow_me_drone.svg
|
||||||
|
- packages/flutter_undraw/assets/undraw/taken.svg
|
||||||
fonts:
|
fonts:
|
||||||
- family: GeistSans
|
- family: GeistSans
|
||||||
fonts:
|
fonts:
|
||||||
|
@ -9,7 +9,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"bn": [
|
"bn": [
|
||||||
@ -22,7 +27,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ca": [
|
"ca": [
|
||||||
@ -35,7 +45,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"cs": [
|
"cs": [
|
||||||
@ -48,7 +63,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
@ -61,7 +81,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
@ -74,7 +99,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"eu": [
|
"eu": [
|
||||||
@ -87,7 +117,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
@ -100,7 +135,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
@ -113,7 +153,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
@ -126,7 +171,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
@ -139,7 +189,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"id": [
|
"id": [
|
||||||
@ -152,7 +207,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
@ -165,7 +225,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
@ -178,7 +243,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ka": [
|
"ka": [
|
||||||
@ -191,7 +261,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
@ -204,7 +279,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ne": [
|
"ne": [
|
||||||
@ -217,7 +297,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
@ -230,7 +315,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
@ -243,7 +333,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
@ -256,7 +351,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
@ -269,7 +369,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"th": [
|
"th": [
|
||||||
@ -282,7 +387,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
@ -295,7 +405,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"uk": [
|
"uk": [
|
||||||
@ -308,7 +423,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"vi": [
|
"vi": [
|
||||||
@ -321,7 +441,12 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
@ -334,6 +459,11 @@
|
|||||||
"add_all_to_queue",
|
"add_all_to_queue",
|
||||||
"play_all_next",
|
"play_all_next",
|
||||||
"pause",
|
"pause",
|
||||||
"view_all"
|
"view_all",
|
||||||
|
"no_tracks_added_yet",
|
||||||
|
"no_tracks",
|
||||||
|
"no_tracks_listened_yet",
|
||||||
|
"not_following_artists",
|
||||||
|
"no_favorite_albums_yet"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user