diff --git a/lib/components/AlbumScreen/song_menu.dart b/lib/components/AlbumScreen/song_menu.dart index 614fd292..70fc6c4c 100644 --- a/lib/components/AlbumScreen/song_menu.dart +++ b/lib/components/AlbumScreen/song_menu.dart @@ -10,6 +10,7 @@ import 'package:finamp/screens/artist_screen.dart'; import 'package:finamp/services/current_track_metadata_provider.dart'; import 'package:finamp/services/feedback_helper.dart'; import 'package:finamp/services/metadata_provider.dart'; +import 'package:finamp/services/one_line_marquee_helper.dart'; import 'package:finamp/services/music_player_background_task.dart'; import 'package:finamp/services/queue_service.dart'; import 'package:finamp/services/theme_provider.dart'; @@ -314,7 +315,7 @@ class _SongMenuState extends ConsumerState { ), ListTile( leading: Icon( - TablerIcons.playlist, + Icons.playlist_add, color: iconColor, ), title: Text(AppLocalizations.of(context)!.addToQueue), @@ -836,10 +837,10 @@ class _SongInfoState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text( - widget.item.name ?? + OneLineMarqueeHelper( + key: ValueKey(widget.item.id), + text: widget.item.name ?? AppLocalizations.of(context)!.unknownName, - textAlign: TextAlign.start, style: TextStyle( fontSize: widget.condensed ? 16 : 18, height: 1.2, @@ -847,9 +848,6 @@ class _SongInfoState extends ConsumerState { Theme.of(context).textTheme.bodyMedium?.color ?? Colors.white, ), - overflow: TextOverflow.ellipsis, - softWrap: true, - maxLines: 2, ), Padding( padding: widget.condensed @@ -873,11 +871,8 @@ class _SongInfoState extends ConsumerState { color: Theme.of(context).textTheme.bodyMedium?.color ?? Colors.white, - backgroundColor: IconTheme.of(context) - .color - ?.withOpacity(0.1) ?? - Theme.of(context).textTheme.bodyMedium?.color ?? - Colors.white, + backgroundColor: + IconTheme.of(context).color!.withOpacity(0.1), key: widget.item.album == null ? null : ValueKey("${widget.item.album}-album"), diff --git a/lib/components/PlayerScreen/album_chip.dart b/lib/components/PlayerScreen/album_chip.dart index 845b8f3f..10d0f654 100644 --- a/lib/components/PlayerScreen/album_chip.dart +++ b/lib/components/PlayerScreen/album_chip.dart @@ -1,7 +1,10 @@ +import 'package:finamp/models/jellyfin_models.dart' as jellyfin_models; import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; +import 'package:finamp/services/scrolling_text_helper.dart'; +import 'package:marquee/marquee.dart'; import '../../models/jellyfin_models.dart'; import '../../screens/album_screen.dart'; @@ -14,26 +17,73 @@ final _borderRadius = BorderRadius.circular(4); class AlbumChip extends StatelessWidget { const AlbumChip({ super.key, - this.item, - this.backgroundColor, - this.color, + required this.item, + required this.backgroundColor, + required this.color, }); - final BaseItemDto? item; + final BaseItemDto item; final Color? backgroundColor; final Color? color; @override Widget build(BuildContext context) { - if (item == null) return const _EmptyAlbumChip(); + if (item?.album == null) return const SizedBox.shrink(); + + // Calculate text width + final textSpan = TextSpan( + text: item?.album ?? '', + style: Theme.of(context).textTheme.bodyMedium, + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + )..layout(); + + final textWidth = textPainter.width + 24; + final screenWidth = MediaQuery.of(context).size.width - 32; + final shouldScroll = textWidth > screenWidth; return Container( - constraints: const BoxConstraints(minWidth: 10), - child: _AlbumChipContent( - item: item!, - color: color, - backgroundColor: backgroundColor, - )); + height: 32, + width: shouldScroll ? screenWidth : textWidth, + decoration: BoxDecoration( + color: backgroundColor?.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Material( + type: MaterialType.transparency, + child: InkWell( + borderRadius: BorderRadius.circular(4), + onTap: () => Navigator.of(context).pushNamed( + AlbumScreen.routeName, + arguments: item?.albumId, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Center( + child: shouldScroll + ? Marquee( + text: item?.album ?? '', + style: Theme.of(context).textTheme.bodyMedium, + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.center, + blankSpace: 50.0, + velocity: 30.0, + pauseAfterRound: const Duration(seconds: 1), + startAfter: const Duration(seconds: 1), + fadingEdgeStartFraction: 0.1, + fadingEdgeEndFraction: 0.1, + ) + : Text( + item?.album ?? '', + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ), + ), + ), + ); } } diff --git a/lib/components/PlayerScreen/queue_list.dart b/lib/components/PlayerScreen/queue_list.dart index 8074ed02..c4faf03d 100644 --- a/lib/components/PlayerScreen/queue_list.dart +++ b/lib/components/PlayerScreen/queue_list.dart @@ -11,6 +11,7 @@ import 'package:finamp/models/finamp_models.dart'; import 'package:finamp/screens/blurred_player_screen_background.dart'; import 'package:finamp/services/feedback_helper.dart'; import 'package:finamp/services/finamp_settings_helper.dart'; +import 'package:finamp/services/one_line_marquee_helper.dart'; import 'package:finamp/services/theme_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -847,15 +848,24 @@ class _CurrentTrackState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - currentTrack?.item.title ?? - AppLocalizations.of(context)! - .unknownName, - style: const TextStyle( - color: Colors.white, + SizedBox( + height: 20, + child: OneLineMarqueeHelper( + key: ValueKey(currentTrack?.item.id), + text: currentTrack?.item.title ?? + AppLocalizations.of(context)! + .unknownName, + style: TextStyle( fontSize: 16, - fontWeight: FontWeight.w500, - overflow: TextOverflow.ellipsis), + height: 26 / 20, + color: Colors.white, + fontWeight: + Theme.of(context).brightness == + Brightness.light + ? FontWeight.w500 + : FontWeight.w600, + ), + ), ), const SizedBox(height: 4), Row( diff --git a/lib/components/PlayerScreen/song_name.dart b/lib/components/PlayerScreen/song_name.dart index 125dacff..19a49424 100644 --- a/lib/components/PlayerScreen/song_name.dart +++ b/lib/components/PlayerScreen/song_name.dart @@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart'; import 'package:finamp/models/jellyfin_models.dart'; import 'package:finamp/screens/artist_screen.dart'; import 'package:finamp/services/finamp_settings_helper.dart'; +import 'package:finamp/services/scrolling_text_helper.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -130,14 +131,23 @@ class SongNameContent extends StatelessWidget { ), ), const Padding(padding: EdgeInsets.symmetric(vertical: 2)), - Text( - mediaItem == null - ? AppLocalizations.of(context)!.noItem - : mediaItem!.title, - style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600), - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1, + Center( + child: + ScrollingTextHelper( + id: ValueKey(mediaItem!.id), + alignment: TextAlign.center, + text: mediaItem == null + ? AppLocalizations.of(context)!.noItem + : mediaItem!.title, + style: TextStyle( + fontSize: 24, + height: 26 / 20, + fontWeight: + Theme.of(context).brightness == Brightness.light + ? FontWeight.w500 + : FontWeight.w600, + ), + ), ), const Padding(padding: EdgeInsets.symmetric(vertical: 2)), RichText( diff --git a/lib/components/PlayerScreen/song_name_content.dart b/lib/components/PlayerScreen/song_name_content.dart index 1234d648..267c91ae 100644 --- a/lib/components/PlayerScreen/song_name_content.dart +++ b/lib/components/PlayerScreen/song_name_content.dart @@ -1,4 +1,7 @@ -import 'package:balanced_text/balanced_text.dart'; +import 'package:finamp/screens/player_screen.dart'; +import 'package:finamp/services/finamp_settings_helper.dart'; +import 'package:finamp/services/scrolling_text_helper.dart'; +import 'package:flutter/material.dart'; import 'package:finamp/components/AddToPlaylistScreen/add_to_playlist_button.dart'; import 'package:finamp/components/PlayerScreen/player_buttons_more.dart'; import 'package:finamp/models/finamp_models.dart'; @@ -10,7 +13,6 @@ import 'package:get_it/get_it.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../../services/queue_service.dart'; -import 'album_chip.dart'; import 'artist_chip.dart'; class SongNameContent extends StatelessWidget { @@ -27,14 +29,12 @@ class SongNameContent extends StatelessWidget { stream: GetIt.instance().getQueueStream(), builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data!.currentTrack == null) { - // show loading indicator return const Center( child: CircularProgressIndicator(), ); } final currentTrack = snapshot.data!.currentTrack!; - final jellyfin_models.BaseItemDto? songBaseItemDto = currentTrack.baseItem; @@ -49,14 +49,7 @@ class SongNameContent extends StatelessWidget { children: [ Center( child: Container( - alignment: Alignment.center, - constraints: BoxConstraints( - maxHeight: - controller.shouldShow(PlayerHideable.twoLineTitle) - ? 52 - : 24, - maxWidth: 280, - ), + constraints: const BoxConstraints(maxWidth: 280), child: Semantics.fromProperties( properties: SemanticsProperties( label: @@ -64,24 +57,74 @@ class SongNameContent extends StatelessWidget { ), excludeSemantics: true, container: true, - child: BalancedText( - currentTrack.item.title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - height: 26 / 20, - fontWeight: - Theme.of(context).brightness == Brightness.light - ? FontWeight.w500 - : FontWeight.w600, - overflow: TextOverflow.visible, - ), - softWrap: true, - overflow: TextOverflow.ellipsis, - maxLines: - controller.shouldShow(PlayerHideable.twoLineTitle) - ? 2 - : 1, + child: Builder( + builder: (context) { + final text = currentTrack.item.title; + final isTwoLineMode = controller + .shouldShow(PlayerHideable.twoLineTitle); + final isMarqueeEnabled = FinampSettingsHelper + .finampSettings.oneLineMarqueeTextButton; + + final textStyle = TextStyle( + fontSize: 20, + height: 1.2, + fontWeight: + Theme.of(context).brightness == Brightness.light + ? FontWeight.w500 + : FontWeight.w600, + ); + + final textSpan = + TextSpan(text: text, style: textStyle); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + maxLines: 2, + )..layout(maxWidth: 280); + + final wouldOverflow = textPainter.didExceedMaxLines; + + if (!isTwoLineMode) { + return Text( + text, + style: textStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ); + } else { + if (!wouldOverflow) { + return Text( + text, + style: textStyle, + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ); + } else { + if (isMarqueeEnabled) { + return SizedBox( + width: 280, + height: 30, + child: ScrollingTextHelper( + id: ValueKey(currentTrack.item.id), + text: text, + style: textStyle, + alignment: TextAlign.center, + ), + ); + } else { + return Text( + text, + style: textStyle, + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ); + } + } + } + }, ), ), ), @@ -105,13 +148,55 @@ class SongNameContent extends StatelessWidget { ), ], ), - AlbumChip( - item: songBaseItemDto, - backgroundColor: - IconTheme.of(context).color!.withOpacity(0.1), - key: songBaseItemDto?.album == null - ? null - : ValueKey("${songBaseItemDto!.album}-album"), + Center( + child: Builder( + builder: (context) { + final text = currentTrack.item.album ?? ""; + final textStyle = TextStyle( + fontSize: 14, + height: 1.0, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ); + + final textSpan = TextSpan(text: text, style: textStyle); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + maxLines: 1, + )..layout(); + + final contentWidth = textPainter.width + 16.0; + final needsMarquee = contentWidth > 280.0; + final width = needsMarquee ? 280.0 : contentWidth; + + return Container( + width: width, + height: 20.0, + padding: const EdgeInsets.symmetric( + horizontal: 4.0, vertical: 2.0), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .surfaceVariant + .withOpacity(0.5), + borderRadius: BorderRadius.circular(4.0), + ), + child: needsMarquee + ? ScrollingTextHelper( + id: ValueKey('album-${currentTrack.item.id}'), + text: text, + style: textStyle, + alignment: TextAlign.center, + ) + : Text( + text, + style: textStyle, + textAlign: TextAlign.center, + maxLines: 1, + ), + ); + }, + ), ), ], ), diff --git a/lib/components/now_playing_bar.dart b/lib/components/now_playing_bar.dart index a6292f7f..7dee96c4 100644 --- a/lib/components/now_playing_bar.dart +++ b/lib/components/now_playing_bar.dart @@ -7,6 +7,7 @@ import 'package:finamp/components/print_duration.dart'; import 'package:finamp/models/finamp_models.dart'; import 'package:finamp/services/current_track_metadata_provider.dart'; import 'package:finamp/services/feedback_helper.dart'; +import 'package:finamp/services/one_line_marquee_helper.dart'; import 'package:finamp/services/queue_service.dart'; import 'package:finamp/services/theme_provider.dart'; import 'package:flutter/material.dart'; @@ -146,7 +147,8 @@ class NowPlayingBar extends ConsumerWidget { final progressBackgroundColor = getProgressBackgroundColor(context); - return Padding( + return SafeArea( + child: Padding( padding: const EdgeInsets.only(left: 12.0, bottom: 12.0, right: 12.0), child: Semantics.fromProperties( properties: SemanticsProperties( @@ -342,15 +344,25 @@ class NowPlayingBar extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - currentTrack.item.title, - style: const TextStyle( - color: Colors.white, + SizedBox( + height: 20, + child: OneLineMarqueeHelper( + key: ValueKey( + currentTrack.item.id), + text: currentTrack + .item.title, + style: TextStyle( fontSize: 16, - fontWeight: - FontWeight.w500, - overflow: TextOverflow - .ellipsis), + height: 26 / 20, + color: Colors.white, + fontWeight: Theme.of( + context) + .brightness == + Brightness.light + ? FontWeight.w500 + : FontWeight.w600, + ), + ), ), const SizedBox(height: 4), Row( @@ -540,7 +552,7 @@ class NowPlayingBar extends ConsumerWidget { ), ), ), - ); + )); } @override diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4a73305a..55d896aa 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1540,6 +1540,12 @@ "@enableVibration": {}, "enableVibrationSubtitle": "Whether to enable vibration.", "@enableVibrationSubtitle": {}, + "hideQueueButton": "Hide queue button", + "hideQueueButtonSubtitle": "Hide the queue button on the player screen. Swipe up to access the queue.", + "oneLineMarqueeTextButton": "Auto-scroll Long Titles", + "oneLineMarqueeTextButtonSubtitle": "Automatically scroll song titles that are too long to display in two lines", + "marqueeOrTruncateButton": "Use ellipsis for long titles", + "marqueeOrTruncateButtonSubtitle": "Show ... at the end of long titles instead of scrolling text", "hidePlayerBottomActions": "Hide bottom actions", "hidePlayerBottomActionsSubtitle": "Hide the queue and lyrics buttons on the player screen. Swipe up to access the queue, swipe left (below the album cover) to view lyrics if available.", "prioritizePlayerCover": "Prioritize album cover", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 103c90fb..d0db3051 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -290,7 +290,7 @@ "@setSleepTimer": {}, "enterLowPriorityStateOnPause": "Przechodź w stan niskiego priorytetu w trakcie trwania pauzy", "@enterLowPriorityStateOnPause": {}, - "songCount": "{count,plural,=1{{count} Utwór} =2..4{{count} Utwory} other{{count} Utworów}}", + "songCount": "{count,plural,=1{{count} Utwór} few{{count} Utwory} other{{count} Utworów}}", "@songCount": { "placeholders": { "count": { @@ -900,7 +900,7 @@ "@playbackSpeedControlSettingSubtitle": { "description": "Subtitle for the playback speed visibility setting" }, - "albumCount": "{count,plural,=1{{count} Album} =3{{count} Albumy} =4{{count} Albumy} other{{count} Albumów}}", + "albumCount": "{count,plural,=1{{count} Album} =2{{count} Albumów} few{{count} Albumy} many{{count} Albumów} other{{count} Albumów}}", "@albumCount": { "placeholders": { "count": { @@ -1080,7 +1080,7 @@ "@shuffleAlbumsToQueue": { "description": "Label for action that shuffles all albums of an artist or genre and adds them at the end of the regular queue" }, - "playCountValue": "{playCount,plural,=1{{playCount} odtworzenie} =2{{playCount} odtworzenia} =3{{playCount} odtworzenia} =4{{playCount} odtworzenia} other{{playCount} odtworzeń}}", + "playCountValue": "{playCount,plural,=1{{playCount} odtworzenie} few{{playCount} odtworzenia} other{{playCount} odtworzeń}}", "@playCountValue": { "placeholders": { "playCount": { @@ -1469,7 +1469,7 @@ "@confirmShuffleNext": { "description": "A confirmation message that is shown after successfully shuffling a list (album, playlist, etc.) to the front of the \"Next Up\" queue" }, - "queueRestoreSubtitle2": "{count,plural,=1{1 utwór} =2..4{{count} Utwory} other{{count} Utworów}}, {remaining} nieodtworzonych", + "queueRestoreSubtitle2": "{count,plural,=1{1 utwór} few{{count} Utwory} other{{count} Utworów}}, {remaining} nieodtworzonych", "@queueRestoreSubtitle2": { "description": "Description of length of a saved queue", "placeholders": { diff --git a/lib/models/finamp_models.dart b/lib/models/finamp_models.dart index 03b50193..74863bc3 100644 --- a/lib/models/finamp_models.dart +++ b/lib/models/finamp_models.dart @@ -141,6 +141,7 @@ class DefaultSettings { static const downloadWorkers = 5; static const maxConcurrentDownloads = 10; static const downloadSizeWarningCutoff = 150; + static const oneLineMarqueeTextButton = false; } @HiveType(typeId: 28) @@ -248,7 +249,9 @@ class FinampSettings { this.transcodingSegmentContainer = DefaultSettings.transcodingSegmentContainer, this.downloadSizeWarningCutoff = - DefaultSettings.downloadSizeWarningCutoff}); + DefaultSettings.downloadSizeWarningCutoff, + this.oneLineMarqueeTextButton = DefaultSettings.oneLineMarqueeTextButton, + }); @HiveField(0, defaultValue: DefaultSettings.isOffline) bool isOffline; @@ -514,6 +517,10 @@ class FinampSettings { @HiveField(80, defaultValue: DefaultSettings.downloadSizeWarningCutoff) int downloadSizeWarningCutoff; + @HiveField(81, defaultValue: DefaultSettings.oneLineMarqueeTextButton) + bool oneLineMarqueeTextButton; + + static Future create() async { final downloadLocation = await DownloadLocation.create( name: "Internal Storage", diff --git a/lib/models/finamp_models.g.dart b/lib/models/finamp_models.g.dart index 45661daa..b23fd386 100644 --- a/lib/models/finamp_models.g.dart +++ b/lib/models/finamp_models.g.dart @@ -195,6 +195,7 @@ class FinampSettingsAdapter extends TypeAdapter { ? FinampSegmentContainer.fragmentedMp4 : fields[75] as FinampSegmentContainer, downloadSizeWarningCutoff: fields[80] == null ? 150 : fields[80] as int, + oneLineMarqueeTextButton: fields[81] == null ? false : fields[81] as bool, ) ..disableGesture = fields[19] == null ? false : fields[19] as bool ..showFastScroller = fields[25] == null ? true : fields[25] as bool @@ -204,7 +205,7 @@ class FinampSettingsAdapter extends TypeAdapter { @override void write(BinaryWriter writer, FinampSettings obj) { writer - ..writeByte(77) + ..writeByte(78) ..writeByte(0) ..write(obj.isOffline) ..writeByte(1) @@ -358,7 +359,9 @@ class FinampSettingsAdapter extends TypeAdapter { ..writeByte(79) ..write(obj.bufferSizeMegabytes) ..writeByte(80) - ..write(obj.downloadSizeWarningCutoff); + ..write(obj.downloadSizeWarningCutoff) + ..writeByte(81) + ..write(obj.oneLineMarqueeTextButton); } @override diff --git a/lib/screens/customization_settings_screen.dart b/lib/screens/customization_settings_screen.dart index 4dbb08de..d2b37820 100644 --- a/lib/screens/customization_settings_screen.dart +++ b/lib/screens/customization_settings_screen.dart @@ -5,6 +5,7 @@ import 'package:finamp/models/finamp_models.dart'; import 'package:finamp/services/finamp_settings_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive/hive.dart'; class CustomizationSettingsScreen extends StatefulWidget { @@ -34,12 +35,43 @@ class _CustomizationSettingsScreenState const PlaybackSpeedControlVisibilityDropdownListTile(), if (!Platform.isIOS) const ShowStopButtonOnMediaNotificationToggle(), const ShowSeekControlsOnMediaNotificationToggle(), + const OneLineMarqueeTextSwitch(), ], ), ); } } +class OneLineMarqueeTextSwitch extends StatelessWidget { + const OneLineMarqueeTextSwitch({super.key}); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder>( + valueListenable: FinampSettingsHelper.finampSettingsListener, + builder: (context, box, child) { + bool? oneLineMarquee = + box.get("FinampSettings")?.oneLineMarqueeTextButton; + + return SwitchListTile.adaptive( + title: Text(AppLocalizations.of(context)!.oneLineMarqueeTextButton), + subtitle: Text( + AppLocalizations.of(context)!.oneLineMarqueeTextButtonSubtitle), + value: oneLineMarquee ?? false, + onChanged: oneLineMarquee == null + ? null + : (value) { + FinampSettings finampSettingsTemp = + box.get("FinampSettings")!; + finampSettingsTemp.oneLineMarqueeTextButton = value; + box.put("FinampSettings", finampSettingsTemp); + }, + ); + }, + ); + } +} + class ShowStopButtonOnMediaNotificationToggle extends StatelessWidget { const ShowStopButtonOnMediaNotificationToggle({super.key}); diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index 9478ce19..34b95395 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../models/finamp_models.dart'; @@ -334,6 +335,8 @@ class FinampSettingsHelper { DefaultSettings.showStopButtonOnMediaNotification; finampSettingsTemp.showSeekControlsOnMediaNotification = DefaultSettings.showSeekControlsOnMediaNotification; + finampSettingsTemp.oneLineMarqueeTextButton = + DefaultSettings.oneLineMarqueeTextButton; Hive.box("FinampSettings") .put("FinampSettings", finampSettingsTemp); @@ -441,6 +444,8 @@ class FinampSettingsHelper { finampSettingsTemp.downloadWorkers = DefaultSettings.downloadWorkers; finampSettingsTemp.maxConcurrentDownloads = DefaultSettings.maxConcurrentDownloads; + finampSettingsTemp.downloadSizeWarningCutoff = + DefaultSettings.downloadSizeWarningCutoff; Hive.box("FinampSettings") .put("FinampSettings", finampSettingsTemp); diff --git a/lib/services/one_line_marquee_helper.dart b/lib/services/one_line_marquee_helper.dart new file mode 100644 index 00000000..99975732 --- /dev/null +++ b/lib/services/one_line_marquee_helper.dart @@ -0,0 +1,96 @@ +import 'package:finamp/services/finamp_settings_helper.dart'; +import 'package:flutter/material.dart'; +import 'package:marquee/marquee.dart'; +import 'package:balanced_text/balanced_text.dart'; + +class OneLineMarqueeHelper extends StatelessWidget { + final String text; + final TextStyle style; + final Key key; + + const OneLineMarqueeHelper({ + required this.text, + required this.style, + required this.key, + }); + + @override + Widget build(BuildContext context) { + if (!FinampSettingsHelper.finampSettings.oneLineMarqueeTextButton) { + return Text( + text, + style: style, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ); + } + + return LayoutBuilder( + builder: (context, constraints) { + final textPainter = TextPainter( + text: TextSpan( + text: text, + style: style, + ), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(maxWidth: constraints.maxWidth); + + final isOverflowing = textPainter.didExceedMaxLines; + + if (isOverflowing) { + return Container( + alignment: Alignment.centerLeft, + height: style.fontSize ?? 16.0, + width: constraints.maxWidth, + child: Stack( + children: [ + Positioned.fill( + child: Marquee( + key: key, + text: text, + style: style, + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 50.0, + pauseAfterRound: const Duration(seconds: 3), + accelerationDuration: const Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: const Duration(milliseconds: 500), + decelerationCurve: Curves.easeOut, + textDirection: TextDirection.ltr, + ), + ), + Positioned( + left: 0, + child: Container( + width: 20, + color: Theme.of(context).scaffoldBackgroundColor, + ), + ), + Positioned( + right: 0, + child: Container( + width: 20, + color: Theme.of(context).scaffoldBackgroundColor, + ), + ), + ], + ), + ); + } else { + return Container( + width: constraints.maxWidth, + child: BalancedText( + text, + style: style, + overflow: TextOverflow.ellipsis, + maxLines: 2, + textAlign: TextAlign.start, + ), + ); + } + }, + ); + } +} diff --git a/lib/services/scrolling_text_helper.dart b/lib/services/scrolling_text_helper.dart new file mode 100644 index 00000000..cba1fca1 --- /dev/null +++ b/lib/services/scrolling_text_helper.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:marquee/marquee.dart'; + +import 'finamp_settings_helper.dart'; + +class ScrollingTextHelper extends StatelessWidget { + const ScrollingTextHelper({ + super.key, + required this.id, + required this.text, + this.style, + this.alignment = TextAlign.start, + }); + + final Key id; + final String text; + final TextStyle? style; + final TextAlign alignment; + + @override + Widget build(BuildContext context) { + final effectiveStyle = + (style ?? Theme.of(context).textTheme.bodyMedium)?.copyWith( + height: 1.0, // Force single line height + ); + + return SizedBox( + height: 20.0, // Fixed height for single line + child: Marquee( + key: id, + text: text, + style: effectiveStyle, + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.center, + blankSpace: 60.0, + velocity: 40.0, + pauseAfterRound: const Duration(seconds: 1), + startAfter: const Duration(seconds: 1), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 06aff5ea..555ad087 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -74,10 +74,10 @@ packages: dependency: "direct main" description: name: audio_service_mpris - sha256: fdab1ae1f659c6db36d5cc396e46e4ee9663caefa6153f8453fcd01d57567c08 + sha256: b16db3584a4b2464c0bfd575c1a21765723d257931222f8adfcb0511f940d352 url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.1.5" audio_service_platform_interface: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 28d858eb..7b6cefbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: path: libs/windows/media_kit_libs_windows_audio smtc_windows: ^1.0.0 audio_service: ^0.18.15 - audio_service_mpris: ^0.2.0 + audio_service_mpris: ^0.1.5 audio_service_platform_interface: ^0.1.1 audio_session: ^0.1.21 rxdart: ^0.28.0