New icon, banner, New font, Player UI fixes, Details UI fixed
22
README.md
@ -1,6 +1,8 @@
|
|||||||
# freezer
|
# freezer
|
||||||
|
|
||||||
A music streaming app written from scratch, which uses Deezer as backend.
|
![Icon](https://notabug.org/exttex/freezer/raw/master/android/app/src/main/res/mipmap-hdpi/ic_launcher.png)
|
||||||
|
|
||||||
|
Free, unlimited, without DRM music streaming app, which uses Deezer as backend.
|
||||||
This app is still in BETA, so it is missing features and contains bugs.
|
This app is still in BETA, so it is missing features and contains bugs.
|
||||||
If you want to report bug or request feature, please open an issue.
|
If you want to report bug or request feature, please open an issue.
|
||||||
|
|
||||||
@ -17,25 +19,27 @@ Compile:
|
|||||||
flutter pub get
|
flutter pub get
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
## just_audio
|
|
||||||
This app depends on modified just_audio plugin with Deezer support. Repo: https://notabug.org/exttex/just_audio
|
|
||||||
|
|
||||||
## Telegram
|
## Telegram
|
||||||
https://t.me/freezerandroid
|
https://t.me/freezerandroid
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
Tobs: Beta tester
|
Tobs: Beta tester
|
||||||
|
Bas Curtiz: Icon, Logo, Banner, Design suggestions
|
||||||
Deemix: https://notabug.org/RemixDev/deemix
|
Deemix: https://notabug.org/RemixDev/deemix
|
||||||
just_audio: https://github.com/ryanheise/just_audio
|
just_audio: https://github.com/ryanheise/just_audio
|
||||||
|
|
||||||
|
|
||||||
|
## Support me
|
||||||
|
BTC: `14hcr4PGbgqeXd3SoXY9QyJFNpyurgrL9y`
|
||||||
|
ETH: `0xb4D1893195404E1F4b45e5BDA77F202Ac4012288`
|
||||||
|
|
||||||
|
## just_audio
|
||||||
|
This app depends on modified just_audio plugin with Deezer support. Repo: https://notabug.org/exttex/just_audio
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
```
|
```
|
||||||
Freezer was not developed for piracy, but educational and private usage.
|
Freezer was not developed for piracy, but educational and private usage.
|
||||||
It may be illegal to use this in your country!
|
It may be illegal to use this in your country!
|
||||||
I am not responsible in any way for the usage of this app.
|
I am not responsible in any way for the usage of this app.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Support me
|
|
||||||
BTC: `14hcr4PGbgqeXd3SoXY9QyJFNpyurgrL9y`
|
|
||||||
ETH: `0xb4D1893195404E1F4b45e5BDA77F202Ac4012288`
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 997 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
assets/cover_thumb.jpg
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/fonts/MabryPro.otf
Normal file
BIN
assets/fonts/MabryProBlack.otf
Normal file
BIN
assets/fonts/MabryProBold.otf
Normal file
BIN
assets/fonts/MabryProItalic.otf
Normal file
BIN
assets/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -43,6 +43,10 @@ class _FreezerAppState extends State<FreezerApp> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
//Make update theme global
|
//Make update theme global
|
||||||
updateTheme = _updateTheme;
|
updateTheme = _updateTheme;
|
||||||
|
|
||||||
|
//Precache placeholder
|
||||||
|
precacheImage(imagesDatabase.placeholderThumb, context);
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,11 +67,13 @@ class Settings {
|
|||||||
|
|
||||||
Settings({this.downloadPath, this.arl});
|
Settings({this.downloadPath, this.arl});
|
||||||
|
|
||||||
|
static const deezerBg = Color(0xFF1F1A16);
|
||||||
|
static const font = 'MabryPro';
|
||||||
ThemeData get themeData {
|
ThemeData get themeData {
|
||||||
switch (theme??Themes.Light) {
|
switch (theme??Themes.Light) {
|
||||||
case Themes.Light:
|
case Themes.Light:
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
fontFamily: 'Montserrat',
|
fontFamily: font,
|
||||||
primaryColor: primaryColor,
|
primaryColor: primaryColor,
|
||||||
accentColor: primaryColor,
|
accentColor: primaryColor,
|
||||||
sliderTheme: _sliderTheme,
|
sliderTheme: _sliderTheme,
|
||||||
@ -79,16 +81,33 @@ class Settings {
|
|||||||
);
|
);
|
||||||
case Themes.Dark:
|
case Themes.Dark:
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
fontFamily: 'Montserrat',
|
fontFamily: font,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primaryColor: primaryColor,
|
primaryColor: primaryColor,
|
||||||
accentColor: primaryColor,
|
accentColor: primaryColor,
|
||||||
sliderTheme: _sliderTheme,
|
sliderTheme: _sliderTheme,
|
||||||
toggleableActiveColor: primaryColor,
|
toggleableActiveColor: primaryColor,
|
||||||
);
|
);
|
||||||
|
case Themes.Deezer:
|
||||||
|
return ThemeData(
|
||||||
|
fontFamily: font,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: primaryColor,
|
||||||
|
accentColor: primaryColor,
|
||||||
|
sliderTheme: _sliderTheme,
|
||||||
|
toggleableActiveColor: primaryColor,
|
||||||
|
backgroundColor: deezerBg,
|
||||||
|
scaffoldBackgroundColor: deezerBg,
|
||||||
|
bottomAppBarColor: deezerBg,
|
||||||
|
dialogBackgroundColor: deezerBg,
|
||||||
|
bottomSheetTheme: BottomSheetThemeData(
|
||||||
|
backgroundColor: deezerBg
|
||||||
|
),
|
||||||
|
cardColor: deezerBg
|
||||||
|
);
|
||||||
case Themes.Black:
|
case Themes.Black:
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
fontFamily: 'Montserrat',
|
fontFamily: font,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primaryColor: primaryColor,
|
primaryColor: primaryColor,
|
||||||
accentColor: primaryColor,
|
accentColor: primaryColor,
|
||||||
@ -185,10 +204,12 @@ enum AudioQuality {
|
|||||||
enum Themes {
|
enum Themes {
|
||||||
Light,
|
Light,
|
||||||
Dark,
|
Dark,
|
||||||
|
Deezer,
|
||||||
Black
|
Black
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DownloadNaming {
|
enum DownloadNaming {
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
STANDALONE
|
STANDALONE,
|
||||||
|
|
||||||
}
|
}
|
@ -99,5 +99,6 @@ const _$DownloadNamingEnumMap = {
|
|||||||
const _$ThemesEnumMap = {
|
const _$ThemesEnumMap = {
|
||||||
Themes.Light: 'Light',
|
Themes.Light: 'Light',
|
||||||
Themes.Dark: 'Dark',
|
Themes.Dark: 'Dark',
|
||||||
|
Themes.Deezer: 'Deezer',
|
||||||
Themes.Black: 'Black',
|
Themes.Black: 'Black',
|
||||||
};
|
};
|
||||||
|
@ -27,6 +27,8 @@ class ImagesDatabase {
|
|||||||
Database db;
|
Database db;
|
||||||
String imagesPath;
|
String imagesPath;
|
||||||
|
|
||||||
|
ImageProvider placeholderThumb = new AssetImage('assets/cover_thumb.jpg');
|
||||||
|
|
||||||
//Prepare database
|
//Prepare database
|
||||||
Future init() async {
|
Future init() async {
|
||||||
String dir = await getDatabasesPath();
|
String dir = await getDatabasesPath();
|
||||||
@ -82,7 +84,7 @@ class ImagesDatabase {
|
|||||||
Future<PaletteGenerator> getPaletteGenerator(String url) async {
|
Future<PaletteGenerator> getPaletteGenerator(String url) async {
|
||||||
String path = await getImage(url);
|
String path = await getImage(url);
|
||||||
//Get image provider
|
//Get image provider
|
||||||
ImageProvider provider = AssetImage('assets/cover.jpg');
|
ImageProvider provider = placeholderThumb;
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
provider = FileImage(File(path));
|
provider = FileImage(File(path));
|
||||||
}
|
}
|
||||||
@ -120,8 +122,7 @@ class CachedImage extends StatefulWidget {
|
|||||||
|
|
||||||
class _CachedImageState extends State<CachedImage> {
|
class _CachedImageState extends State<CachedImage> {
|
||||||
|
|
||||||
final ImageProvider _placeholder = AssetImage('assets/cover.jpg');
|
ImageProvider _image = imagesDatabase.placeholderThumb;
|
||||||
ImageProvider _image = AssetImage('assets/cover.jpg');
|
|
||||||
double _opacity = 0.0;
|
double _opacity = 0.0;
|
||||||
bool _disposed = false;
|
bool _disposed = false;
|
||||||
String _prevUrl;
|
String _prevUrl;
|
||||||
@ -135,7 +136,7 @@ class _CachedImageState extends State<CachedImage> {
|
|||||||
}
|
}
|
||||||
//Load image from db
|
//Load image from db
|
||||||
String path = await imagesDatabase.getImage(widget.url);
|
String path = await imagesDatabase.getImage(widget.url);
|
||||||
if (path == null) return _placeholder;
|
if (path == null) return imagesDatabase.placeholderThumb;
|
||||||
return FileImage(File(path));
|
return FileImage(File(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,10 +178,10 @@ class _CachedImageState extends State<CachedImage> {
|
|||||||
widget.circular ?
|
widget.circular ?
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: (widget.width??widget.height),
|
radius: (widget.width??widget.height),
|
||||||
backgroundImage: _placeholder,
|
backgroundImage: imagesDatabase.placeholderThumb,
|
||||||
):
|
):
|
||||||
Image(
|
Image(
|
||||||
image: _placeholder,
|
image: imagesDatabase.placeholderThumb,
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
),
|
),
|
||||||
|
@ -48,7 +48,7 @@ class AlbumDetails extends StatelessWidget {
|
|||||||
Container(height: 8.0,),
|
Container(height: 8.0,),
|
||||||
CachedImage(
|
CachedImage(
|
||||||
url: album.art.full,
|
url: album.art.full,
|
||||||
height: 256.0,
|
width: MediaQuery.of(context).size.width / 2
|
||||||
),
|
),
|
||||||
Container(height: 8,),
|
Container(height: 8,),
|
||||||
Text(
|
Text(
|
||||||
@ -259,11 +259,10 @@ class ArtistDetails extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CachedImage(
|
CachedImage(
|
||||||
url: artist.picture.full,
|
url: artist.picture.full,
|
||||||
height: 200,
|
width: MediaQuery.of(context).size.width / 2 - 8,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: 200.0,
|
width: MediaQuery.of(context).size.width / 2 - 8,
|
||||||
height: 220,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -500,11 +499,10 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CachedImage(
|
CachedImage(
|
||||||
url: playlist.image.full,
|
url: playlist.image.full,
|
||||||
height: 180.0,
|
height: MediaQuery.of(context).size.width / 2 - 8,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: 180,
|
width: MediaQuery.of(context).size.width / 2 - 8,
|
||||||
height: 200, //Card padding
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -11,19 +11,18 @@ import '../settings.dart';
|
|||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
//TODO: SingleChildScrollView vs ListView speed/perf
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
SafeArea(
|
||||||
padding: EdgeInsets.symmetric(vertical: 16.0),
|
|
||||||
child: FreezerTitle(),
|
child: FreezerTitle(),
|
||||||
),
|
),
|
||||||
Flexible(child: HomePageScreen(),)
|
Flexible(child: HomePageScreen(),)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
return ListView(
|
return ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -34,22 +33,33 @@ class HomeScreen extends StatelessWidget {
|
|||||||
HomePageScreen()
|
HomePageScreen()
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FreezerTitle extends StatelessWidget {
|
class FreezerTitle extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Text(
|
return Padding(
|
||||||
'freezer',
|
padding: EdgeInsets.fromLTRB(0, 24, 0, 8),
|
||||||
textAlign: TextAlign.center,
|
child: Row(
|
||||||
style: TextStyle(
|
mainAxisSize: MainAxisSize.max,
|
||||||
fontFamily: 'Jost',
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
fontSize: 75,
|
children: <Widget>[
|
||||||
fontStyle: FontStyle.italic,
|
Row(
|
||||||
letterSpacing: 7
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Image.asset('assets/icon.png', width: 64, height: 64),
|
||||||
|
Text(
|
||||||
|
'freezer',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 56,
|
||||||
|
fontWeight: FontWeight.w900
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -158,7 +168,10 @@ class _HomePageScreenState extends State<HomePageScreen> {
|
|||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(fontSize: 24.0),
|
style: TextStyle(
|
||||||
|
fontSize: 20.0,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0)
|
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0)
|
||||||
),
|
),
|
||||||
|
@ -121,40 +121,45 @@ class PlayPauseButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
//Playing
|
return StreamBuilder(
|
||||||
if (AudioService.playbackState?.playing??false) {
|
stream: AudioService.playbackStateStream,
|
||||||
return IconButton(
|
builder: (context, snapshot) {
|
||||||
iconSize: this.size,
|
//Playing
|
||||||
icon: Icon(Icons.pause),
|
if (AudioService.playbackState?.playing??false) {
|
||||||
onPressed: () => AudioService.pause()
|
return IconButton(
|
||||||
);
|
iconSize: this.size,
|
||||||
}
|
icon: Icon(Icons.pause),
|
||||||
|
onPressed: () => AudioService.pause()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//Paused
|
//Paused
|
||||||
if ((!AudioService.playbackState.playing &&
|
if ((!AudioService.playbackState.playing &&
|
||||||
AudioService.playbackState.processingState == AudioProcessingState.ready) ||
|
AudioService.playbackState.processingState == AudioProcessingState.ready) ||
|
||||||
//None state (stopped)
|
//None state (stopped)
|
||||||
AudioService.playbackState.processingState == AudioProcessingState.none) {
|
AudioService.playbackState.processingState == AudioProcessingState.none) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
iconSize: this.size,
|
iconSize: this.size,
|
||||||
icon: Icon(Icons.play_arrow),
|
icon: Icon(Icons.play_arrow),
|
||||||
onPressed: () => AudioService.play()
|
onPressed: () => AudioService.play()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (AudioService.playbackState.processingState) {
|
switch (AudioService.playbackState.processingState) {
|
||||||
//Stopped/Error
|
//Stopped/Error
|
||||||
case AudioProcessingState.error:
|
case AudioProcessingState.error:
|
||||||
case AudioProcessingState.none:
|
case AudioProcessingState.none:
|
||||||
case AudioProcessingState.stopped:
|
case AudioProcessingState.stopped:
|
||||||
return Container(width: this.size, height: this.size);
|
return Container(width: this.size, height: this.size);
|
||||||
//Loading, connecting, rewinding...
|
//Loading, connecting, rewinding...
|
||||||
default:
|
default:
|
||||||
return Container(
|
return Container(
|
||||||
width: this.size,
|
width: this.size,
|
||||||
height: this.size,
|
height: this.size,
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:flutter_screenutil/screenutil.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/player.dart';
|
import 'package:freezer/api/player.dart';
|
||||||
import 'package:freezer/ui/menu.dart';
|
import 'package:freezer/ui/menu.dart';
|
||||||
|
import 'package:freezer/ui/settings_screen.dart';
|
||||||
import 'package:freezer/ui/tiles.dart';
|
import 'package:freezer/ui/tiles.dart';
|
||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
|
|
||||||
@ -13,9 +15,6 @@ import 'cached_image.dart';
|
|||||||
import '../api/definitions.dart';
|
import '../api/definitions.dart';
|
||||||
import 'player_bar.dart';
|
import 'player_bar.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerScreen extends StatefulWidget {
|
class PlayerScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_PlayerScreenState createState() => _PlayerScreenState();
|
_PlayerScreenState createState() => _PlayerScreenState();
|
||||||
@ -23,242 +22,35 @@ class PlayerScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _PlayerScreenState extends State<PlayerScreen> {
|
class _PlayerScreenState extends State<PlayerScreen> {
|
||||||
|
|
||||||
double iconSize = 48;
|
|
||||||
bool _lyrics = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
//Responsive
|
||||||
|
ScreenUtil.init(context, allowFontScaling: true);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: StreamBuilder(
|
child: StreamBuilder(
|
||||||
stream: StreamZip([AudioService.playbackStateStream, AudioService.currentMediaItemStream]),
|
stream: StreamZip([AudioService.playbackStateStream, AudioService.currentMediaItemStream]),
|
||||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
|
|
||||||
//Disable lyrics when skipping songs, loading
|
|
||||||
if (snapshot.data is PlaybackState &&
|
|
||||||
snapshot.data.processingState != AudioProcessingState.ready &&
|
|
||||||
snapshot.data.processingState != AudioProcessingState.buffering) _lyrics = false;
|
|
||||||
|
|
||||||
//When disconnected
|
//When disconnected
|
||||||
if (AudioService.currentMediaItem == null) {
|
if (AudioService.currentMediaItem == null) {
|
||||||
playerHelper.startService();
|
playerHelper.startService();
|
||||||
return Center(child: CircularProgressIndicator(),);
|
return Center(child: CircularProgressIndicator(),);
|
||||||
}
|
}
|
||||||
|
|
||||||
return OrientationBuilder(
|
return OrientationBuilder(
|
||||||
builder: (context, orientation) {
|
builder: (context, orientation) {
|
||||||
//Landscape
|
//Landscape
|
||||||
if (orientation == Orientation.landscape) {
|
if (orientation == Orientation.landscape) {
|
||||||
return Row(
|
return PlayerScreenHorizontal();
|
||||||
mainAxisSize: MainAxisSize.max,
|
}
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
//Portrait
|
||||||
children: <Widget>[
|
return PlayerScreenVertical();
|
||||||
Padding(
|
},
|
||||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 8),
|
);
|
||||||
child: Container(
|
|
||||||
width: 320,
|
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
CachedImage(
|
|
||||||
url: AudioService.currentMediaItem.artUri,
|
|
||||||
),
|
|
||||||
if (_lyrics) LyricsWidget(
|
|
||||||
artUri: AudioService.currentMediaItem.artUri,
|
|
||||||
trackId: AudioService.currentMediaItem.id,
|
|
||||||
lyrics: Track.fromMediaItem(AudioService.currentMediaItem).lyrics,
|
|
||||||
height: 320.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width / 2 - 32,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(8, 16, 8, 0),
|
|
||||||
child: Container(
|
|
||||||
width: 300,
|
|
||||||
child: PlayerScreenTopRow(),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
AudioService.currentMediaItem.displayTitle,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.clip,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24.0,
|
|
||||||
fontWeight: FontWeight.bold
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 4,),
|
|
||||||
Text(
|
|
||||||
AudioService.currentMediaItem.displaySubtitle,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.clip,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 320,
|
|
||||||
child: SeekBar(),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 320,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
PrevNextButton(iconSize, prev: true,),
|
|
||||||
PlayPauseButton(iconSize),
|
|
||||||
PrevNextButton(iconSize)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(8, 0, 8, 16),
|
|
||||||
child: Container(
|
|
||||||
width: 300,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.subtitles),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() => _lyrics = !_lyrics);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
AudioService.currentMediaItem.extras['qualityString']
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.more_vert),
|
|
||||||
onPressed: () {
|
|
||||||
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
|
||||||
MenuSheet m = MenuSheet(context);
|
|
||||||
m.defaultTrackMenu(t);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Portrait
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(28, 12, 28, 4),
|
|
||||||
child: PlayerScreenTopRow()
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
|
|
||||||
child: Container(
|
|
||||||
height: 360,
|
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
CachedImage(
|
|
||||||
url: AudioService.currentMediaItem.artUri,
|
|
||||||
),
|
|
||||||
if (_lyrics) LyricsWidget(
|
|
||||||
artUri: AudioService.currentMediaItem.artUri,
|
|
||||||
trackId: AudioService.currentMediaItem.id,
|
|
||||||
lyrics: Track.fromMediaItem(AudioService.currentMediaItem).lyrics,
|
|
||||||
height: 360.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
AudioService.currentMediaItem.displayTitle,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.clip,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24.0,
|
|
||||||
fontWeight: FontWeight.bold
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 4,),
|
|
||||||
Text(
|
|
||||||
AudioService.currentMediaItem.displaySubtitle,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.clip,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SeekBar(),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
PrevNextButton(iconSize, prev: true,),
|
|
||||||
PlayPauseButton(iconSize),
|
|
||||||
PrevNextButton(iconSize)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
//Container(height: 8.0,),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 16.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.subtitles),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() => _lyrics = !_lyrics);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
AudioService.currentMediaItem.extras['qualityString']
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.more_vert),
|
|
||||||
onPressed: () {
|
|
||||||
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
|
||||||
MenuSheet m = MenuSheet(context);
|
|
||||||
m.defaultTrackMenu(t);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -266,6 +58,265 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Landscape
|
||||||
|
class PlayerScreenHorizontal extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_PlayerScreenHorizontalState createState() => _PlayerScreenHorizontalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
||||||
|
|
||||||
|
double iconSize = ScreenUtil().setWidth(64);
|
||||||
|
bool _lyrics = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||||
|
child: Container(
|
||||||
|
width: ScreenUtil().setWidth(500),
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
CachedImage(
|
||||||
|
url: AudioService.currentMediaItem.artUri,
|
||||||
|
),
|
||||||
|
if (_lyrics) LyricsWidget(
|
||||||
|
artUri: AudioService.currentMediaItem.artUri,
|
||||||
|
trackId: AudioService.currentMediaItem.id,
|
||||||
|
lyrics: Track.fromMediaItem(AudioService.currentMediaItem).lyrics,
|
||||||
|
height: ScreenUtil().setWidth(500),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
//Right side
|
||||||
|
SizedBox(
|
||||||
|
width: ScreenUtil().setWidth(500),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(8, 16, 8, 0),
|
||||||
|
child: Container(
|
||||||
|
child: PlayerScreenTopRow(
|
||||||
|
textSize: ScreenUtil().setSp(26),
|
||||||
|
iconSize: ScreenUtil().setSp(32),
|
||||||
|
textWidth: ScreenUtil().setWidth(256),
|
||||||
|
short: true
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
AudioService.currentMediaItem.displayTitle,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenUtil().setSp(40),
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 4,),
|
||||||
|
Text(
|
||||||
|
AudioService.currentMediaItem.displaySubtitle,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenUtil().setSp(32),
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: SeekBar(),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
PrevNextButton(iconSize, prev: true,),
|
||||||
|
PlayPauseButton(iconSize),
|
||||||
|
PrevNextButton(iconSize)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(8, 0, 8, 16),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(32)),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _lyrics = !_lyrics);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => QualitySettings())
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
AudioService.currentMediaItem.extras['qualityString'],
|
||||||
|
style: TextStyle(fontSize: ScreenUtil().setSp(24)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.more_vert, size: ScreenUtil().setWidth(32)),
|
||||||
|
onPressed: () {
|
||||||
|
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
||||||
|
MenuSheet m = MenuSheet(context);
|
||||||
|
m.defaultTrackMenu(t);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Portrait
|
||||||
|
class PlayerScreenVertical extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_PlayerScreenVerticalState createState() => _PlayerScreenVerticalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
||||||
|
double iconSize = ScreenUtil().setWidth(100);
|
||||||
|
bool _lyrics = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(28, 10, 28, 0),
|
||||||
|
child: PlayerScreenTopRow()
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||||
|
child: Container(
|
||||||
|
height: ScreenUtil().setHeight(1050),
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
CachedImage(
|
||||||
|
url: AudioService.currentMediaItem.artUri,
|
||||||
|
),
|
||||||
|
if (_lyrics) LyricsWidget(
|
||||||
|
artUri: AudioService.currentMediaItem.artUri,
|
||||||
|
trackId: AudioService.currentMediaItem.id,
|
||||||
|
lyrics: Track.fromMediaItem(AudioService.currentMediaItem).lyrics,
|
||||||
|
height: ScreenUtil().setHeight(1050),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
AudioService.currentMediaItem.displayTitle,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenUtil().setSp(64),
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 4,),
|
||||||
|
Text(
|
||||||
|
AudioService.currentMediaItem.displaySubtitle,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenUtil().setSp(52),
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SeekBar(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
PrevNextButton(iconSize, prev: true,),
|
||||||
|
PlayPauseButton(iconSize),
|
||||||
|
PrevNextButton(iconSize)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
//Container(height: 8.0,),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(46)),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _lyrics = !_lyrics);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => QualitySettings())
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
AudioService.currentMediaItem.extras['qualityString'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenUtil().setSp(32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.more_vert, size: ScreenUtil().setWidth(46)),
|
||||||
|
onPressed: () {
|
||||||
|
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
||||||
|
MenuSheet m = MenuSheet(context);
|
||||||
|
m.defaultTrackMenu(t);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LyricsWidget extends StatefulWidget {
|
class LyricsWidget extends StatefulWidget {
|
||||||
|
|
||||||
final Lyrics lyrics;
|
final Lyrics lyrics;
|
||||||
@ -287,8 +338,11 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||||||
Timer _timer;
|
Timer _timer;
|
||||||
int _currentIndex;
|
int _currentIndex;
|
||||||
double _boxHeight;
|
double _boxHeight;
|
||||||
|
String _trackId;
|
||||||
|
|
||||||
Future _load() async {
|
Future _load() async {
|
||||||
|
_trackId = widget.trackId;
|
||||||
|
|
||||||
//Get text color by album art (black or white)
|
//Get text color by album art (black or white)
|
||||||
if (widget.artUri != null) {
|
if (widget.artUri != null) {
|
||||||
bool bw = await imagesDatabase.isDark(widget.artUri);
|
bool bw = await imagesDatabase.isDark(widget.artUri);
|
||||||
@ -298,7 +352,7 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||||||
if (widget.lyrics.lyrics == null || widget.lyrics.lyrics.length == 0) {
|
if (widget.lyrics.lyrics == null || widget.lyrics.lyrics.length == 0) {
|
||||||
//Load from api
|
//Load from api
|
||||||
try {
|
try {
|
||||||
_l = await deezerAPI.lyrics(widget.trackId);
|
_l = await deezerAPI.lyrics(_trackId);
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//Error Lyrics
|
//Error Lyrics
|
||||||
@ -315,6 +369,7 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
this._boxHeight = widget.height??400.0;
|
this._boxHeight = widget.height??400.0;
|
||||||
_load();
|
_load();
|
||||||
|
|
||||||
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
||||||
_timer = timer;
|
_timer = timer;
|
||||||
if (_loading) return;
|
if (_loading) return;
|
||||||
@ -340,6 +395,18 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(LyricsWidget oldWidget) {
|
||||||
|
if (this._trackId != widget.trackId) {
|
||||||
|
setState(() {
|
||||||
|
_loading = true;
|
||||||
|
this._trackId = widget.trackId;
|
||||||
|
});
|
||||||
|
_load();
|
||||||
|
}
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -360,15 +427,31 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||||||
return Container(
|
return Container(
|
||||||
height: _boxHeight,
|
height: _boxHeight,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Stack(
|
||||||
_l.lyrics[i].text,
|
children: <Widget>[
|
||||||
textAlign: TextAlign.center,
|
Text(
|
||||||
style: TextStyle(
|
_l.lyrics[i].text,
|
||||||
color: _textColor,
|
textAlign: TextAlign.center,
|
||||||
fontSize: 40.0,
|
style: TextStyle(
|
||||||
fontWeight: (_currentIndex == i)?FontWeight.bold:FontWeight.normal
|
fontSize: 36.0,
|
||||||
),
|
fontWeight: (_currentIndex == i)?FontWeight.bold:FontWeight.normal,
|
||||||
),
|
foreground: Paint()
|
||||||
|
..strokeWidth = 6
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..color = (_textColor==Colors.black)?Colors.white:Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_l.lyrics[i].text,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: _textColor,
|
||||||
|
fontSize: 36.0,
|
||||||
|
fontWeight: (_currentIndex == i)?FontWeight.bold:FontWeight.normal
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -382,26 +465,55 @@ class _LyricsWidgetState extends State<LyricsWidget> {
|
|||||||
|
|
||||||
//Top row containing QueueSource, queue...
|
//Top row containing QueueSource, queue...
|
||||||
class PlayerScreenTopRow extends StatelessWidget {
|
class PlayerScreenTopRow extends StatelessWidget {
|
||||||
|
|
||||||
|
double textSize;
|
||||||
|
double iconSize;
|
||||||
|
double textWidth;
|
||||||
|
bool short;
|
||||||
|
PlayerScreenTopRow({this.textSize, this.iconSize, this.textWidth, this.short});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Row(
|
||||||
'Playing from: ' + playerHelper.queueSource.text,
|
children: <Widget>[
|
||||||
maxLines: 1,
|
Padding(
|
||||||
overflow: TextOverflow.ellipsis,
|
padding: EdgeInsets.fromLTRB(0, 0, 8, 0),
|
||||||
textAlign: TextAlign.right,
|
child: InkWell(
|
||||||
style: TextStyle(fontSize: 16.0),
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Icon(Icons.keyboard_arrow_down, size: this.iconSize??ScreenUtil().setWidth(46)),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: this.textWidth??ScreenUtil().setWidth(600),
|
||||||
|
child: Text(
|
||||||
|
(short??false)?playerHelper.queueSource.text:'Playing from: ' + playerHelper.queueSource.text,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(fontSize: this.textSize??ScreenUtil().setSp(34)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
RepeatButton(),
|
RepeatButton(size: this.iconSize),
|
||||||
Container(width: 16.0,),
|
Container(width: 16.0,),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Icon(Icons.menu),
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Icon(Icons.menu, size: this.iconSize??ScreenUtil().setWidth(46)),
|
||||||
|
),
|
||||||
onTap: (){
|
onTap: (){
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) => QueueScreen()
|
builder: (context) => QueueScreen()
|
||||||
@ -418,25 +530,33 @@ class PlayerScreenTopRow extends StatelessWidget {
|
|||||||
|
|
||||||
|
|
||||||
class RepeatButton extends StatefulWidget {
|
class RepeatButton extends StatefulWidget {
|
||||||
|
|
||||||
|
double size;
|
||||||
|
RepeatButton({this.size, Key key}): super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RepeatButtonState createState() => _RepeatButtonState();
|
_RepeatButtonState createState() => _RepeatButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RepeatButtonState extends State<RepeatButton> {
|
class _RepeatButtonState extends State<RepeatButton> {
|
||||||
|
|
||||||
|
double _size = ScreenUtil().setWidth(46);
|
||||||
|
|
||||||
Icon get icon {
|
Icon get icon {
|
||||||
switch (playerHelper.repeatType) {
|
switch (playerHelper.repeatType) {
|
||||||
case RepeatType.NONE:
|
case RepeatType.NONE:
|
||||||
return Icon(Icons.repeat);
|
return Icon(Icons.repeat, size: widget.size??_size);
|
||||||
case RepeatType.LIST:
|
case RepeatType.LIST:
|
||||||
return Icon(
|
return Icon(
|
||||||
Icons.repeat,
|
Icons.repeat,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
|
size: widget.size??_size
|
||||||
);
|
);
|
||||||
case RepeatType.TRACK:
|
case RepeatType.TRACK:
|
||||||
return Icon(
|
return Icon(
|
||||||
Icons.repeat_one,
|
Icons.repeat_one,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
|
size: widget.size??_size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -453,7 +573,10 @@ class _RepeatButtonState extends State<RepeatButton> {
|
|||||||
await playerHelper.changeRepeat();
|
await playerHelper.changeRepeat();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: icon,
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: icon,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,13 +628,13 @@ class _SeekBarState extends State<SeekBar> {
|
|||||||
Text(
|
Text(
|
||||||
_timeString(position),
|
_timeString(position),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0
|
fontSize: ScreenUtil().setSp(35)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_timeString(duration),
|
_timeString(duration),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0
|
fontSize: ScreenUtil().setSp(35)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -126,7 +126,8 @@ class SearchResultsScreen extends StatelessWidget {
|
|||||||
'Tracks',
|
'Tracks',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0
|
fontSize: 26.0,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...List.generate(3, (i) {
|
...List.generate(3, (i) {
|
||||||
@ -170,7 +171,8 @@ class SearchResultsScreen extends StatelessWidget {
|
|||||||
'Albums',
|
'Albums',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0
|
fontSize: 26.0,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...List.generate(3, (i) {
|
...List.generate(3, (i) {
|
||||||
@ -208,7 +210,8 @@ class SearchResultsScreen extends StatelessWidget {
|
|||||||
'Artists',
|
'Artists',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0
|
fontSize: 26.0,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(height: 4),
|
Container(height: 4),
|
||||||
@ -243,7 +246,8 @@ class SearchResultsScreen extends StatelessWidget {
|
|||||||
'Playlists',
|
'Playlists',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0
|
fontSize: 26.0,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...List.generate(3, (i) {
|
...List.generate(3, (i) {
|
||||||
|
@ -136,7 +136,16 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||||||
updateTheme();
|
updateTheme();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
SimpleDialogOption(
|
||||||
|
child: Text('Deezer (Dark)'),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => settings.theme = Themes.Deezer);
|
||||||
|
settings.save();
|
||||||
|
updateTheme();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -274,9 +283,13 @@ class _QualityPickerState extends State<QualityPicker> {
|
|||||||
});
|
});
|
||||||
switch (widget.field) {
|
switch (widget.field) {
|
||||||
case 'mobile':
|
case 'mobile':
|
||||||
settings.mobileQuality = _quality; break;
|
settings.mobileQuality = _quality;
|
||||||
|
settings.updateAudioServiceQuality();
|
||||||
|
break;
|
||||||
case 'wifi':
|
case 'wifi':
|
||||||
settings.wifiQuality = _quality; break;
|
settings.wifiQuality = _quality;
|
||||||
|
settings.updateAudioServiceQuality();
|
||||||
|
break;
|
||||||
case 'download':
|
case 'download':
|
||||||
settings.downloadQuality = _quality; break;
|
settings.downloadQuality = _quality; break;
|
||||||
case 'offline':
|
case 'offline':
|
||||||
|
@ -129,7 +129,7 @@ class ArtistTile extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0
|
fontSize: 14.0
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(height: 4,),
|
Container(height: 4,),
|
||||||
@ -228,7 +228,7 @@ class PlaylistCardTile extends StatelessWidget {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(fontSize: 16.0),
|
style: TextStyle(fontSize: 14.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(height: 8.0,)
|
Container(height: 8.0,)
|
||||||
@ -271,7 +271,7 @@ class SmartTrackListTile extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0
|
fontSize: 14.0
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -315,7 +315,7 @@ class AlbumCard extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0
|
fontSize: 14.0
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|