diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index 2495863c..61bca7b1 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -28,6 +28,7 @@ class TrackDetailsDialog extends HookWidget { artists: track.artists ?? [], mainAxisAlignment: WrapAlignment.start, textStyle: const TextStyle(color: Colors.blue), + hideOverflowArtist: false, ), context.l10n.album: LinkText( track.album!.name!, diff --git a/lib/components/links/artist_link.dart b/lib/components/links/artist_link.dart index 47ddecd8..d5ec24f8 100644 --- a/lib/components/links/artist_link.dart +++ b/lib/components/links/artist_link.dart @@ -1,6 +1,8 @@ +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/links/anchor_button.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/pages/artist/artist.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -9,7 +11,9 @@ class ArtistLink extends StatelessWidget { final WrapCrossAlignment crossAxisAlignment; final WrapAlignment mainAxisAlignment; final TextStyle textStyle; + final bool hideOverflowArtist; final void Function(String route)? onRouteChange; + final VoidCallback? onOverflowArtistClick; const ArtistLink({ super.key, @@ -18,44 +22,61 @@ class ArtistLink extends StatelessWidget { this.mainAxisAlignment = WrapAlignment.center, this.textStyle = const TextStyle(), this.onRouteChange, - }); + this.hideOverflowArtist = true, + this.onOverflowArtistClick, + }) : assert(hideOverflowArtist ? onOverflowArtistClick != null : true); @override Widget build(BuildContext context) { + final ThemeData(:colorScheme) = Theme.of(context); + return Wrap( crossAxisAlignment: crossAxisAlignment, alignment: mainAxisAlignment, - children: artists - .asMap() - .entries - .map( - (artist) => Builder(builder: (context) { - if (artist.value.name == null) { - return Text("Spotify", style: textStyle); - } - return AnchorButton( - (artist.key != artists.length - 1) - ? "${artist.value.name}, " - : artist.value.name!, - onTap: () { - if (onRouteChange != null) { - onRouteChange?.call("/artist/${artist.value.id}"); - } else { - ServiceUtils.pushNamed( - context, - ArtistPage.name, - pathParameters: { - "id": artist.value.id!, - }, - ); - } - }, - overflow: TextOverflow.ellipsis, - style: textStyle, - ); - }), - ) - .toList(), + children: [ + ...(hideOverflowArtist ? artists.take(3).toList() : artists) + .asMap() + .entries + .map( + (artist) => Builder(builder: (context) { + if (artist.value.name == null) { + return Text("Spotify", style: textStyle); + } + return AnchorButton( + (artist.key != artists.length - 1) + ? "${artist.value.name}, " + : artist.value.name!, + onTap: () { + if (onRouteChange != null) { + onRouteChange?.call("/artist/${artist.value.id}"); + } else { + ServiceUtils.pushNamed( + context, + ArtistPage.name, + pathParameters: { + "id": artist.value.id!, + }, + ); + } + }, + overflow: TextOverflow.ellipsis, + style: textStyle, + ); + }), + ), + if (hideOverflowArtist && artists.length > 3) + AnchorButton( + context.l10n.and_n_more(artists.length - 3), + onTap: () { + onOverflowArtistClick?.call(); + }, + overflow: TextOverflow.ellipsis, + style: textStyle.copyWith( + color: colorScheme.secondary, + decoration: TextDecoration.underline, + ), + ), + ], ); } } diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index c6cfdd35..a4c5661a 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -20,6 +20,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/local_track.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart'; @@ -27,6 +28,7 @@ import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify_provider.dart'; +import 'package:spotube/utils/service_utils.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -313,7 +315,16 @@ class TrackOptions extends HookConsumerWidget { ), subtitle: Align( alignment: Alignment.centerLeft, - child: ArtistLink(artists: track.artists!), + child: ArtistLink( + artists: track.artists!, + onOverflowArtistClick: () => ServiceUtils.pushNamed( + context, + TrackPage.name, + pathParameters: { + "id": track.id!, + }, + ), + ), ), ), ], diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index 0e8d2cd0..12ce063f 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -17,9 +17,11 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/local_track.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/utils/service_utils.dart'; class TrackTile extends HookConsumerWidget { /// [index] will not be shown if null @@ -245,7 +247,16 @@ class TrackTile extends HookConsumerWidget { : ClipRect( child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 40), - child: ArtistLink(artists: track.artists ?? []), + child: ArtistLink( + artists: track.artists ?? [], + onOverflowArtistClick: () => ServiceUtils.pushNamed( + context, + TrackPage.name, + pathParameters: { + "id": track.id!, + }, + ), + ), ), ), ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 04fc8566..ab615225 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -325,5 +325,6 @@ "connect_client_alert": "You're being controlled by {client}", "this_device": "This Device", "remote": "Remote", - "stats": "Stats" + "stats": "Stats", + "and_n_more": "and {count} more" } \ No newline at end of file diff --git a/lib/modules/library/user_downloads/download_item.dart b/lib/modules/library/user_downloads/download_item.dart index bc9abf6a..c4bd7bce 100644 --- a/lib/modules/library/user_downloads/download_item.dart +++ b/lib/modules/library/user_downloads/download_item.dart @@ -7,9 +7,11 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/services/download_manager/download_status.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; +import 'package:spotube/utils/service_utils.dart'; class DownloadItem extends HookConsumerWidget { final Track track; @@ -62,6 +64,13 @@ class DownloadItem extends HookConsumerWidget { subtitle: ArtistLink( artists: track.artists ?? [], mainAxisAlignment: WrapAlignment.start, + onOverflowArtistClick: () => ServiceUtils.pushNamed( + context, + TrackPage.name, + pathParameters: { + "id": track.id!, + }, + ), ), trailing: isQueryingSourceInfo ? Text( diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index d75df796..6db84692 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -24,11 +24,13 @@ import 'package:spotube/hooks/utils/use_custom_status_bar_color.dart'; import 'package:spotube/hooks/utils/use_palette_color.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; +import 'package:spotube/utils/service_utils.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -260,6 +262,14 @@ class PlayerView extends HookConsumerWidget { panelController.close(); GoRouter.of(context).push(route); }, + onOverflowArtistClick: () => + ServiceUtils.pushNamed( + context, + TrackPage.name, + pathParameters: { + "id": currentTrack!.id!, + }, + ), ), ], ), diff --git a/lib/modules/player/player_track_details.dart b/lib/modules/player/player_track_details.dart index d722830e..8d3b99fa 100644 --- a/lib/modules/player/player_track_details.dart +++ b/lib/modules/player/player_track_details.dart @@ -9,6 +9,7 @@ import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -81,6 +82,13 @@ class PlayerTrackDetails extends HookConsumerWidget { onRouteChange: (route) { ServiceUtils.push(context, route); }, + onOverflowArtistClick: () => ServiceUtils.pushNamed( + context, + TrackPage.name, + pathParameters: { + "id": track!.id!, + }, + ), ) ], ), diff --git a/lib/modules/stats/common/album_item.dart b/lib/modules/stats/common/album_item.dart index 58604c45..eec68717 100644 --- a/lib/modules/stats/common/album_item.dart +++ b/lib/modules/stats/common/album_item.dart @@ -35,6 +35,13 @@ class StatsAlbumItem extends StatelessWidget { child: ArtistLink( artists: album.artists ?? [], mainAxisAlignment: WrapAlignment.start, + onOverflowArtistClick: () => ServiceUtils.pushNamed( + context, + AlbumPage.name, + pathParameters: { + "id": album.id!, + }, + ), ), ), ], diff --git a/lib/modules/stats/common/track_item.dart b/lib/modules/stats/common/track_item.dart index 33991d43..44e81340 100644 --- a/lib/modules/stats/common/track_item.dart +++ b/lib/modules/stats/common/track_item.dart @@ -33,6 +33,13 @@ class StatsTrackItem extends StatelessWidget { subtitle: ArtistLink( artists: track.artists!, mainAxisAlignment: WrapAlignment.start, + onOverflowArtistClick: () => ServiceUtils.pushNamed( + context, + TrackPage.name, + pathParameters: { + "id": track.id!, + }, + ), ), trailing: info, onTap: () { diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index d27b7867..cae0bd1b 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -144,6 +144,14 @@ class ConnectControlPage extends HookConsumerWidget { artists: playlist.activeTrack?.artists ?? [], textStyle: textTheme.bodyMedium!, mainAxisAlignment: WrapAlignment.start, + onOverflowArtistClick: () => + ServiceUtils.pushNamed( + context, + TrackPage.name, + pathParameters: { + "id": playlist.activeTrack!.id!, + }, + ), ), ), ], diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index dc4defc8..6f3af0e4 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -148,7 +148,12 @@ class TrackPage extends HookConsumerWidget { children: [ const Icon(SpotubeIcons.artist), const Gap(5), - ArtistLink(artists: track.artists!), + Flexible( + child: ArtistLink( + artists: track.artists!, + hideOverflowArtist: false, + ), + ), ], ), const Gap(10), diff --git a/untranslated_messages.json b/untranslated_messages.json index 9e26dfee..5da4c3c6 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1 +1,105 @@ -{} \ No newline at end of file +{ + "ar": [ + "and_n_more" + ], + + "bn": [ + "and_n_more" + ], + + "ca": [ + "and_n_more" + ], + + "cs": [ + "and_n_more" + ], + + "de": [ + "and_n_more" + ], + + "es": [ + "and_n_more" + ], + + "eu": [ + "and_n_more" + ], + + "fa": [ + "and_n_more" + ], + + "fi": [ + "and_n_more" + ], + + "fr": [ + "and_n_more" + ], + + "hi": [ + "and_n_more" + ], + + "id": [ + "and_n_more" + ], + + "it": [ + "and_n_more" + ], + + "ja": [ + "and_n_more" + ], + + "ka": [ + "and_n_more" + ], + + "ko": [ + "and_n_more" + ], + + "ne": [ + "and_n_more" + ], + + "nl": [ + "and_n_more" + ], + + "pl": [ + "and_n_more" + ], + + "pt": [ + "and_n_more" + ], + + "ru": [ + "and_n_more" + ], + + "th": [ + "and_n_more" + ], + + "tr": [ + "and_n_more" + ], + + "uk": [ + "and_n_more" + ], + + "vi": [ + "and_n_more" + ], + + "zh": [ + "and_n_more" + ] +}