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

View File

@ -255,7 +255,7 @@ class AudioPlayerTask extends BaseAudioHandler {
// listen for connectivity changes
_subscriptions.add(Connectivity()
.onConnectivityChanged
.listen(_determineAudioQualityByResult));
.listen(_determineAudioQualityByResults));
}
if (shouldLoadQueue) {
@ -271,7 +271,7 @@ class AudioPlayerTask extends BaseAudioHandler {
try {
await Connectivity()
.checkConnectivity()
.then(_determineAudioQualityByResult);
.then(_determineAudioQualityByResults);
return true;
} catch (e) {
_isConnectivityPluginAvailable = false;
@ -282,12 +282,13 @@ class AudioPlayerTask extends BaseAudioHandler {
_logger.warning(
'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
_determineAudioQualityByResult(ConnectivityResult.other);
_determineAudioQualityByResults([ConnectivityResult.other]);
return false;
}
/// Determines the [AudioQuality] to use according to [result]
void _determineAudioQualityByResult(ConnectivityResult result) {
void _determineAudioQualityByResults(List<ConnectivityResult> results) {
final result = results[0];
switch (result) {
case ConnectivityResult.mobile:
case ConnectivityResult.bluetooth:

View File

@ -31,7 +31,7 @@ import 'package:freezer/ui/search.dart';
import 'package:freezer/ui/settings_screen.dart';
import 'package:get_it/get_it.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:freezer/translations.i18n.dart';
import 'package:quick_actions/quick_actions.dart';
@ -287,14 +287,15 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
super.initState();
}
Future _logOut() async {
Future<void> _logOut() async {
await GetIt.instance<DeezerAPI>().logout();
setState(() {
settings.arl = null;
settings.offlineMode = false;
});
settings.arl = null;
settings.offlineMode = false;
await settings.save();
await Cache.wipe();
setState(() {});
}
@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,
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(
CurvedAnimation(
parent: dragController,
curve: const Interval(0.0, 0.25))),
child: SizedBox(
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/material.dart';
import 'package:flutter/services.dart';
@ -210,7 +211,7 @@ class _HomePageWidgetState extends State<HomePageWidget> {
return HomePageGridSection(section);
case HomePageSectionLayout.slideshow:
case HomePageSectionLayout.row:
default:
case HomePageSectionLayout.horizontalList:
return HomepageRowSection(section);
}
}
@ -243,72 +244,90 @@ class _HomePageWidgetState extends State<HomePageWidget> {
}
}
class HomepageRowSection extends StatefulWidget {
class HomepageRowSection extends StatelessWidget {
final HomePageSection section;
const HomepageRowSection(this.section, {super.key});
@override
State<HomepageRowSection> createState() => _HomepageRowSectionState();
}
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))
]);
}
class _HomepageRowSectionState extends State<HomepageRowSection> {
final _controller = ScrollController();
List<List<T>> _sliceInNLists<T>(List<T> source, int n) {
final List<List<T>> dest = List.generate(n, (_) => [], growable: false);
@override
void dispose() {
_controller.dispose();
super.dispose();
int i = 0;
for (var item in source) {
dest[i].add(item);
if (++i == n) i = 0;
}
return dest;
}
@override
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(
title: Text(
widget.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(
controller: _controller,
thickness: MainScreen.of(context).isDesktop ? null : 1.0,
child: SingleChildScrollView(
controller: _controller,
scrollDirection: Axis.horizontal,
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),
);
}),
),
),
));
),
subtitle: Scrollbar(
thickness: MainScreen.of(context).isDesktop ? null : 1.0,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: child,
),
),
);
}
}
@ -368,6 +387,7 @@ class HomePageItemWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
switch (item.type) {
case HomePageItemType.SMARTTRACKLIST:
return SmartTrackListTile(
@ -444,6 +464,16 @@ class HomePageItemWidget extends StatelessWidget {
.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:
return const SizedBox(height: 0, width: 0);
}

View File

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

View File

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

View File

@ -1,17 +1,21 @@
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';
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/settings.dart';
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 +25,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 +51,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 +71,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 +143,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 +163,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 +214,6 @@ class _LyricsWidgetState extends State<LyricsWidget>
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('fuck? $state');
switch (state) {
case AppLifecycleState.paused:
_cancelSubscriptions();
@ -240,59 +245,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 +295,30 @@ 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: 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:
@ -312,15 +329,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 +352,7 @@ class _LyricsWidgetState extends State<LyricsWidget>
? TextAlign.center
: TextAlign.start,
style: TextStyle(
color: textColor,
fontSize: _syncedLyrics
? 26.0
: 20.0,
@ -357,25 +382,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();
})))
],
);
}

