diff --git a/README.md b/README.md index 5bc6771..ad3c524 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # 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. If you want to report bug or request feature, please open an issue. @@ -17,25 +19,27 @@ Compile: flutter pub get flutter build apk ``` -## just_audio -This app depends on modified just_audio plugin with Deezer support. Repo: https://notabug.org/exttex/just_audio ## Telegram https://t.me/freezerandroid ## Credits Tobs: Beta tester +Bas Curtiz: Icon, Logo, Banner, Design suggestions Deemix: https://notabug.org/RemixDev/deemix 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 ``` Freezer was not developed for piracy, but educational and private usage. It may be illegal to use this in your country! I am not responsible in any way for the usage of this app. -``` - - -## Support me -BTC: `14hcr4PGbgqeXd3SoXY9QyJFNpyurgrL9y` -ETH: `0xb4D1893195404E1F4b45e5BDA77F202Ac4012288` \ No newline at end of file +``` \ No newline at end of file diff --git a/android/app/src/main/res/drawable-hdpi/ic_logo.png b/android/app/src/main/res/drawable-hdpi/ic_logo.png index ebe557e..691dd95 100644 Binary files a/android/app/src/main/res/drawable-hdpi/ic_logo.png and b/android/app/src/main/res/drawable-hdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_logo.png b/android/app/src/main/res/drawable-mdpi/ic_logo.png index d5bb07a..32d0fd5 100644 Binary files a/android/app/src/main/res/drawable-mdpi/ic_logo.png and b/android/app/src/main/res/drawable-mdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_logo.png b/android/app/src/main/res/drawable-xhdpi/ic_logo.png index e535ac2..5da585d 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/ic_logo.png and b/android/app/src/main/res/drawable-xhdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_logo.png b/android/app/src/main/res/drawable-xxhdpi/ic_logo.png index ccdce98..1611d13 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_logo.png and b/android/app/src/main/res/drawable-xxhdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png b/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png index d01c567..78a8ea4 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png and b/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index e2fe9c3..766a8a3 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index d253b43..3832602 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 970aaae..88d8fed 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 0329859..236aa17 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 3d7313e..13e5f9a 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/assets/cover_thumb.jpg b/assets/cover_thumb.jpg new file mode 100644 index 0000000..584350e Binary files /dev/null and b/assets/cover_thumb.jpg differ diff --git a/assets/fonts/Jost-Italic.ttf b/assets/fonts/Jost-Italic.ttf deleted file mode 100644 index 5b38c72..0000000 Binary files a/assets/fonts/Jost-Italic.ttf and /dev/null differ diff --git a/assets/fonts/MabryPro.otf b/assets/fonts/MabryPro.otf new file mode 100644 index 0000000..c922119 Binary files /dev/null and b/assets/fonts/MabryPro.otf differ diff --git a/assets/fonts/MabryProBlack.otf b/assets/fonts/MabryProBlack.otf new file mode 100644 index 0000000..63ea34e Binary files /dev/null and b/assets/fonts/MabryProBlack.otf differ diff --git a/assets/fonts/MabryProBold.otf b/assets/fonts/MabryProBold.otf new file mode 100644 index 0000000..3de5235 Binary files /dev/null and b/assets/fonts/MabryProBold.otf differ diff --git a/assets/fonts/MabryProItalic.otf b/assets/fonts/MabryProItalic.otf new file mode 100644 index 0000000..2886b69 Binary files /dev/null and b/assets/fonts/MabryProItalic.otf differ diff --git a/assets/fonts/Montserrat-Bold.ttf b/assets/fonts/Montserrat-Bold.ttf deleted file mode 100644 index 221819b..0000000 Binary files a/assets/fonts/Montserrat-Bold.ttf and /dev/null differ diff --git a/assets/fonts/Montserrat-Italic.ttf b/assets/fonts/Montserrat-Italic.ttf deleted file mode 100644 index eb4232a..0000000 Binary files a/assets/fonts/Montserrat-Italic.ttf and /dev/null differ diff --git a/assets/fonts/Montserrat-Regular.ttf b/assets/fonts/Montserrat-Regular.ttf deleted file mode 100644 index 8d443d5..0000000 Binary files a/assets/fonts/Montserrat-Regular.ttf and /dev/null differ diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..62b09ec Binary files /dev/null and b/assets/icon.png differ diff --git a/lib/main.dart b/lib/main.dart index 42e61b6..8b147a9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -43,6 +43,10 @@ class _FreezerAppState extends State { void initState() { //Make update theme global updateTheme = _updateTheme; + + //Precache placeholder + precacheImage(imagesDatabase.placeholderThumb, context); + super.initState(); } diff --git a/lib/settings.dart b/lib/settings.dart index fd09f54..1cef627 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -67,11 +67,13 @@ class Settings { Settings({this.downloadPath, this.arl}); + static const deezerBg = Color(0xFF1F1A16); + static const font = 'MabryPro'; ThemeData get themeData { switch (theme??Themes.Light) { case Themes.Light: return ThemeData( - fontFamily: 'Montserrat', + fontFamily: font, primaryColor: primaryColor, accentColor: primaryColor, sliderTheme: _sliderTheme, @@ -79,16 +81,33 @@ class Settings { ); case Themes.Dark: return ThemeData( - fontFamily: 'Montserrat', + fontFamily: font, brightness: Brightness.dark, primaryColor: primaryColor, accentColor: primaryColor, sliderTheme: _sliderTheme, 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: return ThemeData( - fontFamily: 'Montserrat', + fontFamily: font, brightness: Brightness.dark, primaryColor: primaryColor, accentColor: primaryColor, @@ -185,10 +204,12 @@ enum AudioQuality { enum Themes { Light, Dark, + Deezer, Black } enum DownloadNaming { DEFAULT, - STANDALONE + STANDALONE, + } \ No newline at end of file diff --git a/lib/settings.g.dart b/lib/settings.g.dart index 6d7ca97..eedd0d3 100644 --- a/lib/settings.g.dart +++ b/lib/settings.g.dart @@ -99,5 +99,6 @@ const _$DownloadNamingEnumMap = { const _$ThemesEnumMap = { Themes.Light: 'Light', Themes.Dark: 'Dark', + Themes.Deezer: 'Deezer', Themes.Black: 'Black', }; diff --git a/lib/ui/cached_image.dart b/lib/ui/cached_image.dart index 57be016..1dda7c5 100644 --- a/lib/ui/cached_image.dart +++ b/lib/ui/cached_image.dart @@ -27,6 +27,8 @@ class ImagesDatabase { Database db; String imagesPath; + ImageProvider placeholderThumb = new AssetImage('assets/cover_thumb.jpg'); + //Prepare database Future init() async { String dir = await getDatabasesPath(); @@ -82,7 +84,7 @@ class ImagesDatabase { Future getPaletteGenerator(String url) async { String path = await getImage(url); //Get image provider - ImageProvider provider = AssetImage('assets/cover.jpg'); + ImageProvider provider = placeholderThumb; if (path != null) { provider = FileImage(File(path)); } @@ -120,8 +122,7 @@ class CachedImage extends StatefulWidget { class _CachedImageState extends State { - final ImageProvider _placeholder = AssetImage('assets/cover.jpg'); - ImageProvider _image = AssetImage('assets/cover.jpg'); + ImageProvider _image = imagesDatabase.placeholderThumb; double _opacity = 0.0; bool _disposed = false; String _prevUrl; @@ -135,7 +136,7 @@ class _CachedImageState extends State { } //Load image from db String path = await imagesDatabase.getImage(widget.url); - if (path == null) return _placeholder; + if (path == null) return imagesDatabase.placeholderThumb; return FileImage(File(path)); } @@ -177,10 +178,10 @@ class _CachedImageState extends State { widget.circular ? CircleAvatar( radius: (widget.width??widget.height), - backgroundImage: _placeholder, + backgroundImage: imagesDatabase.placeholderThumb, ): Image( - image: _placeholder, + image: imagesDatabase.placeholderThumb, height: widget.height, width: widget.width, ), diff --git a/lib/ui/details_screens.dart b/lib/ui/details_screens.dart index 88d0896..b08e06d 100644 --- a/lib/ui/details_screens.dart +++ b/lib/ui/details_screens.dart @@ -48,7 +48,7 @@ class AlbumDetails extends StatelessWidget { Container(height: 8.0,), CachedImage( url: album.art.full, - height: 256.0, + width: MediaQuery.of(context).size.width / 2 ), Container(height: 8,), Text( @@ -259,11 +259,10 @@ class ArtistDetails extends StatelessWidget { children: [ CachedImage( url: artist.picture.full, - height: 200, + width: MediaQuery.of(context).size.width / 2 - 8, ), Container( - width: 200.0, - height: 220, + width: MediaQuery.of(context).size.width / 2 - 8, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -500,11 +499,10 @@ class _PlaylistDetailsState extends State { children: [ CachedImage( url: playlist.image.full, - height: 180.0, + height: MediaQuery.of(context).size.width / 2 - 8, ), Container( - width: 180, - height: 200, //Card padding + width: MediaQuery.of(context).size.width / 2 - 8, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/ui/home_screen.dart b/lib/ui/home_screen.dart index 61fa8f5..2d54e2d 100644 --- a/lib/ui/home_screen.dart +++ b/lib/ui/home_screen.dart @@ -11,19 +11,18 @@ import '../settings.dart'; class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - //TODO: SingleChildScrollView vs ListView speed/perf return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: EdgeInsets.symmetric(vertical: 16.0), + SafeArea( child: FreezerTitle(), ), Flexible(child: HomePageScreen(),) ], ), ); + /* return ListView( children: [ @@ -34,22 +33,33 @@ class HomeScreen extends StatelessWidget { HomePageScreen() ], ); - - */ + */ } } class FreezerTitle extends StatelessWidget { @override Widget build(BuildContext context) { - return Text( - 'freezer', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Jost', - fontSize: 75, - fontStyle: FontStyle.italic, - letterSpacing: 7 + return Padding( + padding: EdgeInsets.fromLTRB(0, 24, 0, 8), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + 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 { textAlign: TextAlign.left, maxLines: 2, 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) ), diff --git a/lib/ui/player_bar.dart b/lib/ui/player_bar.dart index 6d422d3..1de6df9 100644 --- a/lib/ui/player_bar.dart +++ b/lib/ui/player_bar.dart @@ -121,40 +121,45 @@ class PlayPauseButton extends StatelessWidget { @override Widget build(BuildContext context) { - //Playing - if (AudioService.playbackState?.playing??false) { - return IconButton( - iconSize: this.size, - icon: Icon(Icons.pause), - onPressed: () => AudioService.pause() - ); - } + return StreamBuilder( + stream: AudioService.playbackStateStream, + builder: (context, snapshot) { + //Playing + if (AudioService.playbackState?.playing??false) { + return IconButton( + iconSize: this.size, + icon: Icon(Icons.pause), + onPressed: () => AudioService.pause() + ); + } - //Paused - if ((!AudioService.playbackState.playing && - AudioService.playbackState.processingState == AudioProcessingState.ready) || - //None state (stopped) - AudioService.playbackState.processingState == AudioProcessingState.none) { - return IconButton( - iconSize: this.size, - icon: Icon(Icons.play_arrow), - onPressed: () => AudioService.play() - ); - } + //Paused + if ((!AudioService.playbackState.playing && + AudioService.playbackState.processingState == AudioProcessingState.ready) || + //None state (stopped) + AudioService.playbackState.processingState == AudioProcessingState.none) { + return IconButton( + iconSize: this.size, + icon: Icon(Icons.play_arrow), + onPressed: () => AudioService.play() + ); + } - switch (AudioService.playbackState.processingState) { - //Stopped/Error - case AudioProcessingState.error: - case AudioProcessingState.none: - case AudioProcessingState.stopped: - return Container(width: this.size, height: this.size); - //Loading, connecting, rewinding... - default: - return Container( - width: this.size, - height: this.size, - child: CircularProgressIndicator(), - ); - } + switch (AudioService.playbackState.processingState) { + //Stopped/Error + case AudioProcessingState.error: + case AudioProcessingState.none: + case AudioProcessingState.stopped: + return Container(width: this.size, height: this.size); + //Loading, connecting, rewinding... + default: + return Container( + width: this.size, + height: this.size, + child: CircularProgressIndicator(), + ); + } + }, + ); } } diff --git a/lib/ui/player_screen.dart b/lib/ui/player_screen.dart index a31d3fe..28ca37f 100644 --- a/lib/ui/player_screen.dart +++ b/lib/ui/player_screen.dart @@ -3,9 +3,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:audio_service/audio_service.dart'; +import 'package:flutter_screenutil/screenutil.dart'; import 'package:freezer/api/deezer.dart'; import 'package:freezer/api/player.dart'; import 'package:freezer/ui/menu.dart'; +import 'package:freezer/ui/settings_screen.dart'; import 'package:freezer/ui/tiles.dart'; import 'package:async/async.dart'; @@ -13,9 +15,6 @@ import 'cached_image.dart'; import '../api/definitions.dart'; import 'player_bar.dart'; - - - class PlayerScreen extends StatefulWidget { @override _PlayerScreenState createState() => _PlayerScreenState(); @@ -23,242 +22,35 @@ class PlayerScreen extends StatefulWidget { class _PlayerScreenState extends State { - double iconSize = 48; - bool _lyrics = false; @override Widget build(BuildContext context) { + //Responsive + ScreenUtil.init(context, allowFontScaling: true); + return Scaffold( body: SafeArea( child: StreamBuilder( stream: StreamZip([AudioService.playbackStateStream, AudioService.currentMediaItemStream]), 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 if (AudioService.currentMediaItem == null) { playerHelper.startService(); return Center(child: CircularProgressIndicator(),); } - return OrientationBuilder( - builder: (context, orientation) { - //Landscape - if (orientation == Orientation.landscape) { - return Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(16, 0, 16, 8), - child: Container( - width: 320, - child: Stack( - children: [ - 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: [ - Padding( - padding: EdgeInsets.fromLTRB(8, 16, 8, 0), - child: Container( - width: 300, - child: PlayerScreenTopRow(), - ) - ), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - 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: [ - 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: [ - 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); - }, - ) - ], - ), - ) - ) - ], - ), - ) - ], - ); - } + return OrientationBuilder( + builder: (context, orientation) { + //Landscape + if (orientation == Orientation.landscape) { + return PlayerScreenHorizontal(); + } + //Portrait + return PlayerScreenVertical(); + }, + ); - //Portrait - return Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 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: [ - 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: [ - 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: [ - 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: [ - 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 { } } +//Landscape +class PlayerScreenHorizontal extends StatefulWidget { + @override + _PlayerScreenHorizontalState createState() => _PlayerScreenHorizontalState(); +} + +class _PlayerScreenHorizontalState extends State { + + double iconSize = ScreenUtil().setWidth(64); + bool _lyrics = false; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Container( + width: ScreenUtil().setWidth(500), + child: Stack( + children: [ + 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: [ + 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: [ + 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: [ + 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: [ + 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 { + double iconSize = ScreenUtil().setWidth(100); + bool _lyrics = false; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 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: [ + 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: [ + 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: [ + 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: [ + 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 { final Lyrics lyrics; @@ -287,8 +338,11 @@ class _LyricsWidgetState extends State { Timer _timer; int _currentIndex; double _boxHeight; + String _trackId; Future _load() async { + _trackId = widget.trackId; + //Get text color by album art (black or white) if (widget.artUri != null) { bool bw = await imagesDatabase.isDark(widget.artUri); @@ -298,7 +352,7 @@ class _LyricsWidgetState extends State { if (widget.lyrics.lyrics == null || widget.lyrics.lyrics.length == 0) { //Load from api try { - _l = await deezerAPI.lyrics(widget.trackId); + _l = await deezerAPI.lyrics(_trackId); setState(() => _loading = false); } catch (e) { //Error Lyrics @@ -315,6 +369,7 @@ class _LyricsWidgetState extends State { void initState() { this._boxHeight = widget.height??400.0; _load(); + Timer.periodic(Duration(milliseconds: 500), (timer) { _timer = timer; if (_loading) return; @@ -340,6 +395,18 @@ class _LyricsWidgetState extends State { super.dispose(); } + @override + void didUpdateWidget(LyricsWidget oldWidget) { + if (this._trackId != widget.trackId) { + setState(() { + _loading = true; + this._trackId = widget.trackId; + }); + _load(); + } + super.didUpdateWidget(oldWidget); + } + @override Widget build(BuildContext context) { return Container( @@ -360,15 +427,31 @@ class _LyricsWidgetState extends State { return Container( height: _boxHeight, child: Center( - child: Text( - _l.lyrics[i].text, - textAlign: TextAlign.center, - style: TextStyle( - color: _textColor, - fontSize: 40.0, - fontWeight: (_currentIndex == i)?FontWeight.bold:FontWeight.normal - ), - ), + child: Stack( + children: [ + Text( + _l.lyrics[i].text, + textAlign: TextAlign.center, + style: TextStyle( + 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 { //Top row containing QueueSource, queue... class PlayerScreenTopRow extends StatelessWidget { + + double textSize; + double iconSize; + double textWidth; + bool short; + PlayerScreenTopRow({this.textSize, this.iconSize, this.textWidth, this.short}); + @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Playing from: ' + playerHelper.queueSource.text, - maxLines: 1, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.right, - style: TextStyle(fontSize: 16.0), + Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 0, 8, 0), + child: InkWell( + 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( mainAxisSize: MainAxisSize.min, children: [ - RepeatButton(), + RepeatButton(size: this.iconSize), Container(width: 16.0,), InkWell( - child: Icon(Icons.menu), + child: Container( + padding: EdgeInsets.all(8.0), + child: Icon(Icons.menu, size: this.iconSize??ScreenUtil().setWidth(46)), + ), onTap: (){ Navigator.of(context).push(MaterialPageRoute( builder: (context) => QueueScreen() @@ -418,25 +530,33 @@ class PlayerScreenTopRow extends StatelessWidget { class RepeatButton extends StatefulWidget { + + double size; + RepeatButton({this.size, Key key}): super(key: key); + @override _RepeatButtonState createState() => _RepeatButtonState(); } class _RepeatButtonState extends State { + double _size = ScreenUtil().setWidth(46); + Icon get icon { switch (playerHelper.repeatType) { case RepeatType.NONE: - return Icon(Icons.repeat); + return Icon(Icons.repeat, size: widget.size??_size); case RepeatType.LIST: return Icon( Icons.repeat, color: Theme.of(context).primaryColor, + size: widget.size??_size ); case RepeatType.TRACK: return Icon( Icons.repeat_one, color: Theme.of(context).primaryColor, + size: widget.size??_size ); } } @@ -453,7 +573,10 @@ class _RepeatButtonState extends State { await playerHelper.changeRepeat(); setState(() {}); }, - child: icon, + child: Container( + padding: EdgeInsets.all(8.0), + child: icon, + ), ); } } @@ -505,13 +628,13 @@ class _SeekBarState extends State { Text( _timeString(position), style: TextStyle( - fontSize: 14.0 + fontSize: ScreenUtil().setSp(35) ), ), Text( _timeString(duration), style: TextStyle( - fontSize: 14.0 + fontSize: ScreenUtil().setSp(35) ), ) ], diff --git a/lib/ui/search.dart b/lib/ui/search.dart index 02c3aea..7ba12de 100644 --- a/lib/ui/search.dart +++ b/lib/ui/search.dart @@ -126,7 +126,8 @@ class SearchResultsScreen extends StatelessWidget { 'Tracks', textAlign: TextAlign.center, style: TextStyle( - fontSize: 26.0 + fontSize: 26.0, + fontWeight: FontWeight.bold ), ), ...List.generate(3, (i) { @@ -170,7 +171,8 @@ class SearchResultsScreen extends StatelessWidget { 'Albums', textAlign: TextAlign.center, style: TextStyle( - fontSize: 26.0 + fontSize: 26.0, + fontWeight: FontWeight.bold ), ), ...List.generate(3, (i) { @@ -208,7 +210,8 @@ class SearchResultsScreen extends StatelessWidget { 'Artists', textAlign: TextAlign.center, style: TextStyle( - fontSize: 26.0 + fontSize: 26.0, + fontWeight: FontWeight.bold ), ), Container(height: 4), @@ -243,7 +246,8 @@ class SearchResultsScreen extends StatelessWidget { 'Playlists', textAlign: TextAlign.center, style: TextStyle( - fontSize: 26.0 + fontSize: 26.0, + fontWeight: FontWeight.bold ), ), ...List.generate(3, (i) { diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index f72d62d..f3795b4 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -136,7 +136,16 @@ class _AppearanceSettingsState extends State { updateTheme(); 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 { }); switch (widget.field) { case 'mobile': - settings.mobileQuality = _quality; break; + settings.mobileQuality = _quality; + settings.updateAudioServiceQuality(); + break; case 'wifi': - settings.wifiQuality = _quality; break; + settings.wifiQuality = _quality; + settings.updateAudioServiceQuality(); + break; case 'download': settings.downloadQuality = _quality; break; case 'offline': diff --git a/lib/ui/tiles.dart b/lib/ui/tiles.dart index c511f4c..585ebfe 100644 --- a/lib/ui/tiles.dart +++ b/lib/ui/tiles.dart @@ -129,7 +129,7 @@ class ArtistTile extends StatelessWidget { textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 16.0 + fontSize: 14.0 ), ), Container(height: 4,), @@ -228,7 +228,7 @@ class PlaylistCardTile extends StatelessWidget { maxLines: 1, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 16.0), + style: TextStyle(fontSize: 14.0), ), ), Container(height: 8.0,) @@ -271,7 +271,7 @@ class SmartTrackListTile extends StatelessWidget { textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 16.0 + fontSize: 14.0 ), ), ), @@ -315,7 +315,7 @@ class AlbumCard extends StatelessWidget { textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 16.0 + fontSize: 14.0 ), ), ),