Compare commits

...

6 commits

Author SHA1 Message Date
Pato05 50717ebbd3
fix pop, fix gradient background looking bad 2024-04-29 16:51:04 +02:00
Pato05 f9389ef50d
better curve animation for the PlayerBar 2024-04-29 16:42:40 +02:00
Pato05 415325ac94
lyrics_screen: don't animate music visualizer if audio isn't playing 2024-04-29 16:35:06 +02:00
Pato05 4b5d0bd09c
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
2024-04-29 16:23:22 +02:00
Pato05 8ea6bcd073
don't round image in TrackCardTile 2024-04-29 00:36:40 +02:00
Pato05 f7283a6633
add horizontal-list support + ui improvements 2024-04-29 00:33:32 +02:00
16 changed files with 657 additions and 479 deletions

View file

@ -990,17 +990,17 @@ class HomePageSection {
'grid-preview-two': HomePageSectionLayout.row, 'grid-preview-two': HomePageSectionLayout.row,
'grid': HomePageSectionLayout.grid, 'grid': HomePageSectionLayout.grid,
'slideshow': HomePageSectionLayout.slideshow, 'slideshow': HomePageSectionLayout.slideshow,
'horizontal-list': HomePageSectionLayout.horizontalList
}[json['layout'] ?? '']; }[json['layout'] ?? ''];
if (layout == null) { if (layout == null) {
_logger.warning('UNKNOWN LAYOUT: ${json['layout']}'); _logger.warning('UNKNOWN LAYOUT: ${json['layout']}');
return null; return null;
} }
_logger.fine('LAYOUT: $layout'); _logger.fine(json['title']);
final items = <HomePageItem>[]; final items = ((json['items'] ?? []) as List)
for (var i in (json['items'] ?? [])) { .map((e) => HomePageItem.fromPrivateJson(e as Map))
HomePageItem? hpi = HomePageItem.fromPrivateJson(i); .whereNotNull()
if (hpi != null) items.add(hpi); .toList(growable: false);
}
return HomePageSection( return HomePageSection(
title: json['title'], title: json['title'],
items: items, items: items,
@ -1044,10 +1044,6 @@ class HomePageItem {
return HomePageItem( return HomePageItem(
type: HomePageItemType.ARTIST, type: HomePageItemType.ARTIST,
value: Artist.fromPrivateJson(json['data'])); value: Artist.fromPrivateJson(json['data']));
case 'channel':
return HomePageItem(
type: HomePageItemType.CHANNEL,
value: DeezerChannel.fromPrivateJson(json, false));
case 'album': case 'album':
return HomePageItem( return HomePageItem(
type: HomePageItemType.ALBUM, type: HomePageItemType.ALBUM,
@ -1056,10 +1052,18 @@ class HomePageItem {
return HomePageItem( return HomePageItem(
type: HomePageItemType.SHOW, type: HomePageItemType.SHOW,
value: Show.fromPrivateJson(json['data'])); value: Show.fromPrivateJson(json['data']));
case 'channel':
return HomePageItem(
type: HomePageItemType.CHANNEL,
value: DeezerChannel.fromPrivateJson(json, false));
case 'external-link': case 'external-link':
return HomePageItem( return HomePageItem(
type: HomePageItemType.EXTERNAL_LINK, type: HomePageItemType.EXTERNAL_LINK,
value: DeezerChannel.fromPrivateJson(json, true)); value: DeezerChannel.fromPrivateJson(json, true));
case 'track':
return HomePageItem(
type: HomePageItemType.TRACK,
value: Track.fromPrivateJson(json['data']));
default: default:
return null; return null;
} }
@ -1101,9 +1105,13 @@ class HomePageItem {
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
String type = describeEnum(this.type); String type = this.type.name;
return {'type': type, 'value': value.toJson()}; return {'type': type, 'value': value.toJson()};
} }
String toString() {
return type.name;
}
} }
@HiveType(typeId: 14) @HiveType(typeId: 14)
@ -1181,6 +1189,9 @@ enum HomePageItemType {
@HiveField(6) @HiveField(6)
EXTERNAL_LINK, EXTERNAL_LINK,
@HiveField(7)
TRACK, // for track mixes
} }
@HiveType(typeId: 3) @HiveType(typeId: 3)
@ -1193,6 +1204,10 @@ enum HomePageSectionLayout {
/// ROW but bigger /// ROW but bigger
@HiveField(2) @HiveField(2)
slideshow, slideshow,
/// Homepage song radios
@HiveField(3)
horizontalList,
} }
enum RepeatType { NONE, LIST, TRACK } enum RepeatType { NONE, LIST, TRACK }

View file

@ -255,7 +255,7 @@ class AudioPlayerTask extends BaseAudioHandler {
// listen for connectivity changes // listen for connectivity changes
_subscriptions.add(Connectivity() _subscriptions.add(Connectivity()
.onConnectivityChanged .onConnectivityChanged
.listen(_determineAudioQualityByResult)); .listen(_determineAudioQualityByResults));
} }
if (shouldLoadQueue) { if (shouldLoadQueue) {
@ -271,7 +271,7 @@ class AudioPlayerTask extends BaseAudioHandler {
try { try {
await Connectivity() await Connectivity()
.checkConnectivity() .checkConnectivity()
.then(_determineAudioQualityByResult); .then(_determineAudioQualityByResults);
return true; return true;
} catch (e) { } catch (e) {
_isConnectivityPluginAvailable = false; _isConnectivityPluginAvailable = false;
@ -282,12 +282,13 @@ class AudioPlayerTask extends BaseAudioHandler {
_logger.warning( _logger.warning(
'Couldn\'t determine connection! Falling back to other (which may use wifi quality)'); 'Couldn\'t determine connection! Falling back to other (which may use wifi quality)');
// on error, return dummy value -- error can happen on linux if not using NetworkManager, for example // on error, return dummy value -- error can happen on linux if not using NetworkManager, for example
_determineAudioQualityByResult(ConnectivityResult.other); _determineAudioQualityByResults([ConnectivityResult.other]);
return false; return false;
} }
/// Determines the [AudioQuality] to use according to [result] /// Determines the [AudioQuality] to use according to [result]
void _determineAudioQualityByResult(ConnectivityResult result) { void _determineAudioQualityByResults(List<ConnectivityResult> results) {
final result = results[0];
switch (result) { switch (result) {
case ConnectivityResult.mobile: case ConnectivityResult.mobile:
case ConnectivityResult.bluetooth: case ConnectivityResult.bluetooth:

View file

@ -31,7 +31,7 @@ import 'package:freezer/ui/search.dart';
import 'package:freezer/ui/settings_screen.dart'; import 'package:freezer/ui/settings_screen.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:i18n_extension/i18n_widget.dart'; import 'package:i18n_extension/i18n_extension.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:freezer/translations.i18n.dart'; import 'package:freezer/translations.i18n.dart';
import 'package:quick_actions/quick_actions.dart'; import 'package:quick_actions/quick_actions.dart';
@ -287,14 +287,15 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
super.initState(); super.initState();
} }
Future _logOut() async { Future<void> _logOut() async {
await GetIt.instance<DeezerAPI>().logout(); await GetIt.instance<DeezerAPI>().logout();
setState(() {
settings.arl = null; settings.arl = null;
settings.offlineMode = false; settings.offlineMode = false;
});
await settings.save(); await settings.save();
await Cache.wipe(); await Cache.wipe();
setState(() {});
} }
@override @override

View file

@ -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),
),
)),
));
}
}

