diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index 0e42ff31f..5fcf2f2e7 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -8,6 +8,7 @@ import 'package:finamp/components/global_snackbar.dart'; import 'package:finamp/models/finamp_models.dart'; import 'package:finamp/models/jellyfin_models.dart' as jellyfin_models; import 'package:finamp/services/finamp_user_helper.dart'; +import 'package:finamp/services/one_line_marquee_helper.dart'; import 'package:finamp/services/queue_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -157,6 +158,7 @@ class _SongListTileState extends ConsumerState ), ), ), + subtitle: Opacity( opacity: playable ? 1.0 : 0.5, child: Text.rich( @@ -281,12 +283,12 @@ class _SongListTileState extends ConsumerState List offlineItems; // If we're on the songs tab, just get all of the downloaded items offlineItems = await downloadService.getAllSongs( - // nameFilter: widget.searchTerm, - viewFilter: finampUserHelper.currentUser?.currentView?.id, - nullableViewFilters: - settings.showDownloadsWithUnknownLibrary, - onlyFavorites: - settings.onlyShowFavourite && settings.trackOfflineFavorites, + // nameFilter: widget.searchTerm, + viewFilter: finampUserHelper.currentUser?.currentView?.id, + nullableViewFilters: + settings.showDownloadsWithUnknownLibrary, + onlyFavorites: settings.onlyShowFavourite && + settings.trackOfflineFavorites, ); var items = offlineItems diff --git a/lib/components/AlbumScreen/song_menu.dart b/lib/components/AlbumScreen/song_menu.dart index faa2c43fe..de65da660 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'; @@ -836,20 +837,18 @@ 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, color: - Theme.of(context).textTheme.bodyMedium?.color ?? - Colors.white, + Theme.of(context).textTheme.bodyMedium?.color ?? + Colors.white, ), - overflow: TextOverflow.ellipsis, - softWrap: true, - maxLines: 2, ), Padding( padding: widget.condensed diff --git a/lib/components/PlayerScreen/queue_list.dart b/lib/components/PlayerScreen/queue_list.dart index 50560c0aa..121139025 100644 --- a/lib/components/PlayerScreen/queue_list.dart +++ b/lib/components/PlayerScreen/queue_list.dart @@ -9,6 +9,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'; @@ -842,15 +843,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/queue_list_item.dart b/lib/components/PlayerScreen/queue_list_item.dart index 8140c065a..280076557 100644 --- a/lib/components/PlayerScreen/queue_list_item.dart +++ b/lib/components/PlayerScreen/queue_list_item.dart @@ -5,6 +5,7 @@ import 'package:finamp/models/finamp_models.dart'; import 'package:finamp/models/jellyfin_models.dart' as jellyfin_models; 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/process_artist.dart'; import 'package:finamp/services/queue_service.dart'; import 'package:flutter/material.dart' hide ReorderableList; @@ -135,11 +136,11 @@ class _QueueListItemState extends State widget.item.item.title, style: widget.isCurrentTrack ? TextStyle( - color: - Theme.of(context).colorScheme.secondary, - fontSize: 16, - fontWeight: FontWeight.w400, - overflow: TextOverflow.ellipsis) + color: + Theme.of(context).colorScheme.secondary, + fontSize: 16, + fontWeight: FontWeight.w400, + overflow: TextOverflow.ellipsis) : null, overflow: TextOverflow.ellipsis, ), diff --git a/lib/components/PlayerScreen/song_name.dart b/lib/components/PlayerScreen/song_name.dart index a81a38cb9..1e2639a34 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 5b97139e8..426af45fd 100644 --- a/lib/components/PlayerScreen/song_name_content.dart +++ b/lib/components/PlayerScreen/song_name_content.dart @@ -1,10 +1,10 @@ -import 'package:balanced_text/balanced_text.dart'; +import 'package:finamp/screens/player_screen.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'; import 'package:finamp/models/jellyfin_models.dart' as jellyfin_models; -import 'package:finamp/screens/player_screen.dart'; -import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import '../../services/queue_service.dart'; @@ -32,7 +32,6 @@ class SongNameContent extends StatelessWidget { } final currentTrack = snapshot.data!.currentTrack!; - final jellyfin_models.BaseItemDto? songBaseItemDto = currentTrack.baseItem; @@ -47,17 +46,13 @@ class SongNameContent extends StatelessWidget { children: [ Center( child: Container( - alignment: Alignment.center, constraints: BoxConstraints( - maxHeight: - controller.shouldShow(PlayerHideable.twoLineTitle) - ? 52 - : 24, maxWidth: 280, ), - child: BalancedText( - currentTrack.item.title, - textAlign: TextAlign.center, + child: ScrollingTextHelper( + id: ValueKey(currentTrack.item.id), + alignment: TextAlign.center, + text: currentTrack.item.title, style: TextStyle( fontSize: 20, height: 26 / 20, @@ -65,14 +60,8 @@ class SongNameContent extends StatelessWidget { 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, + useMarqueeCondition: false, ), ), ), diff --git a/lib/components/now_playing_bar.dart b/lib/components/now_playing_bar.dart index 41572caa7..ab171afd3 100644 --- a/lib/components/now_playing_bar.dart +++ b/lib/components/now_playing_bar.dart @@ -6,6 +6,7 @@ import 'package:finamp/components/AddToPlaylistScreen/add_to_playlist_button.dar 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'; @@ -46,25 +47,20 @@ class NowPlayingBar extends ConsumerWidget { ]); Color getProgressBackgroundColor(BuildContext context) { - return FinampSettingsHelper.finampSettings.showProgressOnNowPlayingBar ? - Color.alphaBlend( - Theme.of(context).brightness == Brightness.dark - ? IconTheme.of(context) - .color! - .withOpacity(0.35) - : IconTheme.of(context) - .color! - .withOpacity(0.5), - Theme.of(context).brightness == - Brightness.dark - ? Colors.black - : Colors.white - ) : - IconTheme.of(context).color!.withOpacity(0.85); + return FinampSettingsHelper.finampSettings.showProgressOnNowPlayingBar + ? Color.alphaBlend( + Theme.of(context).brightness == Brightness.dark + ? IconTheme.of(context).color!.withOpacity(0.35) + : IconTheme.of(context).color!.withOpacity(0.5), + Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.white) + : IconTheme.of(context).color!.withOpacity(0.85); } Widget buildLoadingQueueBar(BuildContext context, Function()? retryCallback) { - final progressBackgroundColor = getProgressBackgroundColor(context).withOpacity(0.5); + final progressBackgroundColor = + getProgressBackgroundColor(context).withOpacity(0.5); return SimpleGestureDetector( onVerticalSwipe: (direction) { @@ -138,7 +134,6 @@ class NowPlayingBar extends ConsumerWidget { Widget buildNowPlayingBar( BuildContext context, FinampQueueItem currentTrack) { - final audioHandler = GetIt.instance(); Duration? playbackPosition; @@ -149,7 +144,7 @@ class NowPlayingBar extends ConsumerWidget { : null; final progressBackgroundColor = getProgressBackgroundColor(context); - + return SafeArea( child: Padding( padding: const EdgeInsets.only(left: 12.0, bottom: 12.0, right: 12.0), @@ -266,7 +261,8 @@ class NowPlayingBar extends ConsumerWidget { Expanded( child: Stack( children: [ - if (FinampSettingsHelper.finampSettings.showProgressOnNowPlayingBar) + if (FinampSettingsHelper.finampSettings + .showProgressOnNowPlayingBar) Positioned( left: 0, top: 0, @@ -279,7 +275,8 @@ class NowPlayingBar extends ConsumerWidget { playbackPosition = snapshot.data; final screenSize = - MediaQuery.of(context).size; + MediaQuery.of(context) + .size; return Container( // rather hacky workaround, using LayoutBuilder would be nice but I couldn't get it to work... width: max( @@ -298,17 +295,20 @@ class NowPlayingBar extends ConsumerWidget { .inMilliseconds)), height: albumImageSize, decoration: ShapeDecoration( - color: IconTheme.of(context) - .color! - .withOpacity(0.75), + color: IconTheme.of( + context) + .color! + .withOpacity(0.75), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: - Radius.circular(12), + Radius.circular( + 12), bottomRight: - Radius.circular(12), + Radius.circular( + 12), ), ), ), @@ -335,15 +335,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( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 86bac07de..699e508e9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1466,6 +1466,10 @@ "@enableVibrationSubtitle": {}, "hideQueueButton": "Hide queue button", "hideQueueButtonSubtitle": "Hide the queue button on the player screen. Swipe up to access the queue.", + "oneLineMarqueeTextButton": "Allow one line break before using scrolling text", + "oneLineMarqueeTextButtonSubtitle": "Sets song title to one line marquee text in player screen. Default 2", + "marqueeOrTruncateButton": "Use marquee effect or truncate by ellipses", + "marqueeOrTruncateButtonSubtitle": "If enabled, long track title overflow will be handled by ellipses (...)", "prioritizePlayerCover": "Prioritize album cover", "prioritizePlayerCoverSubtitle": "Prioritize showing a larger album cover on player screen. Non-critical controls will be hidden more aggressively at small screen sizes.", "suppressPlayerPadding": "Suppress player controls padding", diff --git a/lib/models/finamp_models.dart b/lib/models/finamp_models.dart index 590284de9..758e71e0a 100644 --- a/lib/models/finamp_models.dart +++ b/lib/models/finamp_models.dart @@ -99,6 +99,8 @@ const _enableVibration = true; const _prioritizeCoverFactor = 8.0; const _suppressPlayerPadding = false; const _hideQueueButton = false; +const _oneLineMarqueeTextButton = false; +const _marqueeOrTruncateButton = false; const _reportQueueToServerDefault = false; const _periodicPlaybackSessionUpdateFrequencySecondsDefault = 150; const _showArtistChipImage = true; @@ -171,6 +173,8 @@ class FinampSettings { this.prioritizeCoverFactor = _prioritizeCoverFactor, this.suppressPlayerPadding = _suppressPlayerPadding, this.hideQueueButton = _hideQueueButton, + this.oneLineMarqueeTextButton = _oneLineMarqueeTextButton, + this.marqueeOrTruncateButton = _marqueeOrTruncateButton, this.reportQueueToServer = _reportQueueToServerDefault, this.periodicPlaybackSessionUpdateFrequencySeconds = _periodicPlaybackSessionUpdateFrequencySecondsDefault, @@ -392,6 +396,12 @@ class FinampSettings { @HiveField(65, defaultValue: _startInstantMixForIndividualTracksDefault) bool startInstantMixForIndividualTracks; + @HiveField(67, defaultValue: _oneLineMarqueeTextButton) + bool oneLineMarqueeTextButton; + + @HiveField(68, defaultValue: _oneLineMarqueeTextButton) + bool marqueeOrTruncateButton; + 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 116e8c6dc..e2adf2909 100644 --- a/lib/models/finamp_models.g.dart +++ b/lib/models/finamp_models.g.dart @@ -158,6 +158,7 @@ class FinampSettingsAdapter extends TypeAdapter { prioritizeCoverFactor: fields[49] == null ? 8.0 : fields[49] as double, suppressPlayerPadding: fields[50] == null ? false : fields[50] as bool, hideQueueButton: fields[51] == null ? false : fields[51] as bool, + oneLineMarqueeTextButton: fields[67] == null ? false : fields[67] as bool, reportQueueToServer: fields[52] == null ? false : fields[52] as bool, periodicPlaybackSessionUpdateFrequencySeconds: fields[53] == null ? 150 : fields[53] as int, @@ -176,7 +177,7 @@ class FinampSettingsAdapter extends TypeAdapter { @override void write(BinaryWriter writer, FinampSettings obj) { writer - ..writeByte(64) + ..writeByte(65) ..writeByte(0) ..write(obj.isOffline) ..writeByte(1) @@ -304,7 +305,9 @@ class FinampSettingsAdapter extends TypeAdapter { ..writeByte(64) ..write(obj.showProgressOnNowPlayingBar) ..writeByte(65) - ..write(obj.startInstantMixForIndividualTracks); + ..write(obj.startInstantMixForIndividualTracks) + ..writeByte(67) + ..write(obj.oneLineMarqueeTextButton); } @override diff --git a/lib/screens/customization_settings_screen.dart b/lib/screens/customization_settings_screen.dart index 2e344203b..d02b240e1 100644 --- a/lib/screens/customization_settings_screen.dart +++ b/lib/screens/customization_settings_screen.dart @@ -1,7 +1,9 @@ import 'package:finamp/components/LayoutSettingsScreen/CustomizationSettingsScreen/playback_speed_control_visibility_dropdown_list_tile.dart'; +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'; class CustomizationSettingsScreen extends StatefulWidget { const CustomizationSettingsScreen({Key? key}) : super(key: key); @@ -35,8 +37,70 @@ class _CustomizationSettingsScreenState body: ListView( children: const [ PlaybackSpeedControlVisibilityDropdownListTile(), + OneLineMarqueeTextSwitch(), + MarqueeOrTruncate(), ], ), ); } } + +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 MarqueeOrTruncate extends StatelessWidget { + const MarqueeOrTruncate({super.key}); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder>( + valueListenable: FinampSettingsHelper.finampSettingsListener, + builder: (context, box, child) { + bool? oneLineMarquee = + box.get("FinampSettings")?.marqueeOrTruncateButton; + + return SwitchListTile.adaptive( + title: Text(AppLocalizations.of(context)!.marqueeOrTruncateButton), + subtitle: Text( + AppLocalizations.of(context)!.marqueeOrTruncateButtonSubtitle), + value: oneLineMarquee ?? false, + onChanged: oneLineMarquee == null + ? null + : (value) { + FinampSettings finampSettingsTemp = + box.get("FinampSettings")!; + finampSettingsTemp.marqueeOrTruncateButton = value; + box.put("FinampSettings", finampSettingsTemp); + }, + ); + }, + ); + } +} diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index a452c2ffc..c2920484b 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -2,7 +2,7 @@ import 'package:flutter/foundation.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 '../models/finamp_models.dart'; import '../models/jellyfin_models.dart'; diff --git a/lib/services/one_line_marquee_helper.dart b/lib/services/one_line_marquee_helper.dart new file mode 100644 index 000000000..1eb22e566 --- /dev/null +++ b/lib/services/one_line_marquee_helper.dart @@ -0,0 +1,89 @@ +// Use this helper in places that don't require +// two lines marquee + +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) { + 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 000000000..49e963e22 --- /dev/null +++ b/lib/services/scrolling_text_helper.dart @@ -0,0 +1,84 @@ +import 'package:balanced_text/balanced_text.dart'; +import 'package:flutter/material.dart'; +import 'package:marquee/marquee.dart'; +import 'package:hive/hive.dart'; + +import '../models/finamp_models.dart'; +import 'finamp_settings_helper.dart'; + +class ScrollingTextHelper extends StatelessWidget { + final Key id; + final String text; + final TextStyle? style; + final TextAlign? alignment; + final bool useMarqueeCondition; + + const ScrollingTextHelper({ + Key? key, + required this.id, + required this.text, + this.style, + required this.alignment, + this.useMarqueeCondition = true, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder>( + valueListenable: FinampSettingsHelper.finampSettingsListener, + builder: (context, box, child) { + bool oneLineMarquee = + box.get("FinampSettings")?.oneLineMarqueeTextButton ?? false; + + return LayoutBuilder( + builder: (context, constraints) { + final textPainter = TextPainter( + text: TextSpan( + text: text, + style: style, + ), + textDirection: TextDirection.ltr, + )..layout(maxWidth: constraints.maxWidth); + + final lineHeight = textPainter.preferredLineHeight; + final textHeight = textPainter.size.height; + final lineCount = (textHeight / lineHeight).ceil(); + + if (oneLineMarquee && lineCount > 1 || lineCount > 2) { + return Container( + alignment: Alignment.centerLeft, + height: lineHeight, + width: constraints.maxWidth, + child: Marquee( + key: id, + 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, + ), + ); + } else { + return Container( + width: constraints.maxWidth, + child: BalancedText( + text, + style: style, + overflow: TextOverflow.ellipsis, + maxLines: oneLineMarquee ? 1 : 2, + textAlign: alignment, + ), + ); + } + }, + ); + }, + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index b00f6fc36..bc143594e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,6 +101,10 @@ dependencies: window_manager: ^0.3.8 url_launcher: ^6.2.6 +# Fix for marquee dependency after Flutter 3.2.2 upgrade +dependency_overrides: + fading_edge_scrollview: ^4.1.1 + dev_dependencies: flutter_test: sdk: flutter