mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: migrate stats to shadcn
This commit is contained in:
parent
e6408ccc0d
commit
6dd9b753b0
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
class AnchorButton<T> extends HookWidget {
|
||||
final String text;
|
||||
|
@ -148,7 +148,10 @@ class PlayerControls extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(SpotubeIcons.shuffle),
|
||||
icon: Icon(
|
||||
SpotubeIcons.shuffle,
|
||||
color: shuffled ? theme.colorScheme.primary : null,
|
||||
),
|
||||
variance: shuffled
|
||||
? ButtonVariance.secondary
|
||||
: ButtonVariance.ghost,
|
||||
@ -228,6 +231,9 @@ class PlayerControls extends HookConsumerWidget {
|
||||
loopMode == PlaylistMode.single
|
||||
? SpotubeIcons.repeatOne
|
||||
: SpotubeIcons.repeat,
|
||||
color: loopMode != PlaylistMode.none
|
||||
? theme.colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
variance: loopMode == PlaylistMode.single ||
|
||||
loopMode == PlaylistMode.loop
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/components/links/anchor_button.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
@ -19,7 +19,7 @@ class RootAppUpdateDialog extends StatelessWidget {
|
||||
return AlertDialog(
|
||||
title: Text(context.l10n.spotube_has_an_update),
|
||||
actions: [
|
||||
FilledButton(
|
||||
Button.primary(
|
||||
child: Text(context.l10n.download_now),
|
||||
onPressed: () => launchUrlString(
|
||||
nightlyBuildNum != null ? nightlyUrl : url,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/ui/button_tile.dart';
|
||||
import 'package:spotube/modules/album/album_card.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/components/links/artist_link.dart';
|
||||
@ -14,8 +15,8 @@ class StatsAlbumItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 8,
|
||||
return ButtonTile(
|
||||
style: ButtonVariance.ghost,
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: UniversalImage(
|
||||
@ -47,7 +48,7 @@ class StatsAlbumItem extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
trailing: info,
|
||||
onTap: () {
|
||||
onPressed: () {
|
||||
ServiceUtils.pushNamed(
|
||||
context,
|
||||
AlbumPage.name,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/components/ui/button_tile.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/pages/artist/artist.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
@ -16,18 +17,19 @@ class StatsArtistItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
return ButtonTile(
|
||||
style: ButtonVariance.ghost,
|
||||
title: Text(artist.name!),
|
||||
horizontalTitleGap: 8,
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
leading: Avatar(
|
||||
initials: artist.name!.substring(0, 1),
|
||||
provider: UniversalImage.imageProvider(
|
||||
(artist.images).asUrlString(
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: info,
|
||||
onTap: () {
|
||||
onPressed: () {
|
||||
ServiceUtils.pushNamed(
|
||||
context,
|
||||
ArtistPage.name,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/components/ui/button_tile.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/extensions/string.dart';
|
||||
import 'package:spotube/pages/playlist/playlist.dart';
|
||||
@ -14,8 +15,8 @@ class StatsPlaylistItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 8,
|
||||
return ButtonTile(
|
||||
style: ButtonVariance.ghost,
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: UniversalImage(
|
||||
@ -33,7 +34,7 @@ class StatsPlaylistItem extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: info,
|
||||
onTap: () {
|
||||
onPressed: () {
|
||||
ServiceUtils.pushNamed(
|
||||
context,
|
||||
PlaylistPage.name,
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/components/links/artist_link.dart';
|
||||
import 'package:spotube/components/ui/button_tile.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/pages/track/track.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
@ -17,8 +18,8 @@ class StatsTrackItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 8,
|
||||
return ButtonTile(
|
||||
style: ButtonVariance.ghost,
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: UniversalImage(
|
||||
@ -42,7 +43,7 @@ class StatsTrackItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
trailing: info,
|
||||
onTap: () {
|
||||
onPressed: () {
|
||||
ServiceUtils.pushNamed(
|
||||
context,
|
||||
TrackPage.name,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
@ -48,7 +48,7 @@ class StatsPageSummarySection extends HookConsumerWidget {
|
||||
title: summaryData.duration.inMinutes.toDouble(),
|
||||
unit: context.l10n.summary_minutes,
|
||||
description: context.l10n.summary_listened_to_music,
|
||||
color: Colors.purple,
|
||||
color: Colors.indigo,
|
||||
onTap: () {
|
||||
ServiceUtils.pushNamed(context, StatsMinutesPage.name);
|
||||
},
|
||||
@ -57,7 +57,7 @@ class StatsPageSummarySection extends HookConsumerWidget {
|
||||
title: summaryData.tracks.toDouble(),
|
||||
unit: context.l10n.summary_songs,
|
||||
description: context.l10n.summary_streamed_overall,
|
||||
color: Colors.lightBlue,
|
||||
color: Colors.blue,
|
||||
onTap: () {
|
||||
ServiceUtils.pushNamed(context, StatsStreamsPage.name);
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
|
||||
class SummaryCard extends StatelessWidget {
|
||||
@ -9,7 +10,7 @@ class SummaryCard extends StatelessWidget {
|
||||
final String description;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
final MaterialColor color;
|
||||
final ColorShades color;
|
||||
|
||||
SummaryCard({
|
||||
super.key,
|
||||
@ -31,15 +32,18 @@ class SummaryCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData(:textTheme, :brightness) = Theme.of(context);
|
||||
final ThemeData(:typography, :brightness) = Theme.of(context);
|
||||
|
||||
final descriptionNewLines = description.split("").where((s) => s == "\n");
|
||||
|
||||
return Card(
|
||||
color: brightness == Brightness.dark ? color.shade100 : color.shade50,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: onTap,
|
||||
fillColor: brightness == Brightness.dark ? color.shade100 : color.shade50,
|
||||
filled: true,
|
||||
borderColor: color,
|
||||
padding: EdgeInsets.zero,
|
||||
borderRadius: context.theme.borderRadiusLg,
|
||||
child: Button.ghost(
|
||||
onPressed: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 15),
|
||||
child: Column(
|
||||
@ -52,13 +56,13 @@ class SummaryCard extends StatelessWidget {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: title,
|
||||
style: textTheme.headlineLarge?.copyWith(
|
||||
style: typography.h2.copyWith(
|
||||
color: color.shade900,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " $unit",
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
style: typography.semiBold.copyWith(
|
||||
color: color.shade900,
|
||||
),
|
||||
),
|
||||
@ -73,7 +77,7 @@ class SummaryCard extends StatelessWidget {
|
||||
? descriptionNewLines.length + 1
|
||||
: 1,
|
||||
minFontSize: 9,
|
||||
style: textTheme.labelMedium!.copyWith(
|
||||
style: typography.small.copyWith(
|
||||
color: color.shade900,
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.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:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/modules/stats/top/albums.dart';
|
||||
import 'package:spotube/modules/stats/top/artists.dart';
|
||||
import 'package:spotube/modules/stats/top/tracks.dart';
|
||||
@ -13,94 +15,90 @@ class StatsPageTopSection extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final tabController = useTabController(initialLength: 3);
|
||||
final selectedIndex = useState(0);
|
||||
final historyDuration = ref.watch(playbackHistoryTopDurationProvider);
|
||||
final historyDurationNotifier =
|
||||
ref.watch(playbackHistoryTopDurationProvider.notifier);
|
||||
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
flexibleSpace: TabBar(
|
||||
controller: tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(context.l10n.top_tracks),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(context.l10n.top_artists),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(context.l10n.top_albums),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: DropdownButton(
|
||||
style: Theme.of(context).textTheme.bodySmall!,
|
||||
isDense: true,
|
||||
final translations = <HistoryDuration, String>{
|
||||
HistoryDuration.days7: context.l10n.this_week,
|
||||
HistoryDuration.days30: context.l10n.this_month,
|
||||
HistoryDuration.months6: context.l10n.last_6_months,
|
||||
HistoryDuration.year: context.l10n.this_year,
|
||||
HistoryDuration.years2: context.l10n.last_2_years,
|
||||
HistoryDuration.allTime: context.l10n.all_time,
|
||||
};
|
||||
|
||||
final dropdown = Select<HistoryDuration>(
|
||||
popupConstraints: const BoxConstraints(maxWidth: 150),
|
||||
popupWidthConstraint: PopoverConstraint.flexible,
|
||||
padding: const EdgeInsets.all(4),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
underline: const SizedBox(),
|
||||
value: historyDuration,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
historyDurationNotifier.update((_) => value);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.days7,
|
||||
child: Text(context.l10n.this_week),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.days30,
|
||||
child: Text(context.l10n.this_month),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.months6,
|
||||
child: Text(context.l10n.last_6_months),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.year,
|
||||
child: Text(context.l10n.this_year),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.years2,
|
||||
child: Text(context.l10n.last_2_years),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.allTime,
|
||||
child: Text(context.l10n.all_time),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ListenableBuilder(
|
||||
listenable: tabController,
|
||||
builder: (context, _) {
|
||||
return switch (tabController.index) {
|
||||
1 => const TopArtists(),
|
||||
2 => const TopAlbums(),
|
||||
_ => const TopTracks(),
|
||||
};
|
||||
},
|
||||
itemBuilder: (context, item) => Text(translations[item]!),
|
||||
children: [
|
||||
for (final item in HistoryDuration.values)
|
||||
SelectItemButton(
|
||||
value: item,
|
||||
child: Text(translations[item]!),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return SliverLayoutBuilder(builder: (context, constraints) {
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
elevation: 0,
|
||||
backgroundColor: context.theme.colorScheme.background,
|
||||
flexibleSpace: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
TabList(
|
||||
index: selectedIndex.value,
|
||||
children: [
|
||||
TabButton(
|
||||
child: Text(context.l10n.top_tracks),
|
||||
onPressed: () => selectedIndex.value = 0,
|
||||
),
|
||||
TabButton(
|
||||
child: Text(context.l10n.top_artists),
|
||||
onPressed: () => selectedIndex.value = 1,
|
||||
),
|
||||
TabButton(
|
||||
child: Text(context.l10n.top_albums),
|
||||
onPressed: () => selectedIndex.value = 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (constraints.mdAndUp) ...[
|
||||
const Spacer(),
|
||||
dropdown,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (constraints.smAndDown)
|
||||
SliverToBoxAdapter(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: dropdown,
|
||||
),
|
||||
),
|
||||
switch (selectedIndex.value) {
|
||||
1 => const TopArtists(),
|
||||
2 => const TopAlbums(),
|
||||
_ => const TopTracks(),
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
@ -25,11 +25,13 @@ class StatsAlbumsPage extends HookConsumerWidget {
|
||||
final albumsData = topAlbums.asData?.value.items ?? [];
|
||||
|
||||
return Scaffold(
|
||||
appBar: TitleBar(
|
||||
headers: [
|
||||
TitleBar(
|
||||
automaticallyImplyLeading: true,
|
||||
title: Text(context.l10n.albums),
|
||||
),
|
||||
body: Skeletonizer(
|
||||
)
|
||||
],
|
||||
child: Skeletonizer(
|
||||
enabled: topAlbums.isLoading && !topAlbums.isLoadingNextPage,
|
||||
child: InfiniteList(
|
||||
onFetchData: () async {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
@ -28,11 +28,13 @@ class StatsArtistsPage extends HookConsumerWidget {
|
||||
() => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]);
|
||||
|
||||
return Scaffold(
|
||||
appBar: TitleBar(
|
||||
headers: [
|
||||
TitleBar(
|
||||
automaticallyImplyLeading: true,
|
||||
title: Text(context.l10n.artists),
|
||||
),
|
||||
body: Skeletonizer(
|
||||
)
|
||||
],
|
||||
child: Skeletonizer(
|
||||
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,
|
||||
child: InfiniteList(
|
||||
onFetchData: () async {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:sliver_tools/sliver_tools.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
@ -20,7 +20,6 @@ class StatsStreamFeesPage extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final ThemeData(:textTheme, :hintColor) = Theme.of(context);
|
||||
final duration = useState<HistoryDuration>(HistoryDuration.days30);
|
||||
|
||||
final topTracks = ref.watch(
|
||||
@ -40,12 +39,23 @@ class StatsStreamFeesPage extends HookConsumerWidget {
|
||||
[artistsData],
|
||||
);
|
||||
|
||||
final translations = <HistoryDuration, String>{
|
||||
HistoryDuration.days7: context.l10n.this_week,
|
||||
HistoryDuration.days30: context.l10n.this_month,
|
||||
HistoryDuration.months6: context.l10n.last_6_months,
|
||||
HistoryDuration.year: context.l10n.this_year,
|
||||
HistoryDuration.years2: context.l10n.last_2_years,
|
||||
HistoryDuration.allTime: context.l10n.all_time,
|
||||
};
|
||||
|
||||
return Scaffold(
|
||||
appBar: TitleBar(
|
||||
headers: [
|
||||
TitleBar(
|
||||
automaticallyImplyLeading: true,
|
||||
title: Text(context.l10n.streaming_fees_hypothetical),
|
||||
),
|
||||
body: CustomScrollView(
|
||||
)
|
||||
],
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverCrossAxisConstrained(
|
||||
maxCrossAxisExtent: 600,
|
||||
@ -55,10 +65,7 @@ class StatsStreamFeesPage extends HookConsumerWidget {
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
context.l10n.spotify_hipotetical_calculation,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: hintColor,
|
||||
),
|
||||
),
|
||||
).small().muted(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -70,38 +77,21 @@ class StatsStreamFeesPage extends HookConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.total_money(usdFormatter.format(total)),
|
||||
style: textTheme.titleLarge,
|
||||
),
|
||||
DropdownButton<HistoryDuration>(
|
||||
).semiBold().large(),
|
||||
Select<HistoryDuration>(
|
||||
value: duration.value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
duration.value = value;
|
||||
},
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.days7,
|
||||
child: Text(context.l10n.this_week),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.days30,
|
||||
child: Text(context.l10n.this_month),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.months6,
|
||||
child: Text(context.l10n.last_6_months),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.year,
|
||||
child: Text(context.l10n.this_year),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.years2,
|
||||
child: Text(context.l10n.last_2_years),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: HistoryDuration.allTime,
|
||||
child: Text(context.l10n.all_time),
|
||||
itemBuilder: (context, value) => Text(translations[value]!),
|
||||
constraints: const BoxConstraints(maxWidth: 150),
|
||||
popupWidthConstraint: PopoverConstraint.anchorMaxSize,
|
||||
children: [
|
||||
for (final entry in translations.entries)
|
||||
SelectItemButton(
|
||||
value: entry.key,
|
||||
child: Text(entry.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
@ -28,11 +27,13 @@ class StatsMinutesPage extends HookConsumerWidget {
|
||||
final tracksData = topTracks.asData?.value.items ?? [];
|
||||
|
||||
return Scaffold(
|
||||
appBar: TitleBar(
|
||||
headers: [
|
||||
TitleBar(
|
||||
title: Text(context.l10n.minutes_listened),
|
||||
automaticallyImplyLeading: true,
|
||||
),
|
||||
body: Skeletonizer(
|
||||
)
|
||||
],
|
||||
child: Skeletonizer(
|
||||
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,
|
||||
child: InfiniteList(
|
||||
separatorBuilder: (context, index) => const Gap(8),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
@ -26,11 +26,13 @@ class StatsPlaylistsPage extends HookConsumerWidget {
|
||||
final playlistsData = topPlaylists.asData?.value.items ?? [];
|
||||
|
||||
return Scaffold(
|
||||
appBar: TitleBar(
|
||||
headers: [
|
||||
TitleBar(
|
||||
automaticallyImplyLeading: true,
|
||||
title: Text(context.l10n.playlists),
|
||||
),
|
||||
body: Skeletonizer(
|
||||
)
|
||||
],
|
||||
child: Skeletonizer(
|
||||
enabled: topPlaylists.isLoading && !topPlaylists.isLoadingNextPage,
|
||||
child: InfiniteList(
|
||||
onFetchData: () async {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
import 'package:spotube/modules/stats/summary/summary.dart';
|
||||
import 'package:spotube/modules/stats/top/top.dart';
|
||||
@ -16,8 +15,10 @@ class StatsPage extends HookConsumerWidget {
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Scaffold(
|
||||
appBar: kIsMacOS || kIsMobile ? null : const TitleBar(),
|
||||
body: CustomScrollView(
|
||||
headers: [
|
||||
if (kIsWindows || kIsLinux) const TitleBar(),
|
||||
],
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
if (kIsMacOS) const SliverGap(20),
|
||||
const StatsPageSummarySection(),
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/formatters.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
@ -28,11 +27,13 @@ class StatsStreamsPage extends HookConsumerWidget {
|
||||
final tracksData = topTracks.asData?.value.items ?? [];
|
||||
|
||||
return Scaffold(
|
||||
appBar: TitleBar(
|
||||
headers: [
|
||||
TitleBar(
|
||||
title: Text(context.l10n.streamed_songs),
|
||||
automaticallyImplyLeading: true,
|
||||
),
|
||||
body: Skeletonizer(
|
||||
)
|
||||
],
|
||||
child: Skeletonizer(
|
||||
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,
|
||||
child: InfiniteList(
|
||||
separatorBuilder: (context, index) => const Gap(8),
|
||||
|
Loading…
Reference in New Issue
Block a user