View File

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

View File

@ -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,
)),
),
),
@ -195,7 +195,8 @@ class PlayerScreenBackground extends StatelessWidget {
end: Alignment.bottomCenter,
colors: [
if (provider.dominantColor != null)
provider.dominantColor!,
Color.lerp(provider.dominantColor,
isLightMode ? Colors.white : Colors.black, 0.5)!,
Theme.of(context).scaffoldBackgroundColor,
],
stops: const [0.0, 0.6],
@ -369,10 +370,7 @@ class PlayerScreenDesktop extends StatelessWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: PlayerScreenTopRow(
textSize: 12.h,
iconSize: 21.h,
showQueueButton: false,
),
textSize: 12.h, iconSize: 21.h, desktopMode: true),
),
Flexible(
child: ConstrainedBox(
@ -1049,7 +1047,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
right: 16.0,
child: LyricsButton(
onTap: _pushLyrics,
size: constraints.maxHeight / 20,
size: 20.spMax,
),
);
},
@ -1121,14 +1119,15 @@ class PlayerScreenTopRow extends StatelessWidget {
final double? iconSize;
final double? textWidth;
final bool short;
final bool showQueueButton; // not needed on desktop
const PlayerScreenTopRow(
{super.key,
this.textSize,
this.iconSize,
this.textWidth,
this.short = false,
this.showQueueButton = true});
final bool desktopMode;
const PlayerScreenTopRow({
super.key,
this.textSize,
this.iconSize,
this.textWidth,
this.short = false,
this.desktopMode = false,
});
@override
Widget build(BuildContext context) {
@ -1175,20 +1174,9 @@ class PlayerScreenTopRow extends StatelessWidget {
.copyWith(fontSize: textSize ?? 38.sp)));
}),
),
showQueueButton
? IconButton(
icon: Icon(
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),
desktopMode
? PlayerMenuButtonDesktop(size: size)
: PlayerMenuButton(size: size)
],
);
}
@ -1329,7 +1317,19 @@ class BottomBarControls extends StatelessWidget {
textSize: size * 0.75,
),
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);
// },
// ),
desktopMode
? PlayerMenuButtonDesktop(size: iconSize)
: PlayerMenuButton(size: iconSize)
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,
)),
),
],
);
}

View File

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

View File

@ -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';
@ -23,6 +23,93 @@ VoidCallback? normalizeSecondary(SecondaryTapCallback? callback) {
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 {
final VoidCallback? onTap;
@ -113,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(
@ -399,21 +491,34 @@ class PlaylistCardTile extends StatelessWidget {
class PlayItemButton extends StatelessWidget {
final FutureOr<void> Function() onTap;
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
Widget build(BuildContext context) {
return SizedBox.square(
dimension: size,
child: DecoratedBox(
decoration: const BoxDecoration(
shape: BoxShape.circle, color: Colors.white),
decoration: filled
? const BoxDecoration(
shape: BoxShape.circle, color: Colors.white)
: const BoxDecoration(),
child: Center(
child: AwaitingButton(
onTap: onTap,
child: Icon(
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,
)))));
}

View File

@ -26,7 +26,7 @@ import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
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"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))

View File

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

View File

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