save more battery now, few other changes idr
This commit is contained in:
parent
faec2af805
commit
8679cf844f
|
@ -146,37 +146,40 @@ class DeezerAPI {
|
|||
//Wrapper so it can be globally awaited
|
||||
Future<bool> authorize() async => _authorizing ??= rawAuthorize();
|
||||
|
||||
//Login with email FROM DEEMIX-JS
|
||||
Future<String> getArlByEmail(String email, String password) async {
|
||||
//Get MD5 of password
|
||||
final md5Password = md5.convert(utf8.encode(password)).toString();
|
||||
final hash = md5
|
||||
.convert(utf8
|
||||
.encode([CLIENT_ID, email, md5Password, CLIENT_SECRET].join('')))
|
||||
.toString();
|
||||
//Get access token
|
||||
// String url =
|
||||
// "https://tv.deezer.com/smarttv/8caf9315c1740316053348a24d25afc7/user_auth.php?login=$email&password=$md5password&device=panasonic&output=json";
|
||||
// http.Response response = await http.get(Uri.parse(url));
|
||||
// String? accessToken = jsonDecode(response.body)["access_token"];
|
||||
final res = await dio.get('https://api.deezer.com/auth/token',
|
||||
queryParameters: {
|
||||
'app_id': CLIENT_ID,
|
||||
'login': email,
|
||||
'password': md5Password,
|
||||
'hash': hash
|
||||
},
|
||||
options: Options(responseType: ResponseType.json));
|
||||
print(res.data);
|
||||
final accessToken = res.data['access_token'] as String?;
|
||||
if (accessToken == null) {
|
||||
throw Exception('login failed, access token is null');
|
||||
}
|
||||
// NOT WORKING ANYMORE.
|
||||
// this didn't last very long now, did it?
|
||||
|
||||
print(accessToken);
|
||||
|
||||
return getArlByAccessToken(accessToken);
|
||||
}
|
||||
// //Login with email FROM DEEMIX-JS
|
||||
// Future<String> getArlByEmail(String email, String password) async {
|
||||
// //Get MD5 of password
|
||||
// final md5Password = md5.convert(utf8.encode(password)).toString();
|
||||
// final hash = md5
|
||||
// .convert(utf8
|
||||
// .encode([CLIENT_ID, email, md5Password, CLIENT_SECRET].join('')))
|
||||
// .toString();
|
||||
// //Get access token
|
||||
// // String url =
|
||||
// // "https://tv.deezer.com/smarttv/8caf9315c1740316053348a24d25afc7/user_auth.php?login=$email&password=$md5password&device=panasonic&output=json";
|
||||
// // http.Response response = await http.get(Uri.parse(url));
|
||||
// // String? accessToken = jsonDecode(response.body)["access_token"];
|
||||
// final res = await dio.get('https://api.deezer.com/auth/token',
|
||||
// queryParameters: {
|
||||
// 'app_id': CLIENT_ID,
|
||||
// 'login': email,
|
||||
// 'password': md5Password,
|
||||
// 'hash': hash
|
||||
// },
|
||||
// options: Options(responseType: ResponseType.json));
|
||||
// print(res.data);
|
||||
// final accessToken = res.data['access_token'] as String?;
|
||||
// if (accessToken == null) {
|
||||
// throw Exception('login failed, access token is null');
|
||||
// }
|
||||
//
|
||||
// print(accessToken);
|
||||
//
|
||||
// return getArlByAccessToken(accessToken);
|
||||
// }
|
||||
|
||||
// FROM DEEMIX-JS
|
||||
Future<String> getArlByAccessToken(String accessToken) async {
|
||||
|
|
|
@ -14,15 +14,20 @@ class Paths {
|
|||
final target =
|
||||
await Directory(path.join(home, '.local', 'share', 'freezer'))
|
||||
.create();
|
||||
if (kDebugMode) {
|
||||
return (await Directory(path.join(target.path, 'debug')).create())
|
||||
.path;
|
||||
}
|
||||
return target.path;
|
||||
}
|
||||
return path.dirname(Platform.resolvedExecutable);
|
||||
case TargetPlatform.windows:
|
||||
final String? localAppData = Platform.environment['LOCALAPPDATA'];
|
||||
if (localAppData != null) {
|
||||
final target = await Directory(path.join(localAppData, 'Freezer')).create();
|
||||
return target.path;
|
||||
}
|
||||
final String? localAppData = Platform.environment['LOCALAPPDATA'];
|
||||
if (localAppData != null) {
|
||||
final target =
|
||||
await Directory(path.join(localAppData, 'Freezer')).create();
|
||||
return target.path;
|
||||
}
|
||||
String? home = Platform.environment['USERPROFILE'];
|
||||
if (home == null) {
|
||||
final drive = Platform.environment['HOMEDRIVE'];
|
||||
|
@ -35,7 +40,12 @@ class Paths {
|
|||
}
|
||||
|
||||
final target =
|
||||
await Directory(path.join(home, 'AppData', 'Local', 'Freezer')).create();
|
||||
await Directory(path.join(home, 'AppData', 'Local', 'Freezer'))
|
||||
.create();
|
||||
if (kDebugMode) {
|
||||
return (await Directory(path.join(target.path, 'debug')).create())
|
||||
.path;
|
||||
}
|
||||
return target.path;
|
||||
default:
|
||||
return (await getApplicationDocumentsDirectory()).path;
|
||||
|
|
|
@ -171,8 +171,8 @@ class AudioPlayerTask extends BaseAudioHandler {
|
|||
);
|
||||
|
||||
if (initArgs.ignoreInterruptions) {
|
||||
session.interruptionEventStream.listen((_) {});
|
||||
session.becomingNoisyEventStream.listen((_) {});
|
||||
//session.interruptionEventStream.listen((_) {});
|
||||
//session.becomingNoisyEventStream.listen((_) {});
|
||||
}
|
||||
|
||||
//Update track index
|
||||
|
|
|
@ -39,6 +39,13 @@ class PlayerHelper {
|
|||
final _bufferPositionSubject = BehaviorSubject<Duration>();
|
||||
ValueStream<Duration> get bufferPosition => _bufferPositionSubject.stream;
|
||||
|
||||
final _playingSubject = BehaviorSubject<bool>();
|
||||
ValueStream<bool> get playing => _playingSubject.stream;
|
||||
|
||||
final _processingStateSubject = BehaviorSubject<AudioProcessingState>();
|
||||
ValueStream<AudioProcessingState> get processingState =>
|
||||
_processingStateSubject.stream;
|
||||
|
||||
/// Find queue index by id
|
||||
///
|
||||
/// The function gets more expensive the longer the queue is and the further the element is from the beginning.
|
||||
|
@ -60,13 +67,13 @@ class PlayerHelper {
|
|||
builder: () => AudioPlayerTask(initArgs),
|
||||
config: AudioServiceConfig(
|
||||
notificationColor: settings.primaryColor,
|
||||
androidStopForegroundOnPause: false,
|
||||
androidNotificationOngoing: false,
|
||||
androidStopForegroundOnPause: true,
|
||||
androidNotificationOngoing: true,
|
||||
androidNotificationClickStartsActivity: true,
|
||||
androidNotificationChannelDescription: 'Freezer',
|
||||
androidNotificationChannelName: 'Freezer',
|
||||
androidNotificationIcon: 'drawable/ic_logo',
|
||||
preloadArtwork: false,
|
||||
preloadArtwork: true,
|
||||
),
|
||||
cacheManager: cacheManager,
|
||||
);
|
||||
|
@ -81,8 +88,6 @@ class PlayerHelper {
|
|||
Logger('PlayerHelper').fine("event received: ${event['action']}");
|
||||
switch (event['action']) {
|
||||
case 'onLoad':
|
||||
//After audio_service is loaded, load queue, set quality
|
||||
await settings.updateAudioServiceQuality();
|
||||
break;
|
||||
case 'onRestore':
|
||||
//Load queueSource from isolate
|
||||
|
@ -140,6 +145,21 @@ class PlayerHelper {
|
|||
cache.history.add(Track.fromMediaItem(mediaItem));
|
||||
cache.save();
|
||||
});
|
||||
_playbackStateStreamSubscription =
|
||||
audioHandler.playbackState.listen((playbackState) {
|
||||
if (!_processingStateSubject.hasValue ||
|
||||
_processingStateSubject.value != playbackState.processingState) {
|
||||
_processingStateSubject.add(playbackState.processingState);
|
||||
}
|
||||
|
||||
print(
|
||||
'now ${playbackState.playing}, previous ${_playingSubject.valueOrNull}');
|
||||
if (!_playingSubject.hasValue ||
|
||||
_playingSubject.value != playbackState.playing) {
|
||||
print('added!');
|
||||
_playingSubject.add(playbackState.playing);
|
||||
}
|
||||
});
|
||||
|
||||
//Start audio_service
|
||||
// await startService(); it is already ready, there is no need to start it
|
||||
|
@ -178,16 +198,15 @@ class PlayerHelper {
|
|||
}
|
||||
|
||||
//Executed before exit
|
||||
Future onExit() async {
|
||||
Future stop() async {
|
||||
_customEventSubscription.cancel();
|
||||
_playbackStateStreamSubscription.cancel();
|
||||
_mediaItemSubscription.cancel();
|
||||
_started = false;
|
||||
}
|
||||
|
||||
//Replace queue, play specified track id
|
||||
Future<void> _loadQueuePlay(List<MediaItem> queue, int? index) async {
|
||||
await settings.updateAudioServiceQuality();
|
||||
|
||||
if (index != null) {
|
||||
await audioHandler.customAction('setIndex', {'index': index});
|
||||
}
|
||||
|
@ -270,7 +289,6 @@ class PlayerHelper {
|
|||
|
||||
//Load and play
|
||||
// await startService(); // audioservice is ready
|
||||
await settings.updateAudioServiceQuality();
|
||||
await setQueueSource(queueSource);
|
||||
await audioHandler.customAction('setIndex', {'index': index});
|
||||
await audioHandler.updateQueue(queue);
|
||||
|
|
|
@ -113,7 +113,7 @@ void main() async {
|
|||
DefaultCacheManager.key,
|
||||
// cache aggressively
|
||||
stalePeriod: const Duration(days: 30),
|
||||
maxNrOfCacheObjects: 1000,
|
||||
maxNrOfCacheObjects: 5000,
|
||||
));
|
||||
// cacheManager = HiveCacheManager(
|
||||
// boxName: 'freezer-images', boxPath: await Paths.cacheDir());
|
||||
|
@ -142,32 +142,32 @@ class FreezerApp extends StatefulWidget {
|
|||
State<FreezerApp> createState() => _FreezerAppState();
|
||||
}
|
||||
|
||||
class _FreezerAppState extends State<FreezerApp> {
|
||||
late StreamSubscription _playbackStateSub;
|
||||
|
||||
class _FreezerAppState extends State<FreezerApp> with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
_initStateAsync();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _initStateAsync() async {
|
||||
_playbackStateChanged(audioHandler.playbackState.value);
|
||||
_playbackStateSub =
|
||||
audioHandler.playbackState.listen(_playbackStateChanged);
|
||||
}
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.paused:
|
||||
playerHelper.stop();
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
playerHelper.start();
|
||||
break;
|
||||
|
||||
Future<void> _playbackStateChanged(PlaybackState playbackState) async {
|
||||
if (playbackState.processingState == AudioProcessingState.idle ||
|
||||
playbackState.processingState == AudioProcessingState.error) {
|
||||
// TODO: reconnect maybe?
|
||||
return;
|
||||
default:
|
||||
print('lifecycle: $state');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_playbackStateSub.cancel();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
playerHelper.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -191,13 +191,35 @@ class _FreezerAppState extends State<FreezerApp> {
|
|||
builder: (context, child) =>
|
||||
DynamicColorBuilder(builder: (lightScheme, darkScheme) {
|
||||
final lightTheme = settings.materialYouAccent
|
||||
? ThemeData(colorScheme: lightScheme, useMaterial3: true)
|
||||
? ThemeData(
|
||||
colorScheme: lightScheme,
|
||||
useMaterial3: true,
|
||||
appBarTheme: const AppBarTheme(
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarBrightness: Brightness.dark,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
)),
|
||||
)
|
||||
: settings.themeData;
|
||||
final darkTheme = settings.materialYouAccent
|
||||
? ThemeData(
|
||||
colorScheme: darkScheme,
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark)
|
||||
brightness: Brightness.dark,
|
||||
appBarTheme: const AppBarTheme(
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarBrightness: Brightness.light,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
)),
|
||||
)
|
||||
: null;
|
||||
return MaterialApp(
|
||||
title: 'Freezer',
|
||||
|
@ -269,13 +291,13 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
|||
}
|
||||
|
||||
Future _logOut() async {
|
||||
await deezerAPI.logout();
|
||||
await settings.save();
|
||||
await Cache.wipe();
|
||||
setState(() {
|
||||
settings.arl = null;
|
||||
settings.offlineMode = false;
|
||||
});
|
||||
await deezerAPI.logout();
|
||||
await settings.save();
|
||||
await Cache.wipe();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -672,18 +694,17 @@ class MainScreenState extends State<MainScreen>
|
|||
onKey: _handleKey,
|
||||
child: LayoutBuilder(builder: (context, constraints) {
|
||||
// check if we're able to display the desktop layout
|
||||
if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
|
||||
final isLandscape = constraints.maxWidth > constraints.maxHeight;
|
||||
isDesktop = isLandscape &&
|
||||
constraints.maxWidth >= 1100 &&
|
||||
constraints.maxHeight >= 600;
|
||||
}
|
||||
final isLandscape = constraints.maxWidth > constraints.maxHeight;
|
||||
isDesktop = isLandscape &&
|
||||
constraints.maxWidth >= 1100 &&
|
||||
constraints.maxHeight >= 600;
|
||||
return FancyScaffold(
|
||||
key: _fancyScaffoldKey,
|
||||
navigationRail: _buildNavigationRail(isDesktop),
|
||||
bottomNavigationBar: buildBottomBar(isDesktop),
|
||||
bottomPanel: Builder(
|
||||
builder: (context) => PlayerBar(
|
||||
backgroundColor: Theme.of(context).cardColor,
|
||||
focusNode: playerBarFocusNode,
|
||||
onTap: FancyScaffold.of(context)!.openPanel,
|
||||
shouldHaveHero: false,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezer/api/definitions.dart';
|
||||
import 'package:freezer/api/download.dart';
|
||||
import 'package:freezer/api/player/audio_handler.dart';
|
||||
|
@ -170,6 +171,9 @@ class Settings {
|
|||
NavigationRailAppearance navigationRailAppearance =
|
||||
NavigationRailAppearance.expand_on_hover;
|
||||
|
||||
@HiveField(49, defaultValue: true)
|
||||
bool enableMaterial3PlayButton = true;
|
||||
|
||||
static LazyBox<Settings>? __box;
|
||||
static Future<LazyBox<Settings>> get _box async =>
|
||||
__box ??= await Hive.openLazyBox<Settings>('settings');
|
||||
|
@ -267,12 +271,6 @@ class Settings {
|
|||
downloadManager.updateServiceSettings();
|
||||
}
|
||||
|
||||
Future<void> updateAudioServiceQuality() async {
|
||||
//Send wifi & mobile quality to audio service isolate
|
||||
await audioHandler.customAction('updateQuality',
|
||||
{'mobileQuality': mobileQuality, 'wifiQuality': wifiQuality});
|
||||
}
|
||||
|
||||
// MaterialColor get _primarySwatch =>
|
||||
// MaterialColor(primaryColor.value, <int, Color>{
|
||||
// 50: primaryColor.withOpacity(.1),
|
||||
|
@ -319,6 +317,15 @@ class Settings {
|
|||
sliderTheme: _sliderTheme,
|
||||
bottomAppBarTheme: const BottomAppBarTheme(color: Color(0xfff5f5f5)),
|
||||
useMaterial3: true,
|
||||
appBarTheme: const AppBarTheme(
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarBrightness: Brightness.dark,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
)),
|
||||
),
|
||||
Themes.Dark: ThemeData(
|
||||
textTheme: textTheme,
|
||||
|
@ -331,6 +338,15 @@ class Settings {
|
|||
),
|
||||
sliderTheme: _sliderTheme,
|
||||
useMaterial3: true,
|
||||
appBarTheme: const AppBarTheme(
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarBrightness: Brightness.light,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
)),
|
||||
),
|
||||
Themes.Deezer: ThemeData(
|
||||
textTheme: textTheme,
|
||||
|
@ -349,6 +365,15 @@ class Settings {
|
|||
const BottomSheetThemeData(backgroundColor: deezerBottom),
|
||||
cardColor: deezerBg,
|
||||
useMaterial3: true,
|
||||
appBarTheme: const AppBarTheme(
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarBrightness: Brightness.light,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
)),
|
||||
),
|
||||
Themes.Black: ThemeData(
|
||||
textTheme: textTheme,
|
||||
|
@ -370,6 +395,15 @@ class Settings {
|
|||
BottomSheetThemeData(backgroundColor: _elevation1Black),
|
||||
cardColor: _elevation1Black,
|
||||
useMaterial3: true,
|
||||
appBarTheme: const AppBarTheme(
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarBrightness: Brightness.light,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
)),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezer/api/deezer.dart';
|
||||
import 'package:freezer/api/definitions.dart';
|
||||
import 'package:freezer/api/player/audio_handler.dart';
|
||||
|
@ -54,15 +55,19 @@ class HomeScreen extends StatelessWidget {
|
|||
PointerDeviceKind.trackpad
|
||||
},
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverPersistentHeader(
|
||||
delegate: _SearchHeaderDelegate(), floating: true)
|
||||
],
|
||||
body: const HomePageWidget(cacheable: true),
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: Theme.of(context).appBarTheme.systemOverlayStyle ??
|
||||
const SystemUiOverlayStyle(),
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverPersistentHeader(
|
||||
delegate: _SearchHeaderDelegate(), floating: true)
|
||||
],
|
||||
body: const HomePageWidget(cacheable: true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -209,19 +209,19 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
style: const TextStyle(fontSize: 16.0),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
//Email login dialog
|
||||
ElevatedButton(
|
||||
child: Text(
|
||||
'Login using email'.i18n,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
EmailLogin(_update));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 2.0),
|
||||
//Email login dialog (Not working anymore)
|
||||
// ElevatedButton(
|
||||
// child: Text(
|
||||
// 'Login using email'.i18n,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (context) =>
|
||||
// EmailLogin(_update));
|
||||
// },
|
||||
// ),
|
||||
// const SizedBox(height: 2.0),
|
||||
|
||||
// only supported on android
|
||||
if (Platform.isAndroid)
|
||||
|
@ -345,7 +345,7 @@ class LoginBrowser extends StatelessWidget {
|
|||
controller.evaluateJavascript(
|
||||
source: 'window.location.href = "/open_app"');
|
||||
}
|
||||
print('scheme ${uri.scheme}, host: ${uri.host}');
|
||||
print('uri $uri');
|
||||
//Parse arl from url
|
||||
if (uri.scheme == 'intent' && uri.host == 'deezer.page.link') {
|
||||
try {
|
||||
|
@ -367,107 +367,109 @@ class LoginBrowser extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class EmailLogin extends StatefulWidget {
|
||||
final Function callback;
|
||||
const EmailLogin(this.callback, {Key? key}) : super(key: key);
|
||||
// email login is removed cuz not working = USELESS
|
||||
|
||||
@override
|
||||
State<EmailLogin> createState() => _EmailLoginState();
|
||||
}
|
||||
|
||||
class _EmailLoginState extends State<EmailLogin> {
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
bool _loading = false;
|
||||
|
||||
Future _login() async {
|
||||
setState(() => _loading = true);
|
||||
//Try logging in
|
||||
String? arl;
|
||||
String? exception;
|
||||
try {
|
||||
arl = await deezerAPI.getArlByEmail(
|
||||
_emailController.text, _passwordController.text);
|
||||
} catch (e, st) {
|
||||
exception = e.toString();
|
||||
print(e);
|
||||
print(st);
|
||||
}
|
||||
setState(() => _loading = false);
|
||||
|
||||
//Success
|
||||
if (arl != null) {
|
||||
settings.arl = arl;
|
||||
Navigator.of(context).pop();
|
||||
widget.callback();
|
||||
return;
|
||||
}
|
||||
|
||||
//Error
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text("Error logging in!".i18n),
|
||||
content: Text(
|
||||
"Error logging in using email, please check your credentials.\nError: ${exception ?? 'Unknown'}"),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Dismiss'.i18n),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Email Login'.i18n),
|
||||
content: AutofillGroup(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextFormField(
|
||||
enabled: !_loading,
|
||||
decoration: InputDecoration(labelText: 'Email'.i18n),
|
||||
controller: _emailController,
|
||||
autofillHints: const [
|
||||
AutofillHints.email,
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
TextFormField(
|
||||
enabled: !_loading,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(labelText: "Password".i18n),
|
||||
controller: _passwordController,
|
||||
autofillHints: const [
|
||||
AutofillHints.password,
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _loading
|
||||
? null
|
||||
: () async {
|
||||
if (_emailController.text.isNotEmpty &&
|
||||
_passwordController.text.isNotEmpty) {
|
||||
await _login();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context)
|
||||
.snack("Missing email or password!".i18n);
|
||||
}
|
||||
},
|
||||
child: _loading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('Login'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
//class EmailLogin extends StatefulWidget {
|
||||
// final Function callback;
|
||||
// const EmailLogin(this.callback, {Key? key}) : super(key: key);
|
||||
//
|
||||
// @override
|
||||
// State<EmailLogin> createState() => _EmailLoginState();
|
||||
//}
|
||||
//
|
||||
//class _EmailLoginState extends State<EmailLogin> {
|
||||
// final _emailController = TextEditingController();
|
||||
// final _passwordController = TextEditingController();
|
||||
// bool _loading = false;
|
||||
//
|
||||
// Future _login() async {
|
||||
// setState(() => _loading = true);
|
||||
// //Try logging in
|
||||
// String? arl;
|
||||
// String? exception;
|
||||
// try {
|
||||
// arl = await deezerAPI.getArlByEmail(
|
||||
// _emailController.text, _passwordController.text);
|
||||
// } catch (e, st) {
|
||||
// exception = e.toString();
|
||||
// print(e);
|
||||
// print(st);
|
||||
// }
|
||||
// setState(() => _loading = false);
|
||||
//
|
||||
// //Success
|
||||
// if (arl != null) {
|
||||
// settings.arl = arl;
|
||||
// Navigator.of(context).pop();
|
||||
// widget.callback();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// //Error
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (context) => AlertDialog(
|
||||
// title: Text("Error logging in!".i18n),
|
||||
// content: Text(
|
||||
// "Error logging in using email, please check your credentials.\nError: ${exception ?? 'Unknown'}"),
|
||||
// actions: [
|
||||
// TextButton(
|
||||
// child: Text('Dismiss'.i18n),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pop();
|
||||
// },
|
||||
// )
|
||||
// ],
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return AlertDialog(
|
||||
// title: Text('Email Login'.i18n),
|
||||
// content: AutofillGroup(
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// TextFormField(
|
||||
// enabled: !_loading,
|
||||
// decoration: InputDecoration(labelText: 'Email'.i18n),
|
||||
// controller: _emailController,
|
||||
// autofillHints: const [
|
||||
// AutofillHints.email,
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(height: 8.0),
|
||||
// TextFormField(
|
||||
// enabled: !_loading,
|
||||
// obscureText: true,
|
||||
// decoration: InputDecoration(labelText: "Password".i18n),
|
||||
// controller: _passwordController,
|
||||
// autofillHints: const [
|
||||
// AutofillHints.password,
|
||||
// ],
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// actions: [
|
||||
// TextButton(
|
||||
// onPressed: _loading
|
||||
// ? null
|
||||
// : () async {
|
||||
// if (_emailController.text.isNotEmpty &&
|
||||
// _passwordController.text.isNotEmpty) {
|
||||
// await _login();
|
||||
// } else {
|
||||
// ScaffoldMessenger.of(context)
|
||||
// .snack("Missing email or password!".i18n);
|
||||
// }
|
||||
// },
|
||||
// child: _loading
|
||||
// ? const CircularProgressIndicator()
|
||||
// : const Text('Login'),
|
||||
// )
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -45,12 +45,14 @@ class LyricsWidget extends StatefulWidget {
|
|||
State<LyricsWidget> createState() => _LyricsWidgetState();
|
||||
}
|
||||
|
||||
class _LyricsWidgetState extends State<LyricsWidget> {
|
||||
class _LyricsWidgetState extends State<LyricsWidget>
|
||||
with WidgetsBindingObserver {
|
||||
late StreamSubscription _mediaItemSub;
|
||||
late StreamSubscription _playbackStateSub;
|
||||
int? _currentIndex = -1;
|
||||
Duration _nextOffset = Duration.zero;
|
||||
Duration _currentOffset = Duration.zero;
|
||||
String? _currentTrackId;
|
||||
final ScrollController _controller = ScrollController();
|
||||
final double height = 90;
|
||||
BoxConstraints? _widgetConstraints;
|
||||
|
@ -64,8 +66,12 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||
bool _syncedLyrics = false;
|
||||
|
||||
Future<void> _loadForId(String trackId) async {
|
||||
if (_currentTrackId == trackId) return;
|
||||
_currentTrackId = trackId;
|
||||
print('cancelling req?');
|
||||
// cancel current request, if applicable
|
||||
_lyricsCancelToken?.cancel();
|
||||
|
||||
_currentIndex = -1;
|
||||
_currentOffset = Duration.zero;
|
||||
_nextOffset = Duration.zero;
|
||||
|
@ -83,6 +89,7 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||
_lyricsCancelToken = CancelToken();
|
||||
final lyrics =
|
||||
await deezerAPI.lyrics(trackId, cancelToken: _lyricsCancelToken);
|
||||
|
||||
_syncedLyrics = lyrics.sync;
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
|
@ -95,10 +102,14 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||
} on DioException catch (e) {
|
||||
if (e.type != DioExceptionType.cancel) rethrow;
|
||||
} catch (e) {
|
||||
_currentTrackId = null;
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_error = e;
|
||||
});
|
||||
} finally {
|
||||
_lyricsCancelToken =
|
||||
null; // dispose of cancel token after lyrics are fetched.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,8 +126,6 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||
scrollTo = minScroll - widgetHeight / 2 + height / 2;
|
||||
}
|
||||
|
||||
print(
|
||||
'${height * _currentIndex!}, ${MediaQuery.of(context).size.height / 2}');
|
||||
if (scrollTo < 0.0) scrollTo = 0.0;
|
||||
if (scrollTo > _controller.position.maxScrollExtent) {
|
||||
scrollTo = _controller.position.maxScrollExtent;
|
||||
|
@ -152,28 +161,49 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||
_scrollToLyric();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
//Enable visualizer
|
||||
// if (settings.lyricsVisualizer) playerHelper.startVisualizer();
|
||||
_playbackStateSub = AudioService.position.listen(_updatePosition);
|
||||
});
|
||||
void _makeSubscriptions() {
|
||||
_playbackStateSub = AudioService.position.listen(_updatePosition);
|
||||
|
||||
/// Track change = ~exit~ reload lyrics
|
||||
/// Track change = reload new lyrics
|
||||
_mediaItemSub = audioHandler.mediaItem.listen((mediaItem) {
|
||||
if (mediaItem == null) return;
|
||||
if (_controller.hasClients) _controller.jumpTo(0.0);
|
||||
_loadForId(mediaItem.id);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
//Enable visualizer
|
||||
// if (settings.lyricsVisualizer) playerHelper.startVisualizer();
|
||||
_makeSubscriptions();
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.paused:
|
||||
_mediaItemSub.cancel();
|
||||
_playbackStateSub.cancel();
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
_makeSubscriptions();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_mediaItemSub.cancel();
|
||||
_playbackStateSub.cancel();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
//Stop visualizer
|
||||
// if (settings.lyricsVisualizer) playerHelper.stopVisualizer();
|
||||
super.dispose();
|
||||
|
|
|
@ -148,28 +148,27 @@ class MenuSheet {
|
|||
showModalBottomSheet(
|
||||
isScrollControlled: false, // true,
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
builder: (BuildContext context) {
|
||||
return SafeArea(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: (MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape)
|
||||
? 220
|
||||
: 350,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: options
|
||||
.map((option) => ListTile(
|
||||
title: option.label,
|
||||
leading: option.icon,
|
||||
onTap: () {
|
||||
option.onTap.call();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
))
|
||||
.toList(growable: false)),
|
||||
),
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight:
|
||||
(MediaQuery.of(context).orientation == Orientation.landscape)
|
||||
? 220
|
||||
: 350,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: options
|
||||
.map((option) => ListTile(
|
||||
title: option.label,
|
||||
leading: option.icon,
|
||||
onTap: () {
|
||||
option.onTap.call();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
))
|
||||
.toList(growable: false)),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
@ -197,13 +196,13 @@ class MenuSheet {
|
|||
return DraggableScrollableSheet(
|
||||
initialChildSize: 0.5,
|
||||
minChildSize: 0.45,
|
||||
maxChildSize: 0.75,
|
||||
builder: (context, scrollController) => SafeArea(
|
||||
child: Material(
|
||||
type: MaterialType.card,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
borderRadius:
|
||||
const BorderRadius.vertical(top: Radius.circular(20.0)),
|
||||
maxChildSize: 0.95,
|
||||
builder: (context, scrollController) => Material(
|
||||
type: MaterialType.card,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
borderRadius:
|
||||
const BorderRadius.vertical(top: Radius.circular(20.0)),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
|
|
|
@ -69,6 +69,7 @@ class PlayerBar extends StatelessWidget {
|
|||
}
|
||||
final currentMediaItem = snapshot.data!;
|
||||
final image = CachedImage(
|
||||
rounded: true,
|
||||
width: 50,
|
||||
height: 50,
|
||||
url: currentMediaItem.extras!['thumb'] ??
|
||||
|
@ -185,6 +186,7 @@ class PrevNextButton extends StatelessWidget {
|
|||
class PlayPauseButton extends StatefulWidget {
|
||||
final double size;
|
||||
final bool filled;
|
||||
final bool material3;
|
||||
final Color? iconColor;
|
||||
|
||||
/// The color of the card if [filled] is true
|
||||
|
@ -193,6 +195,7 @@ class PlayPauseButton extends StatefulWidget {
|
|||
this.size, {
|
||||
Key? key,
|
||||
this.filled = false,
|
||||
this.material3 = true,
|
||||
this.color,
|
||||
this.iconColor,
|
||||
}) : super(key: key);
|
||||
|
@ -205,7 +208,8 @@ class _PlayPauseButtonState extends State<PlayPauseButton>
|
|||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<double> _animation;
|
||||
late StreamSubscription _subscription;
|
||||
late StreamSubscription _stateSubscription;
|
||||
late StreamSubscription _playingSubscription;
|
||||
late bool _canPlay = audioHandler.playbackState.value.processingState ==
|
||||
AudioProcessingState.ready ||
|
||||
audioHandler.playbackState.value.processingState ==
|
||||
|
@ -217,26 +221,30 @@ class _PlayPauseButtonState extends State<PlayPauseButton>
|
|||
vsync: this, duration: const Duration(milliseconds: 200));
|
||||
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
|
||||
|
||||
_subscription = audioHandler.playbackState.listen((playbackState) {
|
||||
if (playbackState.processingState == AudioProcessingState.ready ||
|
||||
_stateSubscription = playerHelper.processingState.listen((processingState) {
|
||||
print('yes!');
|
||||
if (processingState == AudioProcessingState.ready ||
|
||||
audioHandler.playbackState.value.processingState ==
|
||||
AudioProcessingState.idle) {
|
||||
if (playbackState.playing) {
|
||||
_controller.forward();
|
||||
} else {
|
||||
_controller.reverse();
|
||||
}
|
||||
if (!_canPlay) setState(() => _canPlay = true);
|
||||
return;
|
||||
}
|
||||
setState(() => _canPlay = false);
|
||||
});
|
||||
_playingSubscription = playerHelper.playing.listen((playing) {
|
||||
if (playing) {
|
||||
_controller.forward();
|
||||
} else {
|
||||
_controller.reverse();
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription.cancel();
|
||||
_stateSubscription.cancel();
|
||||
_playingSubscription.cancel();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -261,7 +269,7 @@ class _PlayPauseButtonState extends State<PlayPauseButton>
|
|||
: 'Play'.i18n,
|
||||
);
|
||||
if (!widget.filled) {
|
||||
return IconButton(
|
||||
child = IconButton(
|
||||
color: widget.iconColor,
|
||||
icon: icon,
|
||||
iconSize: widget.size,
|
||||
|
@ -270,7 +278,7 @@ class _PlayPauseButtonState extends State<PlayPauseButton>
|
|||
child = Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
customBorder: widget.material3 ? null : const CircleBorder(),
|
||||
onTap: _playPause,
|
||||
child: IconTheme.merge(
|
||||
child: Center(child: icon),
|
||||
|
@ -290,16 +298,33 @@ class _PlayPauseButtonState extends State<PlayPauseButton>
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (widget.filled) {
|
||||
return SizedBox.square(
|
||||
dimension: widget.size,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(seconds: 1),
|
||||
decoration:
|
||||
BoxDecoration(shape: BoxShape.circle, color: widget.color),
|
||||
child: child));
|
||||
} else {
|
||||
return SizedBox.square(dimension: widget.size, child: child);
|
||||
if (widget.material3 && widget.filled) {
|
||||
return StreamBuilder<bool>(
|
||||
stream: playerHelper.playing,
|
||||
builder: (context, snapshot) {
|
||||
return AnimatedContainer(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: snapshot.data == true
|
||||
? BorderRadius.circular(24.0)
|
||||
: BorderRadius.circular(36.0),
|
||||
color: widget.color),
|
||||
child: child);
|
||||
});
|
||||
}
|
||||
if (widget.filled) {
|
||||
return AnimatedContainer(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
duration: const Duration(seconds: 1),
|
||||
decoration:
|
||||
BoxDecoration(shape: BoxShape.circle, color: widget.color),
|
||||
child: child);
|
||||
}
|
||||
|
||||
return SizedBox.square(dimension: widget.size * 2, child: child);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ class BackgroundProvider extends ChangeNotifier {
|
|||
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
print('[PROVIDER] listener added $hasListeners');
|
||||
_mediaItemSub ??= audioHandler.mediaItem.listen((mediaItem) {
|
||||
if (mediaItem == null) return;
|
||||
_updateColor(mediaItem);
|
||||
|
@ -71,14 +72,12 @@ class BackgroundProvider extends ChangeNotifier {
|
|||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
super.removeListener(listener);
|
||||
if (!hasListeners && _mediaItemSub != null) {
|
||||
_mediaItemSub!.cancel();
|
||||
_mediaItemSub = null;
|
||||
}
|
||||
print('[PROVIDER] listener removed! hasListeners? $hasListeners');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
print('[PROVIDER] DISPOSED');
|
||||
_isDisposed = true;
|
||||
_mediaItemSub?.cancel();
|
||||
super.dispose();
|
||||
|
@ -243,7 +242,7 @@ class PlayerScreenHorizontal extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 8, 0),
|
||||
child: PlayerScreenTopRow(
|
||||
textSize: 24.sp,
|
||||
iconSize: 36.sp,
|
||||
|
@ -251,19 +250,19 @@ class PlayerScreenHorizontal extends StatelessWidget {
|
|||
short: true),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: PlayerTextSubtext(textSize: 35.sp),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: SeekBar(textSize: 24.sp),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: PlaybackControls(46.sp),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: BottomBarControls(size: 30.sp),
|
||||
)
|
||||
],
|
||||
|
@ -457,6 +456,7 @@ class _FitOrScrollTextState extends State<FitOrScrollText> {
|
|||
style: widget.style,
|
||||
blankSpace: 32.0,
|
||||
startPadding: 0.0,
|
||||
numberOfRounds: 2,
|
||||
accelerationDuration: const Duration(seconds: 1),
|
||||
pauseAfterRound: const Duration(seconds: 2),
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
|
@ -491,7 +491,9 @@ class PlayerTextSubtext extends StatelessWidget {
|
|||
text: currentMediaItem.displayTitle!,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: textSize, fontWeight: FontWeight.bold)),
|
||||
fontSize: textSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
),
|
||||
// child: currentMediaItem.displayTitle!.length >= 26
|
||||
// ? Marquee(
|
||||
|
@ -552,6 +554,12 @@ class QualityInfoWidget extends StatelessWidget {
|
|||
stream: playerHelper.streamInfo.map<String>(_getQualityStringFromInfo),
|
||||
builder: (context, snapshot) {
|
||||
return TextButton(
|
||||
// style: ButtonStyle(
|
||||
// elevation: MaterialStatePropertyAll(0.5),
|
||||
// padding: MaterialStatePropertyAll(
|
||||
// EdgeInsets.symmetric(horizontal: 16, vertical: 4)),
|
||||
// foregroundColor: MaterialStatePropertyAll(
|
||||
// Theme.of(context).colorScheme.onSurface)),
|
||||
child: Text(snapshot.data ?? '',
|
||||
style: textSize == null ? null : TextStyle(fontSize: textSize)),
|
||||
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
|
||||
|
@ -795,6 +803,7 @@ class PlaybackControls extends StatelessWidget {
|
|||
: darken(provider.dominantColor!);
|
||||
return PlayPauseButton(size * 2.25,
|
||||
filled: true,
|
||||
material3: settings.enableMaterial3PlayButton,
|
||||
color: color,
|
||||
iconColor: Color.lerp(
|
||||
(ThemeData.estimateBrightnessForColor(color) ==
|
||||
|
@ -1172,6 +1181,7 @@ class BottomBarControls extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
QualityInfoWidget(textSize: size * 0.75),
|
||||
const Expanded(child: SizedBox()),
|
||||
if (!desktopMode)
|
||||
IconButton(
|
||||
iconSize: size,
|
||||
|
|
|
@ -18,15 +18,6 @@ class QueueScreen extends StatelessWidget {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Queue'.i18n),
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
statusBarBrightness: Brightness.light,
|
||||
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
systemNavigationBarDividerColor: Color(
|
||||
Theme.of(context).scaffoldBackgroundColor.value - 0x00111111),
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
),
|
||||
// actions: <Widget>[
|
||||
// IconButton(
|
||||
// icon: Icon(
|
||||
|
|
|
@ -349,13 +349,21 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
}),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('Enable filled play button'),
|
||||
title: Text('Enable filled play button'.i18n),
|
||||
secondary: const Icon(Icons.play_circle),
|
||||
value: settings.enableFilledPlayButton,
|
||||
onChanged: (bool v) {
|
||||
setState(() => settings.enableFilledPlayButton = v);
|
||||
settings.save();
|
||||
}),
|
||||
SwitchListTile(
|
||||
title: Text('Material 3 play button'.i18n),
|
||||
secondary: const Icon(Icons.play_circle_outline),
|
||||
value: settings.enableMaterial3PlayButton,
|
||||
onChanged: (bool v) {
|
||||
setState(() => settings.enableMaterial3PlayButton = v);
|
||||
settings.save();
|
||||
}),
|
||||
SwitchListTile(
|
||||
title: Text('Visualizer'.i18n),
|
||||
subtitle: Text(
|
||||
|
@ -724,15 +732,12 @@ class _QualityPickerState extends State<QualityPicker> {
|
|||
switch (widget.field) {
|
||||
case 'mobile_wifi':
|
||||
settings.mobileQuality = settings.wifiQuality = _quality;
|
||||
settings.updateAudioServiceQuality();
|
||||
break;
|
||||
case 'mobile':
|
||||
settings.mobileQuality = _quality;
|
||||
settings.updateAudioServiceQuality();
|
||||
break;
|
||||
case 'wifi':
|
||||
settings.wifiQuality = _quality;
|
||||
settings.updateAudioServiceQuality();
|
||||
break;
|
||||
case 'download':
|
||||
settings.downloadQuality = _quality;
|
||||
|
@ -1818,6 +1823,10 @@ class _CreditsScreenState extends State<CreditsScreen> {
|
|||
title: Text('Pato05'),
|
||||
subtitle: Text('Current Developer - best of all'),
|
||||
),
|
||||
const ListTile(
|
||||
title: Text('iDrinkCoffee'),
|
||||
subtitle: Text('idk, he\'s romanian'),
|
||||
),
|
||||
const ListTile(
|
||||
title: Text('exttex'),
|
||||
subtitle: Text('Ex-Developer'),
|
||||
|
|
|
@ -269,6 +269,7 @@ class PlaylistTile extends StatelessWidget {
|
|||
leading: CachedImage(
|
||||
url: playlist!.image!.thumb,
|
||||
width: 48,
|
||||
rounded: true,
|
||||
),
|
||||
onTap: onTap,
|
||||
onLongPress: normalizeSecondary(onSecondary),
|
||||
|
|
Loading…
Reference in New Issue