View file

@ -71,106 +71,112 @@ class FancyScaffoldState extends State<FancyScaffold>
begin: widget.bottomPanelHeight / MediaQuery.of(context).size.height, begin: widget.bottomPanelHeight / MediaQuery.of(context).size.height,
end: 1.0, end: 1.0,
).animate(dragController); ).animate(dragController);
return Stack( return ValueListenableBuilder(
children: [ valueListenable: statusNotifier,
Positioned.fill( builder: (context, state, child) => PopScope(
child: Scaffold( canPop: state == AnimationStatus.dismissed,
body: widget.navigationRail != null onPopInvoked: state == AnimationStatus.dismissed
? Row(children: [ ? null
widget.navigationRail!, : (_) => dragController.fling(velocity: -1.0),
const VerticalDivider( child: child!),
indent: 0.0, child: Stack(
endIndent: 0.0, children: [
width: 2.0, 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(
Positioned( bottom: 0,
bottom: 0, left: 0,
left: 0, right: 0,
right: 0, child: AnimatedBuilder(
child: AnimatedBuilder( animation: sizeAnimation,
animation: sizeAnimation, builder: (context, child) {
builder: (context, child) { final x = 1.0 - sizeAnimation.value;
final x = 1.0 - sizeAnimation.value; return Padding(
return Padding( padding: EdgeInsets.only(
padding: EdgeInsets.only( bottom: (defaultBottomPadding /*+ 8.0*/) * x,
bottom: (defaultBottomPadding /*+ 8.0*/) * x, //right: 8.0 * x,
//right: 8.0 * x, //left: 8.0 * x,
//left: 8.0 * x, ),
),
child: child,
);
},
child: ValueListenableBuilder(
valueListenable: statusNotifier,
builder: (context, state, child) {
return GestureDetector(
onVerticalDragEnd: _onVerticalDragEnd,
onVerticalDragUpdate: _onVerticalDragUpdate,
child: child, child: child,
); );
}, },
child: SizeTransition( child: ValueListenableBuilder(
sizeFactor: sizeAnimation, valueListenable: statusNotifier,
axisAlignment: -1.0, builder: (context, state, child) {
axis: Axis.vertical, return GestureDetector(
child: SizedBox( onVerticalDragEnd: _onVerticalDragEnd,
height: screenHeight, onVerticalDragUpdate: _onVerticalDragUpdate,
width: MediaQuery.of(context).size.width, child: child,
child: ValueListenableBuilder( );
valueListenable: statusNotifier, },
builder: (context, state, _) => Stack( child: SizeTransition(
children: [ sizeFactor: sizeAnimation,
if (state != AnimationStatus.dismissed) axisAlignment: -1.0,
PopScope( axis: Axis.vertical,
canPop: false, child: SizedBox(
onPopInvoked: (_) => height: screenHeight,
dragController.fling(velocity: -1.0), width: MediaQuery.of(context).size.width,
child: Positioned.fill( child: ValueListenableBuilder(
valueListenable: statusNotifier,
builder: (context, state, _) => Stack(
children: [
if (state != AnimationStatus.dismissed)
Positioned.fill(
key: const Key('player_screen'), key: const Key('player_screen'),
child: widget.expandedPanel, child: widget.expandedPanel,
), ),
), if (state != AnimationStatus.completed)
if (state != AnimationStatus.completed) Positioned(
Positioned( top: 0,
top: 0, right: 0,
right: 0, left: 0,
left: 0, key: const Key('player_bar'),
key: const Key('player_bar'), child: FadeTransition(
child: FadeTransition( opacity: Tween(begin: 1.0, end: 0.0).animate(
opacity: Tween(begin: 1.0, end: 0.0) CurvedAnimation(
.animate(dragController), parent: dragController,
child: SizedBox( curve: const Interval(0.0, 0.25))),
height: widget.bottomPanelHeight, child: SizedBox(
child: widget.bottomPanel), height: widget.bottomPanelHeight,
child: widget.bottomPanel),
),
), ),
), ],
], ),
), ),
), )),
)), ),
), ),
), ),
), ],
], ),
); );
} }

View file

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -210,7 +211,7 @@ class _HomePageWidgetState extends State<HomePageWidget> {
return HomePageGridSection(section); return HomePageGridSection(section);
case HomePageSectionLayout.slideshow: case HomePageSectionLayout.slideshow:
case HomePageSectionLayout.row: case HomePageSectionLayout.row:
default: case HomePageSectionLayout.horizontalList:
return HomepageRowSection(section); return HomepageRowSection(section);
} }
} }
@ -243,72 +244,90 @@ class _HomePageWidgetState extends State<HomePageWidget> {
} }
} }
class HomepageRowSection extends StatefulWidget { class HomepageRowSection extends StatelessWidget {
final HomePageSection section; final HomePageSection section;
const HomepageRowSection(this.section, {super.key}); const HomepageRowSection(this.section, {super.key});
@override Widget buildChild(BuildContext context, List<HomePageItem> items,
State<HomepageRowSection> createState() => _HomepageRowSectionState(); {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))
]);
}
class _HomepageRowSectionState extends State<HomepageRowSection> { List<List<T>> _sliceInNLists<T>(List<T> source, int n) {
final _controller = ScrollController(); final List<List<T>> dest = List.generate(n, (_) => [], growable: false);
@override int i = 0;
void dispose() { for (var item in source) {
_controller.dispose(); dest[i].add(item);
super.dispose(); if (++i == n) i = 0;
}
return dest;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Widget child = switch (section.layout) {
HomePageSectionLayout.horizontalList => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _sliceInNLists(section.items!, 3)
.map((e) => buildChild(context, e))
.toList(growable: false)),
_ =>
buildChild(context, section.items!, hasMore: section.hasMore ?? false)
};
return ListTile( return ListTile(
title: Text( title: InkWell(
widget.section.title ?? '', onTap: section.hasMore == true
textAlign: TextAlign.left, ? () => Navigator.of(context).pushRoute(
maxLines: 2, builder: (context) => HomePageScreen(
overflow: TextOverflow.ellipsis, title: section.title!,
style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w900), 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( ),
controller: _controller, subtitle: Scrollbar(
thickness: MainScreen.of(context).isDesktop ? null : 1.0, thickness: MainScreen.of(context).isDesktop ? null : 1.0,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _controller, scrollDirection: Axis.horizontal,
scrollDirection: Axis.horizontal, child: child,
child: Row( ),
children: List.generate(widget.section.items!.length + 1, (j) { ),
//Has more items );
if (j == widget.section.items!.length) {
if (widget.section.hasMore ?? false) {
return TextButton(
child: Text(
'Show more'.i18n,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20.0),
),
onPressed: () => Navigator.of(context).pushRoute(
builder: (context) => HomePageScreen(
title: widget.section.title!,
channel:
DeezerChannel(target: widget.section.pagePath),
),
),
);
}
return const SizedBox();
}
//Show item
HomePageItem item = widget.section.items![j];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: HomePageItemWidget(item),
);
}),
),
),
));
} }
} }
@ -368,6 +387,7 @@ class HomePageItemWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
switch (item.type) { switch (item.type) {
case HomePageItemType.SMARTTRACKLIST: case HomePageItemType.SMARTTRACKLIST:
return SmartTrackListTile( return SmartTrackListTile(
@ -444,6 +464,16 @@ class HomePageItemWidget extends StatelessWidget {
.pushRoute(builder: (context) => ShowScreen(item.value)); .pushRoute(builder: (context) => ShowScreen(item.value));
}, },
); );
case HomePageItemType.TRACK:
final track = item.value as Track;
return TrackCardTile.fromTrack(
track,
onTap: () => playerHelper.playSearchMixDeferred(track),
onSecondary: (details) =>
MenuSheet(context).defaultTrackMenu(track, details: details),
width:
mediaQuery.size.width > 530 ? null : mediaQuery.size.width * 0.75,
);
default: default:
return const SizedBox(height: 0, width: 0); return const SizedBox(height: 0, width: 0);
} }

