fix album art skipping song
new lyrics ui + improvements
This commit is contained in:
parent
aaacd150a5
commit
070ab2d6f2
|
|
@ -178,6 +178,9 @@ class Settings {
|
||||||
@HiveField(50, defaultValue: false)
|
@HiveField(50, defaultValue: false)
|
||||||
bool useColorTrayIcon = false;
|
bool useColorTrayIcon = false;
|
||||||
|
|
||||||
|
@HiveField(51, defaultValue: 1)
|
||||||
|
int lyricsStyle = 1;
|
||||||
|
|
||||||
static LazyBox<Settings>? __box;
|
static LazyBox<Settings>? __box;
|
||||||
static Future<LazyBox<Settings>> get _box async =>
|
static Future<LazyBox<Settings>> get _box async =>
|
||||||
__box ??= await Hive.openLazyBox<Settings>('settings');
|
__box ??= await Hive.openLazyBox<Settings>('settings');
|
||||||
|
|
|
||||||
|
|
@ -13,30 +13,34 @@ import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
|
import 'package:freezer/ui/lyrics_styles/classic.dart';
|
||||||
|
import 'package:freezer/ui/lyrics_styles/lyrics_style.dart';
|
||||||
import 'package:freezer/ui/player_bar.dart';
|
import 'package:freezer/ui/player_bar.dart';
|
||||||
import 'package:freezer/ui/player_screen.dart';
|
import 'package:freezer/ui/player_screen.dart';
|
||||||
import 'package:mini_music_visualizer/mini_music_visualizer.dart';
|
import 'package:mini_music_visualizer/mini_music_visualizer.dart';
|
||||||
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
|
|
||||||
class LyricsScreen extends StatelessWidget {
|
class LyricsScreen extends StatelessWidget {
|
||||||
const LyricsScreen({super.key});
|
const LyricsScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PlayerScreenBackground(
|
return PlayerArtColorScheme(
|
||||||
enabled: settings.playerBackgroundOnLyrics,
|
child: PlayerScreenBackground(
|
||||||
appBar: AppBar(
|
enabled: settings.playerBackgroundOnLyrics,
|
||||||
systemOverlayStyle: PlayerScreenBackground.getSystemUiOverlayStyle(
|
appBar: AppBar(
|
||||||
context,
|
systemOverlayStyle:
|
||||||
enabled: settings.playerBackgroundOnLyrics),
|
PlayerScreenBackground.getSystemUiOverlayStyle(context,
|
||||||
forceMaterialTransparency: true,
|
enabled: settings.playerBackgroundOnLyrics),
|
||||||
),
|
forceMaterialTransparency: true,
|
||||||
child: const Column(
|
),
|
||||||
children: [
|
child: const Column(
|
||||||
Expanded(child: LyricsWidget()),
|
children: [
|
||||||
Divider(height: 1.0, thickness: 1.0),
|
Expanded(child: LyricsWidget()),
|
||||||
PlayerBar(backgroundColor: Colors.transparent),
|
Divider(height: 1.0, thickness: 1.0),
|
||||||
],
|
PlayerBar(backgroundColor: Colors.transparent),
|
||||||
));
|
],
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,10 +59,12 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
||||||
Duration _nextOffset = Duration.zero;
|
Duration _nextOffset = Duration.zero;
|
||||||
Duration _currentOffset = Duration.zero;
|
Duration _currentOffset = Duration.zero;
|
||||||
String? _currentTrackId;
|
String? _currentTrackId;
|
||||||
final ScrollController _controller = ScrollController();
|
final _controller = AutoScrollController(
|
||||||
static const double height = 110.0;
|
suggestedRowHeight: height,
|
||||||
|
axis: Axis.vertical,
|
||||||
|
);
|
||||||
|
static const double height = 82.0;
|
||||||
static const double additionalTranslationHeight = 40.0;
|
static const double additionalTranslationHeight = 40.0;
|
||||||
BoxConstraints? _widgetConstraints;
|
|
||||||
Lyrics? _lyrics;
|
Lyrics? _lyrics;
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
CancelToken? _lyricsCancelToken;
|
CancelToken? _lyricsCancelToken;
|
||||||
|
|
@ -71,9 +77,6 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
||||||
bool _showTranslation = false;
|
bool _showTranslation = false;
|
||||||
bool _availableTranslation = false;
|
bool _availableTranslation = false;
|
||||||
|
|
||||||
// each individual lyric widget's height, either computed or cached
|
|
||||||
final _lyricHeights = HashMap<int, double>();
|
|
||||||
|
|
||||||
Future<void> _loadForId(String trackId) async {
|
Future<void> _loadForId(String trackId) async {
|
||||||
if (_currentTrackId == trackId) return;
|
if (_currentTrackId == trackId) return;
|
||||||
_currentTrackId = trackId;
|
_currentTrackId = trackId;
|
||||||
|
|
@ -133,20 +136,12 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
||||||
|
|
||||||
void _scrollToLyric() {
|
void _scrollToLyric() {
|
||||||
if (!_controller.hasClients) return;
|
if (!_controller.hasClients) return;
|
||||||
//Lyric height, screen height, appbar height
|
|
||||||
final actualHeight =
|
|
||||||
height + (_showTranslation ? additionalTranslationHeight : 0.0);
|
|
||||||
// _currentIndex + 1 because there's also the initial one (though we need to sum half)
|
|
||||||
var scrollTo = actualHeight * (_currentIndex + 1);
|
|
||||||
|
|
||||||
if (scrollTo < 0.0) scrollTo = 0.0;
|
|
||||||
if (scrollTo > _controller.position.maxScrollExtent) {
|
|
||||||
scrollTo = _controller.position.maxScrollExtent;
|
|
||||||
}
|
|
||||||
_animatedScroll = true;
|
_animatedScroll = true;
|
||||||
_controller
|
_controller
|
||||||
.animateTo(scrollTo,
|
.scrollToIndex(_currentIndex + 1,
|
||||||
duration: const Duration(milliseconds: 250), curve: Curves.ease)
|
duration: const Duration(milliseconds: 250),
|
||||||
|
preferPosition: AutoScrollPosition.middle)
|
||||||
.then((_) => _animatedScroll = false);
|
.then((_) => _animatedScroll = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,14 +231,71 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
||||||
return ScrollConfiguration.of(context).copyWith(scrollbars: false);
|
return ScrollConfiguration.of(context).copyWith(scrollbars: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color get activeTextColor => Theme.of(context).colorScheme.onBackground;
|
||||||
|
Color get inactiveTextColor => activeTextColor.withOpacity(0.25);
|
||||||
|
|
||||||
|
Widget _buildLyricWidget(int i, LyricsStyle lyricsStyle) {
|
||||||
|
final isActive = _currentIndex == --i;
|
||||||
|
final textColor =
|
||||||
|
isActive || _freeScroll ? activeTextColor : inactiveTextColor;
|
||||||
|
if (i == -1) {
|
||||||
|
return SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 8.0 * 3 + 6.0,
|
||||||
|
child: StreamBuilder<bool>(
|
||||||
|
initialData: playerHelper.playing.valueOrNull,
|
||||||
|
stream: playerHelper.playing,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return MiniMusicVisualizer(
|
||||||
|
color: textColor,
|
||||||
|
width: 8.0,
|
||||||
|
height: 16.0,
|
||||||
|
animate: (snapshot.data ?? false) && isActive,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: lyricsStyle.getBoxDecoration(isActive),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
onTap: _syncedLyrics && _lyrics!.id != null
|
||||||
|
? () => audioHandler.seek(_lyrics!.lyrics![i].offset!)
|
||||||
|
: null,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 28.0,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(_lyrics!.lyrics![i].text!,
|
||||||
|
textAlign: !_syncedLyrics
|
||||||
|
? TextAlign.start
|
||||||
|
: lyricsStyle.getTextAlignment(),
|
||||||
|
style: lyricsStyle.getTextStyle(
|
||||||
|
isActive, textColor, _syncedLyrics ? 26.0 : 20.0)),
|
||||||
|
if (_showTranslation)
|
||||||
|
Text(_lyrics!.lyrics![i].translated!,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color.lerp(
|
||||||
|
Theme.of(context).colorScheme.onBackground,
|
||||||
|
Colors.black,
|
||||||
|
0.12),
|
||||||
|
fontSize: 20.0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textColor =
|
final lyricsStyle = LyricsStyle.fromIndex(settings.lyricsStyle);
|
||||||
settings.playerBackgroundOnLyrics && settings.blurPlayerBackground
|
|
||||||
? Theme.of(context).brightness == Brightness.light
|
|
||||||
? Colors.black87
|
|
||||||
: Colors.white70
|
|
||||||
: Theme.of(context).colorScheme.onBackground;
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
_error != null
|
_error != null
|
||||||
|
|
@ -253,17 +305,17 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
||||||
_loading
|
_loading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: LayoutBuilder(builder: (context, constraints) {
|
: LayoutBuilder(builder: (context, constraints) {
|
||||||
_widgetConstraints = constraints;
|
|
||||||
return NotificationListener<ScrollStartNotification>(
|
return NotificationListener<ScrollStartNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
if (!_syncedLyrics) return false;
|
if (!_syncedLyrics) return false;
|
||||||
final extentDiff =
|
final extentDelta =
|
||||||
(notification.metrics.extentBefore -
|
(notification.metrics.extentBefore -
|
||||||
notification.metrics.extentAfter)
|
notification.metrics.extentAfter)
|
||||||
.abs();
|
.abs();
|
||||||
// avoid accidental clicks
|
// avoid accidental clicks
|
||||||
const extentThreshold = 10.0;
|
const extentThreshold = 9000.0;
|
||||||
if (extentDiff >= extentThreshold &&
|
print('delta: $extentDelta');
|
||||||
|
if (extentDelta >= extentThreshold &&
|
||||||
!_animatedScroll &&
|
!_animatedScroll &&
|
||||||
!_loading &&
|
!_loading &&
|
||||||
!_freeScroll) {
|
!_freeScroll) {
|
||||||
|
|
@ -282,98 +334,14 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
||||||
vertical: constraints.maxHeight / 2 -
|
vertical: constraints.maxHeight / 2 -
|
||||||
height / 2),
|
height / 2),
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
itemExtent: !_syncedLyrics
|
|
||||||
? null
|
|
||||||
: height +
|
|
||||||
(_showTranslation
|
|
||||||
? additionalTranslationHeight
|
|
||||||
: 0.0),
|
|
||||||
itemCount: _lyrics!.lyrics!.length + 1,
|
itemCount: _lyrics!.lyrics!.length + 1,
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
if (i-- == 0) {
|
return AutoScrollTag(
|
||||||
return SizedBox(
|
key: ValueKey(i),
|
||||||
height: height,
|
controller: _controller,
|
||||||
child: Center(
|
index: i,
|
||||||
child: SizedBox(
|
child:
|
||||||
width: 8.0 * 3 + 6.0,
|
_buildLyricWidget(i, lyricsStyle));
|
||||||
child: StreamBuilder<bool>(
|
|
||||||
initialData: playerHelper
|
|
||||||
.playing.valueOrNull,
|
|
||||||
stream: playerHelper.playing,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
return MiniMusicVisualizer(
|
|
||||||
color: textColor,
|
|
||||||
width: 8.0,
|
|
||||||
height: 16.0,
|
|
||||||
animate: (snapshot.data ??
|
|
||||||
false) &&
|
|
||||||
_currentIndex == -1,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
return DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(8.0),
|
|
||||||
color: _currentIndex == i
|
|
||||||
? Colors.grey.withOpacity(0.25)
|
|
||||||
: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(12.0),
|
|
||||||
onTap: _syncedLyrics &&
|
|
||||||
_lyrics!.id != null
|
|
||||||
? () => audioHandler.seek(
|
|
||||||
_lyrics!.lyrics![i].offset!)
|
|
||||||
: null,
|
|
||||||
child: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4.0,
|
|
||||||
//vertical: 24.0,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
_lyrics!.lyrics![i].text!,
|
|
||||||
textAlign: _syncedLyrics
|
|
||||||
? TextAlign.center
|
|
||||||
: TextAlign.start,
|
|
||||||
style: TextStyle(
|
|
||||||
color: textColor,
|
|
||||||
fontSize: _syncedLyrics
|
|
||||||
? 26.0
|
|
||||||
: 20.0,
|
|
||||||
fontWeight:
|
|
||||||
(_currentIndex == i)
|
|
||||||
? FontWeight
|
|
||||||
.bold
|
|
||||||
: FontWeight
|
|
||||||
.normal),
|
|
||||||
),
|
|
||||||
if (_showTranslation)
|
|
||||||
Text(
|
|
||||||
_lyrics!.lyrics![i]
|
|
||||||
.translated!,
|
|
||||||
textAlign:
|
|
||||||
TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color.lerp(
|
|
||||||
Theme.of(
|
|
||||||
context)
|
|
||||||
.colorScheme
|
|
||||||
.onBackground,
|
|
||||||
Colors.black,
|
|
||||||
0.12),
|
|
||||||
fontSize: 20.0)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)));
|
|
||||||
},
|
},
|
||||||
))));
|
))));
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
17
lib/ui/lyrics_styles/classic.dart
Normal file
17
lib/ui/lyrics_styles/classic.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezer/ui/lyrics_styles/lyrics_style.dart';
|
||||||
|
|
||||||
|
class ClassicLyricsStyle implements LyricsStyle {
|
||||||
|
@override
|
||||||
|
TextAlign getTextAlignment() => TextAlign.center;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle getTextStyle(bool isActive, Color textColor, double textSize) =>
|
||||||
|
TextStyle(
|
||||||
|
fontWeight: FontWeight.bold, color: textColor, fontSize: textSize);
|
||||||
|
|
||||||
|
@override
|
||||||
|
BoxDecoration getBoxDecoration(bool isActive) => BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
|
||||||
|
color: isActive ? Colors.grey.withOpacity(0.25) : Colors.transparent);
|
||||||
|
}
|
||||||
27
lib/ui/lyrics_styles/lyrics_style.dart
Normal file
27
lib/ui/lyrics_styles/lyrics_style.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezer/ui/lyrics_styles/classic.dart';
|
||||||
|
import 'package:freezer/ui/lyrics_styles/modern.dart';
|
||||||
|
|
||||||
|
class LyricsStyle {
|
||||||
|
static const classic = 0;
|
||||||
|
static const modern = 1;
|
||||||
|
|
||||||
|
LyricsStyle._();
|
||||||
|
|
||||||
|
factory LyricsStyle.fromIndex(int i) {
|
||||||
|
return switch (i) {
|
||||||
|
classic => ClassicLyricsStyle(),
|
||||||
|
modern => ModernLyricsStyle(),
|
||||||
|
_ => LyricsStyle._(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAlign getTextAlignment() =>
|
||||||
|
throw Exception('getTextAlignment() has not been implemented');
|
||||||
|
|
||||||
|
TextStyle getTextStyle(bool isActive, Color textColor, double textSize) =>
|
||||||
|
throw Exception('getTextStyle() has not been implemented');
|
||||||
|
|
||||||
|
BoxDecoration getBoxDecoration(bool isActive) =>
|
||||||
|
throw Exception('getBoxDecoration() has not been implemented');
|
||||||
|
}
|
||||||
15
lib/ui/lyrics_styles/modern.dart
Normal file
15
lib/ui/lyrics_styles/modern.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezer/ui/lyrics_styles/lyrics_style.dart';
|
||||||
|
|
||||||
|
class ModernLyricsStyle implements LyricsStyle {
|
||||||
|
@override
|
||||||
|
TextAlign getTextAlignment() => TextAlign.start;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle getTextStyle(bool isActive, Color textColor, double textSize) =>
|
||||||
|
TextStyle(
|
||||||
|
fontWeight: FontWeight.bold, color: textColor, fontSize: textSize);
|
||||||
|
|
||||||
|
@override
|
||||||
|
BoxDecoration getBoxDecoration(bool isActive) => const BoxDecoration();
|
||||||
|
}
|
||||||
|
|
@ -197,7 +197,7 @@ class PlayerScreenBackground extends StatelessWidget {
|
||||||
if (provider.dominantColor != null)
|
if (provider.dominantColor != null)
|
||||||
Color.lerp(provider.dominantColor,
|
Color.lerp(provider.dominantColor,
|
||||||
isLightMode ? Colors.white : Colors.black, 0.5)!,
|
isLightMode ? Colors.white : Colors.black, 0.5)!,
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
Theme.of(context).colorScheme.background,
|
||||||
],
|
],
|
||||||
stops: const [0.0, 0.6],
|
stops: const [0.0, 0.6],
|
||||||
)),
|
)),
|
||||||
|
|
@ -886,6 +886,8 @@ class BigAlbumArt extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
||||||
|
static final _logger = Logger('_BigAlbumArtState');
|
||||||
|
|
||||||
final _pageController = PageController(
|
final _pageController = PageController(
|
||||||
initialPage: playerHelper.queueIndex,
|
initialPage: playerHelper.queueIndex,
|
||||||
keepPage: false,
|
keepPage: false,
|
||||||
|
|
@ -893,13 +895,13 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
||||||
);
|
);
|
||||||
StreamSubscription? _currentItemSub;
|
StreamSubscription? _currentItemSub;
|
||||||
|
|
||||||
/// is true on pointer down event
|
/// Always true, except when [PageController.animateToPage] is called programmatically
|
||||||
/// used to distinguish between [PageController.animateToPage] and user gesture
|
///
|
||||||
|
/// Gets reset to true when [onPageChanged] is called again
|
||||||
bool _userScroll = true;
|
bool _userScroll = true;
|
||||||
|
|
||||||
/// whether the user has already scrolled the [PageView],
|
/// true when the [PageController.animateToPage] [Future] hasn't completed yet.
|
||||||
/// so to avoid calling [PageController.animateToPage] again.
|
bool _isAnimationRunning = false;
|
||||||
bool _initiatedByUser = false;
|
|
||||||
|
|
||||||
void _listenForMediaItemChanges() {
|
void _listenForMediaItemChanges() {
|
||||||
if (_currentItemSub != null) return;
|
if (_currentItemSub != null) return;
|
||||||
|
|
@ -909,21 +911,32 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentItemSub = audioHandler.mediaItem.listen((event) async {
|
_currentItemSub = audioHandler.mediaItem.listen((event) async {
|
||||||
if (_initiatedByUser) {
|
|
||||||
_initiatedByUser = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!_pageController.hasClients) return;
|
if (!_pageController.hasClients) return;
|
||||||
if (_pageController.page?.toInt() == playerHelper.queueIndex) return;
|
if (_pageController.page?.toInt() == playerHelper.queueIndex) return;
|
||||||
print('animating controller to page');
|
|
||||||
|
|
||||||
|
if (((_pageController.page?.toInt() ?? 0) - playerHelper.queueIndex)
|
||||||
|
.abs() >
|
||||||
|
1) {
|
||||||
|
_userScroll = false;
|
||||||
|
_logger.fine('jumping to page (difference > 1)');
|
||||||
|
_pageController.jumpToPage(playerHelper.queueIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.fine('animating controller to page');
|
||||||
_userScroll = false;
|
_userScroll = false;
|
||||||
|
_isAnimationRunning = true;
|
||||||
await _pageController.animateToPage(playerHelper.queueIndex,
|
await _pageController.animateToPage(playerHelper.queueIndex,
|
||||||
duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
|
duration: const Duration(milliseconds: 300), curve: Curves.ease);
|
||||||
_userScroll = true;
|
_isAnimationRunning = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _cancelMediaItemSubscription() {
|
||||||
|
_currentItemSub?.cancel();
|
||||||
|
_currentItemSub = null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_listenForMediaItemChanges();
|
_listenForMediaItemChanges();
|
||||||
|
|
@ -934,7 +947,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case AppLifecycleState.paused:
|
case AppLifecycleState.paused:
|
||||||
_currentItemSub?.cancel();
|
_cancelMediaItemSubscription();
|
||||||
case AppLifecycleState.resumed:
|
case AppLifecycleState.resumed:
|
||||||
_listenForMediaItemChanges();
|
_listenForMediaItemChanges();
|
||||||
default:
|
default:
|
||||||
|
|
@ -971,6 +984,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
FadePageRoute(
|
FadePageRoute(
|
||||||
|
blur: true,
|
||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
opaque: false,
|
opaque: false,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
|
|
@ -994,9 +1008,13 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
||||||
return PageView.builder(
|
return PageView.builder(
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
onPageChanged: (int index) {
|
onPageChanged: (int index) {
|
||||||
|
_logger.finest('onPageChanged()');
|
||||||
// ignore if not initiated by user.
|
// ignore if not initiated by user.
|
||||||
if (!_userScroll) return;
|
if (_isAnimationRunning || !_userScroll) {
|
||||||
Logger('BigAlbumArt')
|
_userScroll = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger
|
||||||
.fine('page changed, skipping to media item');
|
.fine('page changed, skipping to media item');
|
||||||
if (queue[index].id ==
|
if (queue[index].id ==
|
||||||
audioHandler.mediaItem.value?.id) {
|
audioHandler.mediaItem.value?.id) {
|
||||||
|
|
@ -1020,6 +1038,16 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}),
|
}),
|
||||||
|
if (kDebugMode) ...[
|
||||||
|
TextButton(
|
||||||
|
onPressed: _cancelMediaItemSubscription,
|
||||||
|
child: const Text('Unsubscribe')),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: _listenForMediaItemChanges,
|
||||||
|
child: const Text('Subscribe'))),
|
||||||
|
],
|
||||||
if (widget.showLyricsButton)
|
if (widget.showLyricsButton)
|
||||||
StreamBuilder<MediaItem?>(
|
StreamBuilder<MediaItem?>(
|
||||||
initialData: audioHandler.mediaItem.valueOrNull,
|
initialData: audioHandler.mediaItem.valueOrNull,
|
||||||
|
|
@ -1171,7 +1199,6 @@ class PlayerScreenTopRow extends StatelessWidget {
|
||||||
.copyWith(fontSize: textSize ?? 38.sp)));
|
.copyWith(fontSize: textSize ?? 38.sp)));
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
PlayerMenuButtonDesktop(size: size),
|
|
||||||
desktopMode
|
desktopMode
|
||||||
? PlayerMenuButtonDesktop(size: size)
|
? PlayerMenuButtonDesktop(size: size)
|
||||||
: PlayerMenuButton(size: size)
|
: PlayerMenuButton(size: size)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import 'package:freezer/api/player/systray.dart';
|
||||||
import 'package:freezer/icons.dart';
|
import 'package:freezer/icons.dart';
|
||||||
import 'package:freezer/ui/login_on_other_device.dart';
|
import 'package:freezer/ui/login_on_other_device.dart';
|
||||||
import 'package:freezer/ui/login_screen.dart';
|
import 'package:freezer/ui/login_screen.dart';
|
||||||
|
import 'package:freezer/ui/lyrics_styles/lyrics_style.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:scrobblenaut/scrobblenaut.dart';
|
import 'package:scrobblenaut/scrobblenaut.dart';
|
||||||
|
|
||||||
|
|
@ -292,6 +293,34 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
settings.save();
|
settings.save();
|
||||||
}
|
}
|
||||||
: null),
|
: null),
|
||||||
|
ListTile(
|
||||||
|
title: Text('Lyrics style'.i18n),
|
||||||
|
subtitle: Text(
|
||||||
|
'Style of the transition between screens within the app'.i18n),
|
||||||
|
leading: const Icon(Icons.auto_awesome_motion),
|
||||||
|
onTap: () => showDialog<int>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return SimpleDialog(
|
||||||
|
title: Text('Select lyrics style'.i18n),
|
||||||
|
children: <Widget>[
|
||||||
|
SimpleDialogOption(
|
||||||
|
child: const Text('Modern (default)'),
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.pop(context, LyricsStyle.modern),
|
||||||
|
),
|
||||||
|
SimpleDialogOption(
|
||||||
|
child: const Text('Classic'),
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.pop(context, LyricsStyle.classic),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}).then((value) {
|
||||||
|
if (value == null) return;
|
||||||
|
settings.lyricsStyle = value;
|
||||||
|
settings.save();
|
||||||
|
}),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Screens style'),
|
title: const Text('Screens style'),
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
|
|
|
||||||
|
|
@ -1305,6 +1305,14 @@ packages:
|
||||||
url: "https://github.com/Pato05/Scrobblenaut.git"
|
url: "https://github.com/Pato05/Scrobblenaut.git"
|
||||||
source: git
|
source: git
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
scroll_to_index:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: scroll_to_index
|
||||||
|
sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ dependencies:
|
||||||
mini_music_visualizer:
|
mini_music_visualizer:
|
||||||
git: https://github.com/Pato05/mini_music_visualizer.git
|
git: https://github.com/Pato05/mini_music_visualizer.git
|
||||||
fading_edge_scrollview: ^3.0.0
|
fading_edge_scrollview: ^3.0.0
|
||||||
|
scroll_to_index: ^3.0.1
|
||||||
#deezcryptor:
|
#deezcryptor:
|
||||||
#path: deezcryptor/
|
#path: deezcryptor/
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue