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