improve player screen with blurred album art
ui improvements in lyrics screen animated bars when track is playing fix back button when player screen is open instantly pop when track is changed in queue list
This commit is contained in:
parent
8ea6bcd073
commit
4b5d0bd09c
|
@ -1,51 +0,0 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedBars extends StatefulWidget {
|
||||
final double size;
|
||||
final Color? color;
|
||||
const AnimatedBars({
|
||||
super.key,
|
||||
this.size = 24.0,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedBars> createState() => _AnimatedBarsState();
|
||||
}
|
||||
|
||||
class _AnimatedBarsState extends State<AnimatedBars>
|
||||
with TickerProviderStateMixin {
|
||||
late final _controller = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 1000))
|
||||
..repeat(reverse: true);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = widget.color ?? Theme.of(context).colorScheme.onSurface;
|
||||
const count = 3;
|
||||
AnimatedIcons.search_ellipsis;
|
||||
return SizedBox.square(
|
||||
dimension: widget.size,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(
|
||||
count,
|
||||
(index) => SizedBox(
|
||||
width: widget.size / count,
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(color: color),
|
||||
),
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -71,106 +71,110 @@ class FancyScaffoldState extends State<FancyScaffold>
|
|||
begin: widget.bottomPanelHeight / MediaQuery.of(context).size.height,
|
||||
end: 1.0,
|
||||
).animate(dragController);
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Scaffold(
|
||||
body: widget.navigationRail != null
|
||||
? Row(children: [
|
||||
widget.navigationRail!,
|
||||
const VerticalDivider(
|
||||
indent: 0.0,
|
||||
endIndent: 0.0,
|
||||
width: 2.0,
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: statusNotifier,
|
||||
builder: (context, state, child) => PopScope(
|
||||
canPop: state != AnimationStatus.dismissed,
|
||||
onPopInvoked: state == AnimationStatus.dismissed
|
||||
? null
|
||||
: (_) => dragController.fling(velocity: -1.0),
|
||||
child: child!),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Scaffold(
|
||||
body: widget.navigationRail != null
|
||||
? Row(children: [
|
||||
widget.navigationRail!,
|
||||
const VerticalDivider(
|
||||
indent: 0.0,
|
||||
endIndent: 0.0,
|
||||
width: 2.0,
|
||||
),
|
||||
Expanded(child: widget.body)
|
||||
])
|
||||
: widget.body,
|
||||
drawer: widget.drawer,
|
||||
bottomNavigationBar: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: widget.bottomPanelHeight),
|
||||
if (widget.bottomNavigationBar != null)
|
||||
SizeTransition(
|
||||
axisAlignment: -1.0,
|
||||
sizeFactor:
|
||||
Tween(begin: 1.0, end: 0.0).animate(sizeAnimation),
|
||||
child: widget.bottomNavigationBar,
|
||||
),
|
||||
Expanded(child: widget.body)
|
||||
])
|
||||
: widget.body,
|
||||
drawer: widget.drawer,
|
||||
bottomNavigationBar: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: widget.bottomPanelHeight),
|
||||
if (widget.bottomNavigationBar != null)
|
||||
SizeTransition(
|
||||
axisAlignment: -1.0,
|
||||
sizeFactor:
|
||||
Tween(begin: 1.0, end: 0.0).animate(sizeAnimation),
|
||||
child: widget.bottomNavigationBar,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: AnimatedBuilder(
|
||||
animation: sizeAnimation,
|
||||
builder: (context, child) {
|
||||
final x = 1.0 - sizeAnimation.value;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: (defaultBottomPadding /*+ 8.0*/) * x,
|
||||
//right: 8.0 * x,
|
||||
//left: 8.0 * x,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: statusNotifier,
|
||||
builder: (context, state, child) {
|
||||
return GestureDetector(
|
||||
onVerticalDragEnd: _onVerticalDragEnd,
|
||||
onVerticalDragUpdate: _onVerticalDragUpdate,
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: AnimatedBuilder(
|
||||
animation: sizeAnimation,
|
||||
builder: (context, child) {
|
||||
final x = 1.0 - sizeAnimation.value;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: (defaultBottomPadding /*+ 8.0*/) * x,
|
||||
//right: 8.0 * x,
|
||||
//left: 8.0 * x,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: SizeTransition(
|
||||
sizeFactor: sizeAnimation,
|
||||
axisAlignment: -1.0,
|
||||
axis: Axis.vertical,
|
||||
child: SizedBox(
|
||||
height: screenHeight,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: statusNotifier,
|
||||
builder: (context, state, _) => Stack(
|
||||
children: [
|
||||
if (state != AnimationStatus.dismissed)
|
||||
PopScope(
|
||||
canPop: false,
|
||||
onPopInvoked: (_) =>
|
||||
dragController.fling(velocity: -1.0),
|
||||
child: Positioned.fill(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: statusNotifier,
|
||||
builder: (context, state, child) {
|
||||
return GestureDetector(
|
||||
onVerticalDragEnd: _onVerticalDragEnd,
|
||||
onVerticalDragUpdate: _onVerticalDragUpdate,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: SizeTransition(
|
||||
sizeFactor: sizeAnimation,
|
||||
axisAlignment: -1.0,
|
||||
axis: Axis.vertical,
|
||||
child: SizedBox(
|
||||
height: screenHeight,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: statusNotifier,
|
||||
builder: (context, state, _) => Stack(
|
||||
children: [
|
||||
if (state != AnimationStatus.dismissed)
|
||||
Positioned.fill(
|
||||
key: const Key('player_screen'),
|
||||
child: widget.expandedPanel,
|
||||
),
|
||||
),
|
||||
if (state != AnimationStatus.completed)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
key: const Key('player_bar'),
|
||||
child: FadeTransition(
|
||||
opacity: Tween(begin: 1.0, end: 0.0)
|
||||
.animate(dragController),
|
||||
child: SizedBox(
|
||||
height: widget.bottomPanelHeight,
|
||||
child: widget.bottomPanel),
|
||||
if (state != AnimationStatus.completed)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
key: const Key('player_bar'),
|
||||
child: FadeTransition(
|
||||
opacity: Tween(begin: 1.0, end: 0.0)
|
||||
.animate(dragController),
|
||||
child: SizedBox(
|
||||
height: widget.bottomPanelHeight,
|
||||
child: widget.bottomPanel),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -248,14 +248,23 @@ class HomepageRowSection extends StatelessWidget {
|
|||
final HomePageSection section;
|
||||
const HomepageRowSection(this.section, {super.key});
|
||||
|
||||
Widget buildChild(BuildContext context, List<HomePageItem> items) {
|
||||
return Row(
|
||||
children: items
|
||||
.map((item) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: HomePageItemWidget(item),
|
||||
))
|
||||
.toList(growable: false));
|
||||
Widget buildChild(BuildContext context, List<HomePageItem> items,
|
||||
{bool hasMore = false}) {
|
||||
return Row(children: [
|
||||
...items.map((item) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: HomePageItemWidget(item),
|
||||
)),
|
||||
if (hasMore)
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pushRoute(
|
||||
builder: (context) => HomePageScreen(
|
||||
title: section.title!,
|
||||
channel: DeezerChannel(target: section.pagePath),
|
||||
),
|
||||
),
|
||||
child: Text('Show more'.i18n))
|
||||
]);
|
||||
}
|
||||
|
||||
List<List<T>> _sliceInNLists<T>(List<T> source, int n) {
|
||||
|
@ -279,15 +288,37 @@ class HomepageRowSection extends StatelessWidget {
|
|||
children: _sliceInNLists(section.items!, 3)
|
||||
.map((e) => buildChild(context, e))
|
||||
.toList(growable: false)),
|
||||
_ => buildChild(context, section.items!)
|
||||
_ =>
|
||||
buildChild(context, section.items!, hasMore: section.hasMore ?? false)
|
||||
};
|
||||
return ListTile(
|
||||
title: Text(
|
||||
section.title ?? '',
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w900),
|
||||
title: InkWell(
|
||||
onTap: section.hasMore == true
|
||||
? () => Navigator.of(context).pushRoute(
|
||||
builder: (context) => HomePageScreen(
|
||||
title: section.title!,
|
||||
channel: DeezerChannel(target: section.pagePath),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
section.title ?? '',
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w900),
|
||||
)),
|
||||
if (section.hasMore == true) ...[
|
||||
const SizedBox(width: 16.0),
|
||||
const Icon(Icons.keyboard_arrow_right),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
subtitle: Scrollbar(
|
||||
thickness: MainScreen.of(context).isDesktop ? null : 1.0,
|
||||
|
@ -296,17 +327,6 @@ class HomepageRowSection extends StatelessWidget {
|
|||
child: child,
|
||||
),
|
||||
),
|
||||
trailing: section.hasMore == true
|
||||
? const Icon(Icons.keyboard_arrow_right)
|
||||
: null,
|
||||
onTap: section.hasMore == true
|
||||
? () => Navigator.of(context).pushRoute(
|
||||
builder: (context) => HomePageScreen(
|
||||
title: section.title!,
|
||||
channel: DeezerChannel(target: section.pagePath),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fading_edge_scrollview/fading_edge_scrollview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:freezer/api/definitions.dart';
|
||||
|
@ -12,6 +14,7 @@ import 'package:freezer/translations.i18n.dart';
|
|||
import 'package:freezer/ui/error.dart';
|
||||
import 'package:freezer/ui/player_bar.dart';
|
||||
import 'package:freezer/ui/player_screen.dart';
|
||||
import 'package:mini_music_visualizer/mini_music_visualizer.dart';
|
||||
|
||||
class LyricsScreen extends StatelessWidget {
|
||||
const LyricsScreen({super.key});
|
||||
|
@ -21,11 +24,10 @@ class LyricsScreen extends StatelessWidget {
|
|||
return PlayerScreenBackground(
|
||||
enabled: settings.playerBackgroundOnLyrics,
|
||||
appBar: AppBar(
|
||||
title: Text('Lyrics'.i18n),
|
||||
systemOverlayStyle: PlayerScreenBackground.getSystemUiOverlayStyle(
|
||||
context,
|
||||
enabled: settings.playerBackgroundOnLyrics),
|
||||
backgroundColor: Colors.transparent,
|
||||
forceMaterialTransparency: true,
|
||||
),
|
||||
child: const Column(
|
||||
children: [
|
||||
|
@ -48,7 +50,7 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
with WidgetsBindingObserver {
|
||||
StreamSubscription? _mediaItemSub;
|
||||
StreamSubscription? _positionSub;
|
||||
int? _currentIndex = -1;
|
||||
int _currentIndex = -1;
|
||||
Duration _nextOffset = Duration.zero;
|
||||
Duration _currentOffset = Duration.zero;
|
||||
String? _currentTrackId;
|
||||
|
@ -68,6 +70,9 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
bool _showTranslation = 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 {
|
||||
if (_currentTrackId == trackId) return;
|
||||
_currentTrackId = trackId;
|
||||
|
@ -137,7 +142,7 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
} else {
|
||||
final widgetHeight = _widgetConstraints!.maxHeight;
|
||||
final minScroll = actualHeight * _currentIndex!;
|
||||
scrollTo = minScroll - widgetHeight / 2 + height / 2;
|
||||
scrollTo = minScroll + height / 2;
|
||||
}
|
||||
|
||||
if (scrollTo < 0.0) scrollTo = 0.0;
|
||||
|
@ -157,10 +162,10 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
if (position < _nextOffset && position > _currentOffset) return;
|
||||
|
||||
_currentIndex =
|
||||
_lyrics?.lyrics?.lastIndexWhere((l) => l.offset! <= position);
|
||||
if (_currentIndex! < 0) return;
|
||||
_lyrics?.lyrics?.lastIndexWhere((l) => l.offset! <= position) ?? -1;
|
||||
if (_currentIndex < 0) return;
|
||||
|
||||
if (_currentIndex! < _lyrics!.lyrics!.length - 1) {
|
||||
if (_currentIndex < _lyrics!.lyrics!.length - 1) {
|
||||
// update nextOffset
|
||||
_nextOffset = _lyrics!.lyrics![_currentIndex! + 1].offset!;
|
||||
} else {
|
||||
|
@ -208,7 +213,6 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
print('fuck? $state');
|
||||
switch (state) {
|
||||
case AppLifecycleState.paused:
|
||||
_cancelSubscriptions();
|
||||
|
@ -240,59 +244,49 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textColor =
|
||||
settings.playerBackgroundOnLyrics && settings.blurPlayerBackground
|
||||
? Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black87
|
||||
: Colors.white70
|
||||
: Theme.of(context).colorScheme.onBackground;
|
||||
return Stack(
|
||||
children: [
|
||||
Column(children: [
|
||||
if (_freeScroll && !_loading)
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
setState(() => _freeScroll = false);
|
||||
_scrollToLyric();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
foregroundColor: MaterialStateProperty.all(Colors.white)),
|
||||
child: Text(
|
||||
_currentIndex! >= 0
|
||||
? (_lyrics?.lyrics?[_currentIndex!].text ?? '...')
|
||||
: '...',
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
),
|
||||
Expanded(
|
||||
child: _error != null
|
||||
?
|
||||
//Shouldn't really happen, empty lyrics have own text
|
||||
ErrorScreen(message: _error.toString())
|
||||
:
|
||||
// Loading lyrics
|
||||
_loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: LayoutBuilder(builder: (context, constraints) {
|
||||
_widgetConstraints = constraints;
|
||||
return NotificationListener<ScrollStartNotification>(
|
||||
onNotification:
|
||||
(ScrollStartNotification notification) {
|
||||
if (!_syncedLyrics) return false;
|
||||
final extentDiff =
|
||||
(notification.metrics.extentBefore -
|
||||
notification.metrics.extentAfter)
|
||||
.abs();
|
||||
// avoid accidental clicks
|
||||
const extentThreshold = 10.0;
|
||||
if (extentDiff >= extentThreshold &&
|
||||
!_animatedScroll &&
|
||||
!_loading &&
|
||||
!_freeScroll) {
|
||||
setState(() => _freeScroll = true);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: ScrollConfiguration(
|
||||
behavior: _scrollBehavior,
|
||||
_error != null
|
||||
? ErrorScreen(message: _error.toString())
|
||||
:
|
||||
// Loading lyrics
|
||||
_loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: LayoutBuilder(builder: (context, constraints) {
|
||||
_widgetConstraints = constraints;
|
||||
return NotificationListener<ScrollStartNotification>(
|
||||
onNotification: (notification) {
|
||||
if (!_syncedLyrics) return false;
|
||||
final extentDiff =
|
||||
(notification.metrics.extentBefore -
|
||||
notification.metrics.extentAfter)
|
||||
.abs();
|
||||
// avoid accidental clicks
|
||||
const extentThreshold = 10.0;
|
||||
if (extentDiff >= extentThreshold &&
|
||||
!_animatedScroll &&
|
||||
!_loading &&
|
||||
!_freeScroll) {
|
||||
setState(() => _freeScroll = true);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: ScrollConfiguration(
|
||||
behavior: _scrollBehavior,
|
||||
child: FadingEdgeScrollView.fromScrollView(
|
||||
gradientFractionOnStart: 0.25,
|
||||
gradientFractionOnEnd: 0.25,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: constraints.maxHeight / 2 -
|
||||
height / 2),
|
||||
controller: _controller,
|
||||
itemExtent: !_syncedLyrics
|
||||
? null
|
||||
|
@ -300,8 +294,22 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
(_showTranslation
|
||||
? additionalTranslationHeight
|
||||
: 0.0),
|
||||
itemCount: _lyrics!.lyrics!.length,
|
||||
itemCount: _lyrics!.lyrics!.length + 1,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
if (i-- == 0) {
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 8.0 * 3 + 6.0,
|
||||
child: MiniMusicVisualizer(
|
||||
color: textColor,
|
||||
width: 8.0,
|
||||
height: 16.0,
|
||||
animate: _currentIndex == -1,
|
||||
),
|
||||
)));
|
||||
}
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
|
@ -312,15 +320,22 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
),
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.circular(8.0),
|
||||
BorderRadius.circular(12.0),
|
||||
onTap: _syncedLyrics &&
|
||||
_lyrics!.id != null
|
||||
? () => audioHandler.seek(
|
||||
_lyrics!.lyrics![i].offset!)
|
||||
: null,
|
||||
child: Center(
|
||||
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!,
|
||||
|
@ -328,6 +343,7 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
? TextAlign.center
|
||||
: TextAlign.start,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: _syncedLyrics
|
||||
? 26.0
|
||||
: 20.0,
|
||||
|
@ -357,25 +373,35 @@ class _LyricsWidgetState extends State<LyricsWidget>
|
|||
),
|
||||
)));
|
||||
},
|
||||
)));
|
||||
}),
|
||||
),
|
||||
]),
|
||||
))));
|
||||
}),
|
||||
if (_availableTranslation)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
setState(() => _showTranslation = !_showTranslation);
|
||||
SchedulerBinding.instance
|
||||
.addPostFrameCallback((_) => _scrollToLyric());
|
||||
},
|
||||
child: Text(_showTranslation
|
||||
? 'Without translation'.i18n
|
||||
: 'With translation'.i18n)),
|
||||
)),
|
||||
Positioned(
|
||||
bottom: 16.0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
setState(() => _showTranslation = !_showTranslation);
|
||||
SchedulerBinding.instance
|
||||
.addPostFrameCallback((_) => _scrollToLyric());
|
||||
},
|
||||
child: Text(_showTranslation
|
||||
? 'Without translation'.i18n
|
||||
: 'With translation'.i18n)),
|
||||
),
|
||||
),
|
||||
if (_freeScroll)
|
||||
Positioned(
|
||||
bottom: 16.0,
|
||||
right: 16.0,
|
||||
child: FloatingActionButton(
|
||||
child: const Icon(Icons.sync),
|
||||
onPressed: () => setState(() {
|
||||
_freeScroll = false;
|
||||
_scrollToLyric();
|
||||
})))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -163,12 +163,15 @@ class PlayerScreenBackground extends StatelessWidget {
|
|||
|
||||
Widget _buildChild(
|
||||
BuildContext context, BackgroundProvider provider, Widget child) {
|
||||
final isLightMode = Theme.of(context).brightness == Brightness.light;
|
||||
return Stack(children: [
|
||||
if (provider.imageProvider != null || settings.colorGradientBackground)
|
||||
Positioned.fill(
|
||||
child: provider.imageProvider != null
|
||||
? DecoratedBox(
|
||||
decoration: const BoxDecoration(color: Colors.black),
|
||||
decoration: BoxDecoration(
|
||||
color: Color.lerp(provider.dominantColor,
|
||||
isLightMode ? Colors.white : Colors.black, 0.75)),
|
||||
child: ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(
|
||||
tileMode: TileMode.decal,
|
||||
|
@ -180,10 +183,7 @@ class PlayerScreenBackground extends StatelessWidget {
|
|||
image: DecorationImage(
|
||||
image: provider.imageProvider!,
|
||||
fit: BoxFit.cover,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Colors.white
|
||||
.withOpacity(settings.isDark ? 0.55 : 0.75),
|
||||
BlendMode.dstATop),
|
||||
opacity: 0.35,
|
||||
)),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -170,11 +170,10 @@ class _QueueListWidgetState extends State<QueueListWidget> {
|
|||
trailing: ReorderableDragStartListener(
|
||||
index: index, child: const Icon(Icons.drag_handle)),
|
||||
onTap: () {
|
||||
audioHandler.skipToQueueItem(index).then((value) {
|
||||
if (widget.shouldPopOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
});
|
||||
if (widget.shouldPopOnTap) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
audioHandler.skipToQueueItem(index);
|
||||
},
|
||||
onSecondary: (details) => menuSheet.defaultTrackMenu(
|
||||
Track.fromMediaItem(mediaItem),
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:freezer/api/player/player_helper.dart';
|
|||
import 'package:freezer/icons.dart';
|
||||
import 'package:freezer/main.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
import 'package:freezer/ui/animated_bars.dart';
|
||||
import 'package:mini_music_visualizer/mini_music_visualizer.dart';
|
||||
|
||||
import '../api/definitions.dart';
|
||||
import 'cached_image.dart';
|
||||
|
@ -107,15 +107,6 @@ class TrackCardTile extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
);
|
||||
ListTile(
|
||||
title: Text(title),
|
||||
subtitle: Text(artist),
|
||||
leading: Stack(
|
||||
children: [CachedImage(url: artUri), PlayItemButton(onTap: onTap)],
|
||||
),
|
||||
contentPadding: const EdgeInsets.only(left: 16.0, right: 124.0),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,34 +200,39 @@ class TrackTile extends StatelessWidget {
|
|||
artist,
|
||||
maxLines: 1,
|
||||
),
|
||||
leading: CachedImage(
|
||||
url: artUri,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
),
|
||||
// StreamBuilder<MediaItem?>(
|
||||
// initialData: audioHandler.mediaItem.value,
|
||||
// stream: audioHandler.mediaItem,
|
||||
// builder: (context, snapshot) {
|
||||
// final child = CachedImage(
|
||||
// url: artUri,
|
||||
// width: 48.0,
|
||||
// height: 48.0,
|
||||
// );
|
||||
//
|
||||
// if (snapshot.data?.id == trackId) {
|
||||
// return Stack(children: [
|
||||
// child,
|
||||
// const Positioned.fill(
|
||||
// child: DecoratedBox(
|
||||
// decoration: BoxDecoration(color: Colors.black26),
|
||||
// child: AnimatedBars()),
|
||||
// ),
|
||||
// ]);
|
||||
// }
|
||||
//
|
||||
// return child;
|
||||
// }),
|
||||
leading: StreamBuilder<MediaItem?>(
|
||||
initialData: audioHandler.mediaItem.value,
|
||||
stream: audioHandler.mediaItem,
|
||||
builder: (context, snapshot) {
|
||||
final child = CachedImage(
|
||||
url: artUri,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
);
|
||||
|
||||
if (snapshot.data?.id == trackId) {
|
||||
return Stack(children: [
|
||||
child,
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(color: Colors.black26),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 18.0,
|
||||
height: 16.0,
|
||||
child: StreamBuilder<bool>(
|
||||
stream: playerHelper.playing,
|
||||
builder: (context, snapshot) {
|
||||
return MiniMusicVisualizer(
|
||||
color: Colors.white70,
|
||||
animate: snapshot.data ?? false);
|
||||
})),
|
||||
)),
|
||||
),
|
||||
]);
|
||||
}
|
||||
return child;
|
||||
}),
|
||||
onTap: onTap,
|
||||
onLongPress: normalizeSecondary(onSecondary),
|
||||
trailing: Row(
|
||||
|
|
|
@ -371,7 +371,7 @@ packages:
|
|||
source: hosted
|
||||
version: "2.0.5"
|
||||
fading_edge_scrollview:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fading_edge_scrollview
|
||||
sha256: c25c2231652ce774cc31824d0112f11f653881f43d7f5302c05af11942052031
|
||||
|
|
|
@ -115,6 +115,7 @@ dependencies:
|
|||
freezed_annotation:
|
||||
^2.4.1
|
||||
mini_music_visualizer: ^1.1.0
|
||||
fading_edge_scrollview: ^3.0.0
|
||||
#deezcryptor:
|
||||
#path: deezcryptor/
|
||||
|
||||
|
|
Loading…
Reference in New Issue