New icon, banner, New font, Player UI fixes, Details UI fixed

This commit is contained in:
exttex 2020-07-18 23:45:48 +02:00
parent 4b3d264e2a
commit 691f7865d5
32 changed files with 516 additions and 329 deletions

View File

@ -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`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
assets/cover_thumb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

BIN
assets/fonts/MabryPro.otf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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':

View File

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