View file

@ -293,7 +293,8 @@ class _LibraryTracksState extends State<LibraryTracks> {
return; return;
} }
ConnectivityResult connectivity = await Connectivity().checkConnectivity(); ConnectivityResult connectivity =
(await Connectivity().checkConnectivity())[0];
if (connectivity != ConnectivityResult.none) { if (connectivity != ConnectivityResult.none) {
setState(() => _loading = true); setState(() => _loading = true);
int pos = tracks.length; int pos = tracks.length;

View file

@ -96,7 +96,6 @@ class _LoginOnOtherDeviceState extends State<LoginOnOtherDevice> {
} }
} }
print(res);
final data = res.data as Map; final data = res.data as Map;
if (!data['ok']) { if (!data['ok']) {
setState(() { setState(() {

View file

@ -1,17 +1,21 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:fading_edge_scrollview/fading_edge_scrollview.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:freezer/api/definitions.dart'; import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/pipe_api.dart'; import 'package:freezer/api/pipe_api.dart';
import 'package:freezer/api/player/audio_handler.dart';
import 'package:freezer/api/player/player_helper.dart'; 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/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';
class LyricsScreen extends StatelessWidget { class LyricsScreen extends StatelessWidget {
const LyricsScreen({super.key}); const LyricsScreen({super.key});
@ -21,11 +25,10 @@ class LyricsScreen extends StatelessWidget {
return PlayerScreenBackground( return PlayerScreenBackground(
enabled: settings.playerBackgroundOnLyrics, enabled: settings.playerBackgroundOnLyrics,
appBar: AppBar( appBar: AppBar(
title: Text('Lyrics'.i18n),
systemOverlayStyle: PlayerScreenBackground.getSystemUiOverlayStyle( systemOverlayStyle: PlayerScreenBackground.getSystemUiOverlayStyle(
context, context,
enabled: settings.playerBackgroundOnLyrics), enabled: settings.playerBackgroundOnLyrics),
backgroundColor: Colors.transparent, forceMaterialTransparency: true,
), ),
child: const Column( child: const Column(
children: [ children: [
@ -48,7 +51,7 @@ class _LyricsWidgetState extends State<LyricsWidget>
with WidgetsBindingObserver { with WidgetsBindingObserver {
StreamSubscription? _mediaItemSub; StreamSubscription? _mediaItemSub;
StreamSubscription? _positionSub; StreamSubscription? _positionSub;
int? _currentIndex = -1; int _currentIndex = -1;
Duration _nextOffset = Duration.zero; Duration _nextOffset = Duration.zero;
Duration _currentOffset = Duration.zero; Duration _currentOffset = Duration.zero;
String? _currentTrackId; String? _currentTrackId;
@ -68,6 +71,9 @@ 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;
@ -137,7 +143,7 @@ class _LyricsWidgetState extends State<LyricsWidget>
} else { } else {
final widgetHeight = _widgetConstraints!.maxHeight; final widgetHeight = _widgetConstraints!.maxHeight;
final minScroll = actualHeight * _currentIndex!; final minScroll = actualHeight * _currentIndex!;
scrollTo = minScroll - widgetHeight / 2 + height / 2; scrollTo = minScroll + height / 2;
} }
if (scrollTo < 0.0) scrollTo = 0.0; if (scrollTo < 0.0) scrollTo = 0.0;
@ -157,10 +163,10 @@ class _LyricsWidgetState extends State<LyricsWidget>
if (position < _nextOffset && position > _currentOffset) return; if (position < _nextOffset && position > _currentOffset) return;
_currentIndex = _currentIndex =
_lyrics?.lyrics?.lastIndexWhere((l) => l.offset! <= position); _lyrics?.lyrics?.lastIndexWhere((l) => l.offset! <= position) ?? -1;
if (_currentIndex! < 0) return; if (_currentIndex < 0) return;
if (_currentIndex! < _lyrics!.lyrics!.length - 1) { if (_currentIndex < _lyrics!.lyrics!.length - 1) {
// update nextOffset // update nextOffset
_nextOffset = _lyrics!.lyrics![_currentIndex! + 1].offset!; _nextOffset = _lyrics!.lyrics![_currentIndex! + 1].offset!;
} else { } else {
@ -208,7 +214,6 @@ class _LyricsWidgetState extends State<LyricsWidget>
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
print('fuck? $state');
switch (state) { switch (state) {
case AppLifecycleState.paused: case AppLifecycleState.paused:
_cancelSubscriptions(); _cancelSubscriptions();
@ -240,59 +245,49 @@ class _LyricsWidgetState extends State<LyricsWidget>
@override @override
Widget build(BuildContext context) { 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( return Stack(
children: [ children: [
Column(children: [ _error != null
if (_freeScroll && !_loading) ? ErrorScreen(message: _error.toString())
Center( :
child: TextButton( // Loading lyrics
onPressed: () { _loading
setState(() => _freeScroll = false); ? const Center(child: CircularProgressIndicator())
_scrollToLyric(); : LayoutBuilder(builder: (context, constraints) {
}, _widgetConstraints = constraints;
style: ButtonStyle( return NotificationListener<ScrollStartNotification>(
foregroundColor: MaterialStateProperty.all(Colors.white)), onNotification: (notification) {
child: Text( if (!_syncedLyrics) return false;
_currentIndex! >= 0 final extentDiff =
? (_lyrics?.lyrics?[_currentIndex!].text ?? '...') (notification.metrics.extentBefore -
: '...', notification.metrics.extentAfter)
textAlign: TextAlign.center, .abs();
)), // avoid accidental clicks
), const extentThreshold = 10.0;
Expanded( if (extentDiff >= extentThreshold &&
child: _error != null !_animatedScroll &&
? !_loading &&
//Shouldn't really happen, empty lyrics have own text !_freeScroll) {
ErrorScreen(message: _error.toString()) setState(() => _freeScroll = true);
: }
// Loading lyrics return false;
_loading },
? const Center(child: CircularProgressIndicator()) child: ScrollConfiguration(
: LayoutBuilder(builder: (context, constraints) { behavior: _scrollBehavior,
_widgetConstraints = constraints; child: FadingEdgeScrollView.fromScrollView(
return NotificationListener<ScrollStartNotification>( gradientFractionOnStart: 0.25,
onNotification: gradientFractionOnEnd: 0.25,
(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,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 8.0), horizontal: 8.0,
vertical: constraints.maxHeight / 2 -
height / 2),
controller: _controller, controller: _controller,
itemExtent: !_syncedLyrics itemExtent: !_syncedLyrics
? null ? null
@ -300,8 +295,30 @@ class _LyricsWidgetState extends State<LyricsWidget>
(_showTranslation (_showTranslation
? additionalTranslationHeight ? additionalTranslationHeight
: 0.0), : 0.0),
itemCount: _lyrics!.lyrics!.length, itemCount: _lyrics!.lyrics!.length + 1,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
if (i-- == 0) {
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) &&
_currentIndex == -1,
);
}),
)));
}
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
@ -312,15 +329,22 @@ class _LyricsWidgetState extends State<LyricsWidget>
), ),
child: InkWell( child: InkWell(
borderRadius: borderRadius:
BorderRadius.circular(8.0), BorderRadius.circular(12.0),
onTap: _syncedLyrics && onTap: _syncedLyrics &&
_lyrics!.id != null _lyrics!.id != null
? () => audioHandler.seek( ? () => audioHandler.seek(
_lyrics!.lyrics![i].offset!) _lyrics!.lyrics![i].offset!)
: null, : null,
child: Center( child: Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 4.0,
//vertical: 24.0,
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.center,
children: [ children: [
Text( Text(
_lyrics!.lyrics![i].text!, _lyrics!.lyrics![i].text!,
@ -328,6 +352,7 @@ class _LyricsWidgetState extends State<LyricsWidget>
? TextAlign.center ? TextAlign.center
: TextAlign.start, : TextAlign.start,
style: TextStyle( style: TextStyle(
color: textColor,
fontSize: _syncedLyrics fontSize: _syncedLyrics
? 26.0 ? 26.0
: 20.0, : 20.0,
@ -357,25 +382,35 @@ class _LyricsWidgetState extends State<LyricsWidget>
), ),
))); )));
}, },
))); ))));
}), }),
),
]),
if (_availableTranslation) if (_availableTranslation)
Align( Positioned(
alignment: Alignment.bottomCenter, bottom: 16.0,
child: Padding( left: 0,
padding: const EdgeInsets.only(bottom: 8.0), right: 0,
child: ElevatedButton( child: Center(
onPressed: () { child: ElevatedButton(
setState(() => _showTranslation = !_showTranslation); onPressed: () {
SchedulerBinding.instance setState(() => _showTranslation = !_showTranslation);
.addPostFrameCallback((_) => _scrollToLyric()); SchedulerBinding.instance
}, .addPostFrameCallback((_) => _scrollToLyric());
child: Text(_showTranslation },
? 'Without translation'.i18n child: Text(_showTranslation
: 'With translation'.i18n)), ? '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();
})))
], ],
); );
} }

View file

@ -20,68 +20,71 @@ import 'package:url_launcher/url_launcher.dart';
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate { class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
final Track track; final Track track;
final double extent; static const kExtent = 84.0 + 16.0 * 2 + 2.0;
const SliverTrackPersistentHeader(this.track, {required this.extent}); const SliverTrackPersistentHeader(this.track);
@override @override
bool shouldRebuild(oldDelegate) => false; bool shouldRebuild(oldDelegate) => false;
@override @override
double get maxExtent => extent; double get maxExtent => kExtent;
@override @override
double get minExtent => extent; double get minExtent => kExtent;
@override @override
Widget build( Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) { BuildContext context, double shrinkOffset, bool overlapsContent) {
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration(color: Theme.of(context).cardColor), decoration:
BoxDecoration(color: Theme.of(context).colorScheme.background),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
Row( Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
const SizedBox(width: 16.0),
Semantics( Semantics(
label: "Album art".i18n, label: "Album art".i18n,
image: true, image: true,
child: CachedImage( child: CachedImage(
url: track.albumArt!.full, url: track.albumArt!.full,
height: 128, height: 86.0,
width: 128, width: 86.0,
), ),
), ),
SizedBox( Expanded(
width: 240.0, child: Padding(
child: Column( padding: const EdgeInsets.symmetric(horizontal: 16.0),
mainAxisSize: MainAxisSize.min, child: Column(
children: <Widget>[ mainAxisSize: MainAxisSize.min,
Text( crossAxisAlignment: CrossAxisAlignment.start,
track.title!, children: <Widget>[
maxLines: 1, Text(
textAlign: TextAlign.center, track.title!,
overflow: TextOverflow.ellipsis, maxLines: 1,
style: const TextStyle( textAlign: TextAlign.center,
fontSize: 22.0, fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis,
), style: const TextStyle(fontWeight: FontWeight.bold),
Text( ),
track.artistString, Text(
textAlign: TextAlign.center, track.album!.title!,
overflow: TextOverflow.ellipsis, textAlign: TextAlign.center,
maxLines: 1, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 20.0), maxLines: 1,
), ),
const SizedBox(height: 8.0), Text(
Text( track.artistString,
track.album!.title!, textAlign: TextAlign.center,
textAlign: TextAlign.center, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, maxLines: 1,
maxLines: 1, style: Theme.of(context).textTheme.bodyMedium,
), ),
Text(track.durationString) ],
], ),
), ),
), ),
], ],
@ -202,6 +205,7 @@ class MenuSheet {
builder: (context, scrollController) => Material( builder: (context, scrollController) => Material(
type: MaterialType.card, type: MaterialType.card,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
color: Theme.of(context).colorScheme.background,
borderRadius: borderRadius:
const BorderRadius.vertical(top: Radius.circular(20.0)), const BorderRadius.vertical(top: Radius.circular(20.0)),
child: SafeArea( child: SafeArea(
@ -210,8 +214,7 @@ class MenuSheet {
slivers: [ slivers: [
SliverPersistentHeader( SliverPersistentHeader(
pinned: true, pinned: true,
delegate: SliverTrackPersistentHeader(track, delegate: SliverTrackPersistentHeader(track)),
extent: 128.0 + 16.0 + 16.0)),
SliverList( SliverList(
delegate: SliverChildListDelegate.fixed(options delegate: SliverChildListDelegate.fixed(options
.map((option) => ListTile( .map((option) => ListTile(

View file

@ -163,12 +163,15 @@ class PlayerScreenBackground extends StatelessWidget {
Widget _buildChild( Widget _buildChild(
BuildContext context, BackgroundProvider provider, Widget child) { BuildContext context, BackgroundProvider provider, Widget child) {
final isLightMode = Theme.of(context).brightness == Brightness.light;
return Stack(children: [ return Stack(children: [
if (provider.imageProvider != null || settings.colorGradientBackground) if (provider.imageProvider != null || settings.colorGradientBackground)
Positioned.fill( Positioned.fill(
child: provider.imageProvider != null child: provider.imageProvider != null
? DecoratedBox( ? DecoratedBox(
decoration: const BoxDecoration(color: Colors.black), decoration: BoxDecoration(
color: Color.lerp(provider.dominantColor,
isLightMode ? Colors.white : Colors.black, 0.75)),
child: ImageFiltered( child: ImageFiltered(
imageFilter: ImageFilter.blur( imageFilter: ImageFilter.blur(
tileMode: TileMode.decal, tileMode: TileMode.decal,
@ -180,10 +183,7 @@ class PlayerScreenBackground extends StatelessWidget {
image: DecorationImage( image: DecorationImage(
image: provider.imageProvider!, image: provider.imageProvider!,
fit: BoxFit.cover, fit: BoxFit.cover,
colorFilter: ColorFilter.mode( opacity: 0.35,
Colors.white
.withOpacity(settings.isDark ? 0.55 : 0.75),
BlendMode.dstATop),
)), )),
), ),
), ),
@ -195,7 +195,8 @@ class PlayerScreenBackground extends StatelessWidget {
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
if (provider.dominantColor != null) if (provider.dominantColor != null)
provider.dominantColor!, Color.lerp(provider.dominantColor,
isLightMode ? Colors.white : Colors.black, 0.5)!,
Theme.of(context).scaffoldBackgroundColor, Theme.of(context).scaffoldBackgroundColor,
], ],
stops: const [0.0, 0.6], stops: const [0.0, 0.6],
@ -369,10 +370,7 @@ class PlayerScreenDesktop extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: PlayerScreenTopRow( child: PlayerScreenTopRow(
textSize: 12.h, textSize: 12.h, iconSize: 21.h, desktopMode: true),
iconSize: 21.h,
showQueueButton: false,
),
), ),
Flexible( Flexible(
child: ConstrainedBox( child: ConstrainedBox(
@ -1049,7 +1047,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
right: 16.0, right: 16.0,
child: LyricsButton( child: LyricsButton(
onTap: _pushLyrics, onTap: _pushLyrics,
size: constraints.maxHeight / 20, size: 20.spMax,
), ),
); );
}, },
@ -1121,14 +1119,15 @@ class PlayerScreenTopRow extends StatelessWidget {
final double? iconSize; final double? iconSize;
final double? textWidth; final double? textWidth;
final bool short; final bool short;
final bool showQueueButton; // not needed on desktop final bool desktopMode;
const PlayerScreenTopRow( const PlayerScreenTopRow({
{super.key, super.key,
this.textSize, this.textSize,
this.iconSize, this.iconSize,
this.textWidth, this.textWidth,
this.short = false, this.short = false,
this.showQueueButton = true}); this.desktopMode = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -1175,20 +1174,9 @@ class PlayerScreenTopRow extends StatelessWidget {
.copyWith(fontSize: textSize ?? 38.sp))); .copyWith(fontSize: textSize ?? 38.sp)));
}), }),
), ),
showQueueButton desktopMode
? IconButton( ? PlayerMenuButtonDesktop(size: size)
icon: Icon( : PlayerMenuButton(size: size)
Icons.menu,
semanticLabel: "Queue".i18n,
),
iconSize: size,
splashRadius: size * 1.5,
onPressed: () => Navigator.of(context).pushRoute(
builder: (ctx) => QueueScreen(
closePlayer: FancyScaffold.of(context)!.closePanel,
)),
)
: SizedBox.square(dimension: size + 16.0),
], ],
); );
} }
@ -1329,7 +1317,19 @@ class BottomBarControls extends StatelessWidget {
textSize: size * 0.75, textSize: size * 0.75,
), ),
const Expanded(child: SizedBox()), const Expanded(child: SizedBox()),
PlayerMenuButton(size: size), if (!desktopMode)
IconButton(
icon: Icon(
Icons.playlist_play,
semanticLabel: "Queue".i18n,
),
iconSize: size,
splashRadius: size * 1.5,
onPressed: () => Navigator.of(context).pushRoute(
builder: (ctx) => QueueScreen(
closePlayer: FancyScaffold.of(context)!.closePanel,
)),
),
], ],
); );
} }
@ -1371,9 +1371,19 @@ class BottomBarControls extends StatelessWidget {
// toastLength: Toast.LENGTH_SHORT); // toastLength: Toast.LENGTH_SHORT);
// }, // },
// ), // ),
desktopMode if (!desktopMode)
? PlayerMenuButtonDesktop(size: iconSize) IconButton(
: PlayerMenuButton(size: iconSize) icon: Icon(
Icons.playlist_play,
semanticLabel: "Queue".i18n,
),
iconSize: size,
splashRadius: size * 1.5,
onPressed: () => Navigator.of(context).pushRoute(
builder: (ctx) => QueueScreen(
closePlayer: FancyScaffold.of(context)!.closePanel,
)),
),
], ],
); );
} }

View file

@ -170,11 +170,10 @@ class _QueueListWidgetState extends State<QueueListWidget> {
trailing: ReorderableDragStartListener( trailing: ReorderableDragStartListener(
index: index, child: const Icon(Icons.drag_handle)), index: index, child: const Icon(Icons.drag_handle)),
onTap: () { onTap: () {
audioHandler.skipToQueueItem(index).then((value) { if (widget.shouldPopOnTap) {
if (widget.shouldPopOnTap) { Navigator.pop(context);
Navigator.of(context).pop(); }
} audioHandler.skipToQueueItem(index);
});
}, },
onSecondary: (details) => menuSheet.defaultTrackMenu( onSecondary: (details) => menuSheet.defaultTrackMenu(
Track.fromMediaItem(mediaItem), Track.fromMediaItem(mediaItem),

View file

@ -8,7 +8,7 @@ import 'package:freezer/api/player/player_helper.dart';
import 'package:freezer/icons.dart'; import 'package:freezer/icons.dart';
import 'package:freezer/main.dart'; import 'package:freezer/main.dart';
import 'package:freezer/translations.i18n.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 '../api/definitions.dart';
import 'cached_image.dart'; import 'cached_image.dart';
@ -23,6 +23,93 @@ VoidCallback? normalizeSecondary(SecondaryTapCallback? callback) {
return () => callback.call(null); return () => callback.call(null);
} }
class TrackCardTile extends StatelessWidget {
static const _kDefaultWidth = 424.0;
final VoidCallback onTap;
/// Hold or Right Click
final SecondaryTapCallback? onSecondary;
final Widget? trailing;
final String trackId;
final String title;
final String artist;
final String artUri;
final bool explicit;
final double? width;
const TrackCardTile({
required this.trackId,
required this.title,
required this.artist,
required this.artUri,
required this.explicit,
required this.onTap,
this.onSecondary,
this.trailing,
this.width,
super.key,
});
factory TrackCardTile.fromTrack(
Track track, {
required VoidCallback onTap,
SecondaryTapCallback? onSecondary,
Widget? trailing,
double? width,
}) =>
TrackCardTile(
trackId: track.id,
title: track.title!,
artist: track.artistString,
artUri: track.albumArt!.thumb,
explicit: track.explicit ?? false,
onSecondary: onSecondary,
onTap: onTap,
width: width,
trailing: trailing,
);
@override
Widget build(BuildContext context) {
return SizedBox(
width: width ?? _kDefaultWidth,
height: 64.0,
child: Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
onLongPress: normalizeSecondary(onSecondary),
onSecondaryTapUp: onSecondary,
child: Row(mainAxisSize: MainAxisSize.min, children: [
CachedImage(url: artUri, rounded: false),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, overflow: TextOverflow.ellipsis, maxLines: 1),
Text(
artist,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: Theme.of(context).disabledColor),
)
]),
),
),
]),
),
),
);
}
}
class TrackTile extends StatelessWidget { class TrackTile extends StatelessWidget {
final VoidCallback? onTap; final VoidCallback? onTap;
@ -113,34 +200,39 @@ class TrackTile extends StatelessWidget {
artist, artist,
maxLines: 1, maxLines: 1,
), ),
leading: CachedImage( leading: StreamBuilder<MediaItem?>(
url: artUri, initialData: audioHandler.mediaItem.value,
width: 48.0, stream: audioHandler.mediaItem,
height: 48.0, builder: (context, snapshot) {
), final child = CachedImage(
// StreamBuilder<MediaItem?>( url: artUri,
// initialData: audioHandler.mediaItem.value, width: 48.0,
// stream: audioHandler.mediaItem, height: 48.0,
// builder: (context, snapshot) { );
// final child = CachedImage(
// url: artUri, if (snapshot.data?.id == trackId) {
// width: 48.0, return Stack(children: [
// height: 48.0, child,
// ); Positioned.fill(
// child: DecoratedBox(
// if (snapshot.data?.id == trackId) { decoration: const BoxDecoration(color: Colors.black26),
// return Stack(children: [ child: Center(
// child, child: SizedBox(
// const Positioned.fill( width: 18.0,
// child: DecoratedBox( height: 16.0,
// decoration: BoxDecoration(color: Colors.black26), child: StreamBuilder<bool>(
// child: AnimatedBars()), stream: playerHelper.playing,
// ), builder: (context, snapshot) {
// ]); return MiniMusicVisualizer(
// } color: Colors.white70,
// animate: snapshot.data ?? false);
// return child; })),
// }), )),
),
]);
}
return child;
}),
onTap: onTap, onTap: onTap,
onLongPress: normalizeSecondary(onSecondary), onLongPress: normalizeSecondary(onSecondary),
trailing: Row( trailing: Row(
@ -399,21 +491,34 @@ class PlaylistCardTile extends StatelessWidget {
class PlayItemButton extends StatelessWidget { class PlayItemButton extends StatelessWidget {
final FutureOr<void> Function() onTap; final FutureOr<void> Function() onTap;
final double size; final double size;
const PlayItemButton({required this.onTap, this.size = 32.0, super.key}); final bool filled;
const PlayItemButton({
required this.onTap,
this.size = 32.0,
this.filled = true,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox.square( return SizedBox.square(
dimension: size, dimension: size,
child: DecoratedBox( child: DecoratedBox(
decoration: const BoxDecoration( decoration: filled
shape: BoxShape.circle, color: Colors.white), ? const BoxDecoration(
shape: BoxShape.circle, color: Colors.white)
: const BoxDecoration(),
child: Center( child: Center(
child: AwaitingButton( child: AwaitingButton(
onTap: onTap, onTap: onTap,
child: Icon( child: Icon(
Icons.play_arrow, Icons.play_arrow,
color: Colors.black, color: filled ? Colors.black : Colors.white,
shadows: filled
? null
: const [
Shadow(blurRadius: 2.0, color: Colors.black)
],
size: size / 1.5, size: size / 1.5,
))))); )))));
} }

View file

@ -26,7 +26,7 @@ import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))

View file

@ -5,26 +5,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "61.0.0" version: "67.0.0"
analyzer: analyzer:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: analyzer name: analyzer
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.13.0" version: "6.4.1"
archive: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.10" version: "3.5.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -229,18 +229,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity_plus name: connectivity_plus
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0" sha256: db7a4e143dc72cc3cb2044ef9b052a7ebfe729513e6a82943bc3526f784365b8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.2" version: "6.0.3"
connectivity_plus_platform_interface: connectivity_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: connectivity_plus_platform_interface name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a sha256: b6a56efe1e6675be240de39107281d4034b64ac23438026355b4234042a35adb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.4" version: "2.0.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -269,10 +269,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3+8" version: "0.3.4+1"
crypto: crypto:
dependency: "direct main" dependency: "direct main"
description: description:
@ -371,7 +371,7 @@ packages:
source: hosted source: hosted
version: "2.0.5" version: "2.0.5"
fading_edge_scrollview: fading_edge_scrollview:
dependency: transitive dependency: "direct main"
description: description:
name: fading_edge_scrollview name: fading_edge_scrollview
sha256: c25c2231652ce774cc31824d0112f11f653881f43d7f5302c05af11942052031 sha256: c25c2231652ce774cc31824d0112f11f653881f43d7f5302c05af11942052031
@ -475,10 +475,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_cache_manager name: flutter_cache_manager
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" sha256: "395d6b7831f21f3b989ebedbb785545932adb9afe2622c1ffacf7f4b53a7e544"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.1" version: "3.3.2"
flutter_cache_manager_hive: flutter_cache_manager_hive:
dependency: "direct main" dependency: "direct main"
description: description:
@ -508,10 +508,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: "55b9b229307a10974b26296ff29f2e132256ba4bd74266939118eaefa941cb00" sha256: "8cdc719114ab1c86c64bb7a86d3a679674c3637edd229e3a994797d4a1504ce4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "16.3.3" version: "17.1.0"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
@ -683,10 +683,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -707,10 +707,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: i18n_extension name: i18n_extension
sha256: "813da89a434e617e3065a5d729f148a4d2d93d227e4d1ed4e77660eda6fa58c2" sha256: "514fca4f34e8eb73cd29d2938225bf139b1ff3cede462c0b2875f60f470e64b2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.3" version: "11.0.12"
i18n_extension_core:
dependency: transitive
description:
name: i18n_extension_core
sha256: f45157bcd04d4fd88811e8e7c104a66ffbf77d16a6cf8526197c30cb78cbf1c4
url: "https://pub.dev"
source: hosted
version: "2.0.6"
i18n_extension_importer: i18n_extension_importer:
dependency: "direct main" dependency: "direct main"
description: description:
@ -755,26 +763,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: isar name: isar
sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" sha256: "01de08cdea6e2060987e75bdedea46f244d9657fcd0e73b4372d6309b0e60f73"
url: "https://pub.dev" url: "https://pub.isar-community.dev"
source: hosted source: hosted
version: "3.1.0+1" version: "3.1.6"
isar_flutter_libs: isar_flutter_libs:
dependency: "direct main" dependency: "direct main"
description: description:
name: isar_flutter_libs name: isar_flutter_libs
sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 sha256: "268ef3fa93f213096f34e8f7f91ad16d920b33631fbdae317f3eb29f90e95b85"
url: "https://pub.dev" url: "https://pub.isar-community.dev"
source: hosted source: hosted
version: "3.1.0+1" version: "3.1.6"
isar_generator: isar_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: isar_generator name: isar_generator
sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" sha256: d2bbd77573c60ae1eddc323a20020223ba161e5a06e84143ea659411fb73b574
url: "https://pub.dev" url: "https://pub.isar-community.dev"
source: hosted source: hosted
version: "3.1.0+1" version: "3.1.6"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -943,6 +951,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
mini_music_visualizer:
dependency: "direct main"
description:
name: mini_music_visualizer
sha256: "095b3c5e12f4c045544432829a044a7fe1c5a7789a6d0003347c73d9bf3170cc"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
move_to_background: move_to_background:
dependency: "direct main" dependency: "direct main"
description: description:
@ -963,18 +979,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: network_info_plus name: network_info_plus
sha256: "4601b815b1c6a46d84839f65cd774a7d999738471d910fae00d813e9e98b04e1" sha256: "5bd4b86e28fed5ed4e6ac7764133c031dfb7d3f46aa2a81b46f55038aa78ecc0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0+1" version: "5.0.3"
network_info_plus_platform_interface: network_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: network_info_plus_platform_interface name: network_info_plus_platform_interface
sha256: "881f5029c5edaf19c616c201d3d8b366c5b1384afd5c1da5a49e4345de82fb8b" sha256: "2e193d61d3072ac17824638793d3b89c6d581ce90c11604f4ca87311b42f2706"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.3" version: "2.0.0"
nm: nm:
dependency: transitive dependency: transitive
description: description:
@ -1027,18 +1043,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" sha256: "2c582551839386fa7ddbc7770658be7c0f87f388a4bff72066478f597c34d17f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.1" version: "7.0.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.0"
palette_generator: palette_generator:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1171,10 +1187,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: photo_view name: photo_view
sha256: "8036802a00bae2a78fc197af8a158e3e2f7b500561ed23b4c458107685e645bb" sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.14.0" version: "0.15.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -1308,18 +1324,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900" sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.2" version: "9.0.0"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496" sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.0" version: "4.0.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -1377,10 +1393,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: spotify name: spotify
sha256: "50bd5a07b580ee441d0b4d81227185ada768332c353671aa7555ea47cc68eb9e" sha256: d8effb9fa14731fe98fc5d111e0e1a0aac27b34366b5dcb8db79e6dd2219c000
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.5" version: "0.13.6+1"
sprintf: sprintf:
dependency: transitive dependency: transitive
description: description:
@ -1601,10 +1617,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.3" version: "2.3.1"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@ -1649,10 +1665,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: wakelock_plus name: wakelock_plus
sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d sha256: c8b7cc80f045533b40a0e6c9109905494e3cf32c0fbd5c62616998e0de44003f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.4" version: "1.2.4"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1673,18 +1689,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.2" version: "0.5.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "2.4.5"
webview_flutter: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -35,45 +35,45 @@ dependencies:
path: ^1.6.4 path: ^1.6.4
sqflite: ^2.0.0+3 sqflite: ^2.0.0+3
permission_handler: ^11.2.0 permission_handler: ^11.2.0
intl: ^0.18.0 intl: ^0.18.1
filesize: ^2.0.1 filesize: ^2.0.1
fluttertoast: ^8.0.8 fluttertoast: ^8.0.8
palette_generator: ^0.3.0 palette_generator: ^0.3.0
flutter_material_color_picker: ^1.0.5 flutter_material_color_picker: ^1.0.5
country_pickers: ^2.0.0 country_pickers: ^2.0.0
move_to_background: ^1.0.1 move_to_background: ^1.0.1
flutter_local_notifications: ^16.3.2 flutter_local_notifications: ^17.1.0
collection: ^1.17.1 collection: ^1.17.1
random_string: ^2.0.1 random_string: ^2.0.1
async: ^2.6.1 async: ^2.6.1
html: ^0.15.0 html: ^0.15.0
flutter_screenutil: ^5.0.0+2 flutter_screenutil: ^5.0.0+2
marquee: ^2.2.0 marquee: ^2.2.0
flutter_cache_manager: ^3.0.0 flutter_cache_manager: ^3.3.2
cached_network_image: ^3.1.0 cached_network_image: ^3.1.0
i18n_extension: ^10.0.3 i18n_extension: ^11.0.12
url_launcher: ^6.0.5 url_launcher: ^6.0.5
uni_links: ^0.5.1 uni_links: ^0.5.1
numberpicker: ^2.1.1 numberpicker: ^2.1.1
quick_actions: ^1.0.5 quick_actions: ^1.0.5
photo_view: ^0.14.0 photo_view: ^0.15.0
scrobblenaut: scrobblenaut:
git: git:
url: https://github.com/Pato05/Scrobblenaut.git url: https://github.com/Pato05/Scrobblenaut.git
ref: main ref: main
open_file: ^3.0.3 open_file: ^3.0.3
version: ^3.0.2 version: ^3.0.2
wakelock_plus: ^1.1.1 wakelock_plus: ^1.2.4
google_fonts: ^6.1.0 google_fonts: ^6.1.0
audio_session: ^0.1.6 audio_session: ^0.1.6
audio_service: ^0.18.1 audio_service: ^0.18.1
provider: ^6.0.0 provider: ^6.0.0
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
connectivity_plus: ^5.0.2 connectivity_plus: ^6.0.3
share_plus: ^7.0.2 share_plus: ^9.0.0
disk_space_plus: ^0.2.3 disk_space_plus: ^0.2.3
dynamic_color: ^1.6.6 dynamic_color: ^1.6.6
package_info_plus: ^5.0.1 package_info_plus: ^7.0.0
encrypt: ^5.0.1 encrypt: ^5.0.1
dart_blowfish: dart_blowfish:
git: git:
@ -93,8 +93,12 @@ dependencies:
audio_service_mpris: ^0.1.3 audio_service_mpris: ^0.1.3
rxdart: ^0.27.7 rxdart: ^0.27.7
isar: ^3.1.0+1 isar:
isar_flutter_libs: ^3.1.0+1 hosted: https://pub.isar-community.dev
version: ^3.1.6
isar_flutter_libs:
hosted: https://pub.isar-community.dev
version: ^3.1.6
flutter_background_service: ^5.0.1 flutter_background_service: ^5.0.1
dio: ^5.3.3 dio: ^5.3.3
dio_cookie_manager: ^3.1.1 dio_cookie_manager: ^3.1.1
@ -102,7 +106,7 @@ dependencies:
git: https://github.com/Pato05/flutter_cache_manager_hive.git git: https://github.com/Pato05/flutter_cache_manager_hive.git
flex_color_picker: ^3.3.0 flex_color_picker: ^3.3.0
webview_flutter: ^4.4.4 webview_flutter: ^4.4.4
network_info_plus: ^4.1.0+1 network_info_plus: ^5.0.3
pointycastle: ^3.7.4 pointycastle: ^3.7.4
i18n_extension_importer: ^0.0.6 i18n_extension_importer: ^0.0.6
tray_manager: ^0.2.1 tray_manager: ^0.2.1
@ -110,18 +114,22 @@ dependencies:
get_it: ^7.6.7 get_it: ^7.6.7
freezed_annotation: freezed_annotation:
^2.4.1 ^2.4.1
mini_music_visualizer: ^1.1.0
fading_edge_scrollview: ^3.0.0
#deezcryptor: #deezcryptor:
#path: deezcryptor/ #path: deezcryptor/
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
analyzer: ^5.13.0 analyzer: ^6.4.1
json_serializable: ^6.0.1 json_serializable: ^6.0.1
build_runner: ^2.4.6 build_runner: ^2.4.6
hive_generator: ^2.0.0 hive_generator: ^2.0.0
flutter_lints: ^3.0.1 flutter_lints: ^3.0.1
isar_generator: ^3.1.0+1 isar_generator:
hosted: https://pub.isar-community.dev
version: ^3.1.0
freezed: ^2.4.7 freezed: ^2.4.7
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the