Merge pull request #39 from PetitPrinc3/master

MOD / Collaboration (Implement Deezer' app UI)
This commit is contained in:
DJDoubleD
2024-11-02 19:15:57 +01:00
committed by GitHub
52 changed files with 2188 additions and 1425 deletions

1
.gitignore vendored
View File

@@ -69,6 +69,7 @@ app.*.map.json
# Envied
.env
lib/utils/env.g.dart
lib/.env
# Possible translations remnants
translations/refreezer.zip

View File

@@ -1,13 +1,13 @@
![ReFreezer](./assets/banner.png?raw=true)
![Deezer](./assets/banner.png?raw=true)
[![Latest Version](https://img.shields.io/github/v/release/DJDoubleD/ReFreezer?color=blue)](../../releases/latest)
[![Release date](https://img.shields.io/github/release-date/DJDoubleD/ReFreezer)](../../releases/latest)
[![Downloads Latest](https://img.shields.io/github/downloads/DJDoubleD/ReFreezer/latest/total?color=blue&label=downloads%20latest)](../../releases)
[![Downloads Total](https://img.shields.io/github/downloads/DJDoubleD/ReFreezer/total?color=blue&label=downloads%20total)](../../releases)
[![Latest Version](https://img.shields.io/github/v/release/PetitPrinc3/Deezer?color=blue)](../../releases/latest)
[![Release date](https://img.shields.io/github/release-date/PetitPrinc3/Deezer)](../../releases/latest)
[![Downloads Original](https://img.shields.io/github/downloads/DJDoubleD/ReFreezer/total?color=blue&label=downloads%20total)](../../releases)
[![Downloads MOD](https://img.shields.io/github/downloads/PetitPrinc3/Deezer/total?color=blue&label=downloads%20total)](../../releases)
[![Flutter Version](https://shields.io/badge/Flutter-v3.24.4-darkgreen.svg)](https://docs.flutter.dev/tools/sdk)
[![Dart Version](https://shields.io/badge/Dart-v3.5.4-darkgreen.svg)](https://dart.dev/get-dart)
[![Crowdin](https://badges.crowdin.net/refreezer/localized.svg)](https://crowdin.com/project/refreezer)
[![License](https://img.shields.io/github/license/DJDoubleD/ReFreezer?flat)](./LICENSE)
[![License](https://img.shields.io/github/license/PetitPrinc3/Deezer?flat)](./LICENSE)
[![Dart](https://img.shields.io/badge/Dart-0175C2?style=for-the-badge&logo=dart&logoColor=white)](https://dart.dev/)
[![Flutter](https://img.shields.io/badge/Flutter-02569B?style=for-the-badge&logo=flutter&logoColor=white)](https://flutter.dev/)
@@ -15,17 +15,26 @@
---
An alternative Deezer music streaming & downloading client, based on Freezer.
The entire codebase has been updated/rewritten to be compatible with the latest version of flutter, the dart SDK & android (current build target is API level 34).
This repo is a MOD of the [ReFreezer](https://github.com/DJDoubleD/ReFreezer) app by @DJDoubleD.
My goal is to have a style closer to Deezer's original app.
To apply the Deezer theme, select it under Settings > Appearance > Theme.
## Screenshots
<p align="center">
<img src="./assets/screenshots/Mod_home.png" width=200>
<img src="./assets/screenshots/Mod_player.png" width=200>
<img src="./assets/screenshots/Mod_search.png" width=200>
</p>
<details><summary><b>Original ReFreezer App</b></summary>
<p align="center">
<img src="./assets/screenshots/Login.jpg" width=200>
<img src="./assets/screenshots/Home.jpg" width=200>
<img src="./assets/screenshots/Player.jpg" width=200>
<img src="./assets/screenshots/Lyrics.jpg" width=200>
</p>
</details>
<details><summary><b>More Android Phone</b></summary>
<p align="center">
@@ -37,7 +46,6 @@ The entire codebase has been updated/rewritten to be compatible with the latest
<img src="./assets/screenshots/PlayerHorizontal.jpg" height=200>
</p>
</details>
</br>
<details><summary><b>Android Auto</b></summary>
<p align="center">
<img src="./assets/screenshots/Android_Auto-Head_Unit-home.png" max-height=400>
@@ -49,6 +57,11 @@ The entire codebase has been updated/rewritten to be compatible with the latest
## Features & changes
### Not working / On going
- Offline playlist / titles access
- Explore / Favorites page
### ReFreezer :
- Restored all features of the old Freezer app, most notably:
- Restored all login options
- Restored Highest quality streaming and download options (premium account required, free accounts limited to MP3 128kbps)
@@ -66,6 +79,13 @@ The entire codebase has been updated/rewritten to be compatible with the latest
- Removed the need of custom just_audio & audio_service plugin versions & refactored source code to use the latest version of the official plugins
- Multiple other fixes
### MOD :
- Floating player bar with background color based on title artwork
- Deezer original icons
- Deezer original navigation menu (+ settings)
- Deezer clone player screen
- Deezer similar info menu
## Compile from source
Install the latest flutter SDK: <https://flutter.dev/docs/get-started/install>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 997 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -13,6 +13,6 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowBackground">#0F0D13</item>
</style>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_favorites_background">#3DDC84</color>
<color name="ic_favorites_background">#0F0D13</color>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#1C1C14</color>
<color name="ic_launcher_background">#0F0D13</color>
</resources>

BIN
assets/DJDoubleD.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/PetitPrince.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

BIN
assets/app_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 58 KiB

BIN
assets/fonts/Deezer.ttf Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Submodule custom_navigator deleted from bef1badfa6

Submodule external_path deleted from c753b1a154

162
lib/fonts/deezer_icons.dart Normal file
View File

@@ -0,0 +1,162 @@
/// Flutter icons Deezer
/// Copyright (C) 2024 by original authors @ fluttericon.com, fontello.com
/// This font was generated by FlutterIcon.com, which is derived from Fontello.
///
/// To use this font, place it in your fonts/ directory and include the
/// following in your pubspec.yaml
///
/// flutter:
/// fonts:
/// - family: Deezer
/// fonts:
/// - asset: fonts/Deezer.ttf
///
///
///
import 'package:flutter/widgets.dart';
class DeezerIcons {
DeezerIcons._();
static const _kFontFam = 'Deezer';
static const String? _kFontPkg = null;
static const IconData skip_back =
IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData shuffle_small =
IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData share_android_small =
IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData share_android =
IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData shuffle =
IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData settings_with_badge =
IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData settings_small =
IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData settings =
IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData search =
IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData search_small =
IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData search_fill_small =
IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData search_fill =
IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData repeat_small =
IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData repeat_one_small =
IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData repeat_one =
IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData repeat =
IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData queue_small =
IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData queue =
IconData(0xe812, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData play_before =
IconData(0xe813, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData play_before_small =
IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData pen_small =
IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData pen =
IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData offline_small =
IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData offline =
IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData microphone_small =
IconData(0xe819, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData microphone_show_small =
IconData(0xe81a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData microphone_show =
IconData(0xe81b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData microphone_show_fill_small =
IconData(0xe81c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData microphone_show_fill =
IconData(0xe81d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData microphone =
IconData(0xe81e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData house =
IconData(0xe81f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData house_small =
IconData(0xe820, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData house_fill =
IconData(0xe821, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData house_fill_small =
IconData(0xe822, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData heart_small =
IconData(0xe823, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData heart_fill_small =
IconData(0xe824, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData heart =
IconData(0xe825, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData heart_fill =
IconData(0xe826, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData download_small =
IconData(0xe827, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData download_fill_small =
IconData(0xe828, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData download =
IconData(0xe829, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData download_fill =
IconData(0xe82a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData cross_small =
IconData(0xe82b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData compass_small =
IconData(0xe82c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData cross =
IconData(0xe82d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData compass =
IconData(0xe82e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData compass_fill_small =
IconData(0xe82f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData compass_fill =
IconData(0xe830, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData angry_face_small =
IconData(0xe833, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData angry_face =
IconData(0xe834, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData album =
IconData(0xe835, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData album_small =
IconData(0xe836, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData trash =
IconData(0xe837, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData trash_small =
IconData(0xe838, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData skip_next_fill =
IconData(0xe839, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData queue_1 =
IconData(0xe84e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData queue_small_2 =
IconData(0xe84f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData play =
IconData(0xe850, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData play_small =
IconData(0xe851, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData play_fill_small =
IconData(0xe852, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData pause_small =
IconData(0xe857, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData pause =
IconData(0xe858, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData pause_fill_small =
IconData(0xe859, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData album_small_1 =
IconData(0xe877, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData album_1 =
IconData(0xe879, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData queue_small_1 =
IconData(0xe890, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData sort_small =
IconData(0xe8bf, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData sort =
IconData(0xe8c1, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData more_vert =
IconData(0xe831, fontFamily: _kFontFam, fontPackage: _kFontPkg);
}

View File

@@ -2,6 +2,9 @@ import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:custom_navigator/custom_navigator.dart';
import 'package:refreezer/fonts/deezer_icons.dart';
import 'package:refreezer/ui/restartable.dart';
import 'package:refreezer/ui/settings_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
@@ -12,7 +15,6 @@ import 'package:logging/logging.dart';
import 'package:move_to_background/move_to_background.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:refreezer/ui/restartable.dart';
//import 'package:restart_app/restart_app.dart';
import 'api/cache.dart';
@@ -97,7 +99,7 @@ class _ReFreezerAppState extends State<ReFreezerApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ReFreezer',
title: 'Deezer',
shortcuts: <ShortcutActivator, Intent>{
...WidgetsApp.defaultShortcuts,
LogicalKeySet(LogicalKeyboardKey.select):
@@ -202,8 +204,10 @@ class _MainScreenState extends State<MainScreen>
late final AppLifecycleListener _lifeCycleListener;
final List<Widget> _screens = [
const HomeScreen(),
const LibraryScreen(),
const LibraryPlaylists(),
const SearchScreen(),
const LibraryScreen()
const SettingsScreen()
];
Future<void>? _initialization;
int _selected = 0;
@@ -427,15 +431,26 @@ class _MainScreenState extends State<MainScreen>
onKeyEvent: (event) =>
_handleKey(event, navigationBarFocusNode, screenFocusNode),
child: Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: Container(
margin: EdgeInsets.fromLTRB(6, 0, 6, 0),
decoration: BoxDecoration(
color: Colors.transparent,
),
child: const PlayerBar(),
),
bottomNavigationBar: FocusScope(
node: navigationBarFocusNode,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const PlayerBar(),
BottomNavigationBar(
backgroundColor:
Theme.of(context).bottomAppBarTheme.color,
child: Theme(
data: Theme.of(context).copyWith(
canvasColor:
Theme.of(context).scaffoldBackgroundColor),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
unselectedItemColor:
Theme.of(context).unselectedWidgetColor,
currentIndex: _selected,
onTap: (int index) async {
//Pop all routes until home screen
@@ -448,29 +463,44 @@ class _MainScreenState extends State<MainScreen>
setState(() {
_selected = index;
});
//Fix statusbar
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
));
},
selectedItemColor: Theme.of(context).primaryColor,
selectedItemColor:
settings.primaryColor.withOpacity(0.8),
showUnselectedLabels: true,
selectedLabelStyle:
TextStyle(color: settings.primaryColor),
unselectedLabelStyle:
TextStyle(color: Settings.secondaryText),
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: const Icon(Icons.home),
activeIcon: const Icon(DeezerIcons.house_fill),
icon: const Icon(DeezerIcons.house),
label: 'Home'.i18n),
BottomNavigationBarItem(
icon: const Icon(Icons.search),
activeIcon:
const Icon(DeezerIcons.compass_fill),
icon: const Icon(DeezerIcons.compass),
label: 'Explore'.i18n),
BottomNavigationBarItem(
activeIcon: const Icon(DeezerIcons.heart_fill),
icon: const Icon(DeezerIcons.heart),
label: 'Favorites'.i18n),
BottomNavigationBarItem(
activeIcon: const Icon(DeezerIcons.search_fill),
icon: const Icon(DeezerIcons.search),
label: 'Search'.i18n,
),
BottomNavigationBarItem(
icon: const Icon(Icons.library_music),
label: 'Library'.i18n)
activeIcon: const Icon(DeezerIcons.settings),
icon: const Icon(DeezerIcons.settings),
label: 'Settings'.i18n)
],
)
],
)),
))),
body: CustomNavigator(
navigatorKey: customNavigatorKey,
home: Focus(
@@ -482,9 +512,11 @@ class _MainScreenState extends State<MainScreen>
));
} else {
// While audio_service is initializing
return const Scaffold(
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(
color: Theme.of(context).primaryColor,
),
),
);
}

View File

@@ -117,10 +117,13 @@ class Settings {
//Colors
@JsonKey(toJson: _colorToJson, fromJson: _colorFromJson)
Color primaryColor = Colors.blue;
Color primaryColor = Color(0xFFA238FF);
static const deezerBg = Color(0xFF0F0D13);
static const deezerBottom = Color(0xFF1B191F);
static const secondaryText = Color(0xFFA9A6AA);
static _colorToJson(Color c) => c.value;
static _colorFromJson(int? v) => v == null ? Colors.blue : Color(v);
static _colorFromJson(int? v) => v == null ? Color(0xFFA238FF) : Color(v);
@JsonKey(defaultValue: false)
bool useArtColor = false;
@@ -195,9 +198,13 @@ class Settings {
}
SliderThemeData get _sliderTheme => SliderThemeData(
thumbColor: primaryColor,
activeTrackColor: primaryColor,
inactiveTrackColor: primaryColor.withOpacity(0.2));
inactiveTrackColor: primaryColor.withOpacity(0.2),
trackHeight: 0.5,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 1),
thumbColor: primaryColor,
overlayShape: RoundSliderOverlayShape(overlayRadius: 4),
overlayColor: primaryColor.withOpacity(0.2));
//Load settings/init
Future<Settings> loadSettings() async {
@@ -255,8 +262,6 @@ class Settings {
return true;
}
static const deezerBg = Color(0xFF1F1A16);
static const deezerBottom = Color(0xFF1b1714);
TextTheme? get textTheme => (font == 'Deezer')
? null
: GoogleFonts.getTextTheme(font,
@@ -394,18 +399,24 @@ class Settings {
}),
),
bottomAppBarTheme:
const BottomAppBarTheme(color: Color(0xff424242))),
const BottomAppBarTheme(color: Color(0xFF0F0D13))),
Themes.Deezer: ThemeData(
useMaterial3: false,
brightness: Brightness.dark,
textTheme: textTheme,
fontFamily: _fontFamily,
primaryColor: primaryColor,
unselectedWidgetColor: secondaryText,
sliderTheme: _sliderTheme,
scaffoldBackgroundColor: deezerBg,
dialogBackgroundColor: deezerBottom,
dialogBackgroundColor: deezerBg,
hintColor: secondaryText,
inputDecorationTheme: const InputDecorationTheme(
hintStyle: TextStyle(color: secondaryText),
labelStyle: TextStyle(color: secondaryText),
),
bottomSheetTheme:
const BottomSheetThemeData(backgroundColor: deezerBottom),
const BottomSheetThemeData(backgroundColor: deezerBg),
cardColor: deezerBg,
outlinedButtonTheme: outlinedButtonTheme,
textButtonTheme: textButtonTheme,
@@ -459,15 +470,15 @@ class Settings {
return null;
}),
),
bottomAppBarTheme: const BottomAppBarTheme(color: deezerBottom)),
bottomAppBarTheme: const BottomAppBarTheme(color: deezerBg)),
Themes.Black: ThemeData(
useMaterial3: false,
brightness: Brightness.dark,
textTheme: textTheme,
fontFamily: _fontFamily,
primaryColor: primaryColor,
scaffoldBackgroundColor: Colors.black,
dialogBackgroundColor: Colors.black,
scaffoldBackgroundColor: deezerBg,
dialogBackgroundColor: deezerBg,
sliderTheme: _sliderTheme,
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Colors.black,

View File

@@ -13,8 +13,9 @@ class LeadingIcon extends StatelessWidget {
return Container(
width: 42.0,
height: 42.0,
decoration:
BoxDecoration(color: (color ?? Theme.of(context).primaryColor).withOpacity(1.0), shape: BoxShape.circle),
decoration: BoxDecoration(
color: (color ?? Theme.of(context).primaryColor).withOpacity(1.0),
shape: BoxShape.circle),
child: Icon(
icon,
color: Colors.white,
@@ -40,7 +41,8 @@ class FreezerAppBar extends StatelessWidget implements PreferredSizeWidget {
//Should be specified if bottom is specified
final double height;
const FreezerAppBar(this.title, {super.key, this.actions = const [], this.bottom, this.height = 56.0});
const FreezerAppBar(this.title,
{super.key, this.actions = const [], this.bottom, this.height = 64.0});
@override
Size get preferredSize => Size.fromHeight(height);
@@ -48,18 +50,26 @@ class FreezerAppBar extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(primaryColor: (Theme.of(context).brightness == Brightness.light) ? Colors.white : Colors.black),
data: ThemeData(
primaryColor: (Theme.of(context).brightness == Brightness.light)
? Colors.white
: Colors.black),
child: AppBar(
systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Theme.of(context).brightness),
systemOverlayStyle: SystemUiOverlayStyle(
statusBarBrightness: Theme.of(context).brightness),
elevation: 0.0,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: (Theme.of(context).brightness == Brightness.light) ? Colors.black : Colors.white,
title: Text(
foregroundColor: (Theme.of(context).brightness == Brightness.light)
? Colors.black
: Colors.white,
title: Container(
child: Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 48,
),
),
)),
actions: actions,
bottom: bottom as PreferredSizeWidget?,
),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:refreezer/fonts/deezer_icons.dart';
import '../api/deezer.dart';
import '../api/definitions.dart';
@@ -23,6 +24,8 @@ class HomeScreen extends StatelessWidget {
return Scaffold(
appBar: const HomeAppBar(),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(bottom: 80.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
@@ -32,7 +35,7 @@ class HomeScreen extends StatelessWidget {
)
],
),
));
)));
}
}
@@ -49,7 +52,7 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
actions: <Widget>[
IconButton(
icon: Icon(
Icons.file_download,
DeezerIcons.download,
semanticLabel: 'Download'.i18n,
),
onPressed: () {
@@ -59,7 +62,7 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
),
IconButton(
icon: Icon(
Icons.settings,
DeezerIcons.settings,
semanticLabel: 'Settings'.i18n,
),
onPressed: () {
@@ -88,7 +91,7 @@ class FreezerTitle extends StatelessWidget {
children: <Widget>[
Image.asset('assets/icon.png', width: 64, height: 64),
const Text(
'ReFreezer',
'Deezer',
style: TextStyle(fontSize: 56, fontWeight: FontWeight.w900),
)
],

View File

@@ -6,6 +6,7 @@ import 'package:fluttericon/font_awesome5_icons.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:refreezer/fonts/deezer_icons.dart';
import '../api/cache.dart';
import '../api/deezer.dart';
@@ -37,7 +38,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
actions: <Widget>[
IconButton(
icon: Icon(
Icons.file_download,
DeezerIcons.download,
semanticLabel: 'Download'.i18n,
),
onPressed: () {
@@ -47,7 +48,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
),
IconButton(
icon: Icon(
Icons.settings,
DeezerIcons.settings,
semanticLabel: 'Settings'.i18n,
),
onPressed: () {
@@ -76,7 +77,7 @@ class LibraryScreen extends StatelessWidget {
ListTile(
title: Text('Downloads'.i18n),
leading:
const LeadingIcon(Icons.file_download, color: Colors.grey),
const LeadingIcon(DeezerIcons.download, color: Colors.grey),
subtitle: Text(
'Downloading is currently stopped, click here to resume.'
.i18n),
@@ -88,7 +89,8 @@ class LibraryScreen extends StatelessWidget {
),
ListTile(
title: Text('Shuffle'.i18n),
leading: const LeadingIcon(Icons.shuffle, color: Color(0xffeca704)),
leading: const LeadingIcon(DeezerIcons.shuffle,
color: Color(0xffeca704)),
onTap: () async {
List<Track> tracks = await deezerAPI.libraryShuffle();
GetIt.I<AudioPlayerHandler>().playFromTrackList(
@@ -112,7 +114,8 @@ class LibraryScreen extends StatelessWidget {
),
ListTile(
title: Text('Albums'.i18n),
leading: const LeadingIcon(Icons.album, color: Color(0xff4b2e7e)),
leading:
const LeadingIcon(DeezerIcons.album, color: Color(0xff4b2e7e)),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const LibraryAlbums()));
@@ -224,7 +227,7 @@ class LibraryScreen extends StatelessWidget {
),
ListTile(
title: Text('Offline albums'.i18n),
leading: const Icon(Icons.album),
leading: const Icon(DeezerIcons.album),
trailing: Text(data[1]),
),
ListTile(
@@ -715,7 +718,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
),
PopupMenuButton(
color: Theme.of(context).scaffoldBackgroundColor,
child: const Icon(Icons.sort, size: 32.0),
child: const Icon(DeezerIcons.sort, size: 32.0),
onSelected: (SortType s) async {
setState(() => _sort.type = s);
//Save to cache
@@ -962,7 +965,7 @@ class _LibraryArtistsState extends State<LibraryArtists> {
child: Text('Popularity'.i18n, style: popupMenuTextStyle()),
),
],
child: const Icon(Icons.sort, size: 32.0),
child: const Icon(DeezerIcons.sort, size: 32.0),
),
Container(width: 8.0),
],
@@ -1138,7 +1141,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
child: Text('Alphabetic'.i18n, style: popupMenuTextStyle()),
),
],
child: const Icon(Icons.sort, size: 32.0),
child: const Icon(DeezerIcons.sort, size: 32.0),
),
Container(width: 8.0),
],

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get_it/get_it.dart';
import 'package:numberpicker/numberpicker.dart';
import 'package:refreezer/fonts/deezer_icons.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
@@ -23,7 +24,8 @@ class MenuSheet {
Function navigateCallback;
// Use no-op callback if not provided
MenuSheet({Function? navigateCallback}) : navigateCallback = navigateCallback ?? (() {});
MenuSheet({Function? navigateCallback})
: navigateCallback = navigateCallback ?? (() {});
//===================
// DEFAULT
@@ -36,7 +38,10 @@ class MenuSheet {
builder: (BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight: (MediaQuery.of(context).orientation == Orientation.landscape) ? 220 : 350,
maxHeight:
(MediaQuery.of(context).orientation == Orientation.landscape)
? 220
: 350,
),
child: SingleChildScrollView(
child: Column(children: options),
@@ -54,78 +59,94 @@ class MenuSheet {
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 16.0,
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
border: Border.all(color: Colors.transparent),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Semantics(
label: 'Album art'.i18n,
image: true,
child: CachedImage(
url: track.albumArt?.full ?? '',
height: 128,
width: 128,
),
Container(
height: 16.0,
),
SizedBox(
width: 240.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
track.title ?? '',
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold),
Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Semantics(
label: 'Album art'.i18n,
image: true,
child: CachedImage(
url: track.albumArt?.full ?? '',
height: 128,
width: 128,
circular: true,
),
Text(
track.artistString ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: const TextStyle(fontSize: 20.0),
),
Container(
height: 8,
),
SizedBox(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
track.title ?? '',
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold),
),
Text(
track.artistString ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: const TextStyle(fontSize: 14.0),
),
Container(
height: 8.0,
),
Text(
track.album?.title ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Text(track.durationString ?? '')
],
),
Container(
height: 8.0,
),
Text(
track.album?.title ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Text(track.durationString ?? '')
],
),
),
],
),
Container(
height: 16.0,
),
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: (MediaQuery.of(context).orientation ==
Orientation.landscape)
? 200
: 350,
),
child: SingleChildScrollView(
child: Column(children: options),
),
)
],
),
Container(
height: 16.0,
),
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: (MediaQuery.of(context).orientation == Orientation.landscape) ? 200 : 350,
),
child: SingleChildScrollView(
child: Column(children: options),
),
)
],
);
));
});
}
//Default track options
void defaultTrackMenu(Track track,
{required BuildContext context, List<Widget> options = const [], Function? onRemove}) {
{required BuildContext context,
List<Widget> options = const [],
Function? onRemove}) {
showWithTrack(context, track, [
addToQueueNext(track, context),
addToQueue(track, context),
@@ -138,7 +159,8 @@ class MenuSheet {
shareTile('track', track.id ?? ''),
playMix(track, context),
showAlbum(track.album!, context),
...List.generate(track.artists?.length ?? 0, (i) => showArtist(track.artists![i], context)),
...List.generate(track.artists?.length ?? 0,
(i) => showArtist(track.artists![i], context)),
...options
]);
}
@@ -152,7 +174,8 @@ class MenuSheet {
leading: const Icon(Icons.playlist_play),
onTap: () async {
//-1 = next
await GetIt.I<AudioPlayerHandler>().insertQueueItem(-1, t.toMediaItem());
await GetIt.I<AudioPlayerHandler>()
.insertQueueItem(-1, t.toMediaItem());
if (context.mounted) _close(context);
});
@@ -166,7 +189,7 @@ class MenuSheet {
Widget addTrackFavorite(Track t, BuildContext context) => ListTile(
title: Text('Add track to favorites'.i18n),
leading: const Icon(Icons.favorite),
leading: const Icon(DeezerIcons.heart_fill),
onTap: () async {
await deezerAPI.addFavoriteTrack(t.id!);
//Make track offline, if favorites are offline
@@ -175,7 +198,9 @@ class MenuSheet {
downloadManager.addOfflinePlaylist(p);
}
Fluttertoast.showToast(
msg: 'Added to library'.i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT);
msg: 'Added to library'.i18n,
gravity: ToastGravity.BOTTOM,
toastLength: Toast.LENGTH_SHORT);
//Add to cache
cache.libraryTracks ??= [];
cache.libraryTracks?.add(t.id!);
@@ -185,9 +210,11 @@ class MenuSheet {
Widget downloadTrack(Track t, BuildContext context) => ListTile(
title: Text('Download'.i18n),
leading: const Icon(Icons.file_download),
leading: const Icon(DeezerIcons.download),
onTap: () async {
if (await downloadManager.addOfflineTrack(t, private: false, isSingleton: true) != false) {
if (await downloadManager.addOfflineTrack(t,
private: false, isSingleton: true) !=
false) {
showDownloadStartedToast();
}
if (context.mounted) _close(context);
@@ -221,9 +248,10 @@ class MenuSheet {
},
);
Widget removeFromPlaylist(Track t, Playlist p, BuildContext context) => ListTile(
Widget removeFromPlaylist(Track t, Playlist p, BuildContext context) =>
ListTile(
title: Text('Remove from playlist'.i18n),
leading: const Icon(Icons.delete),
leading: const Icon(DeezerIcons.trash),
onTap: () async {
await deezerAPI.removeFromPlaylist(t.id!, p.id!);
Fluttertoast.showToast(
@@ -235,9 +263,10 @@ class MenuSheet {
},
);
Widget removeFavoriteTrack(Track t, BuildContext context, {onUpdate}) => ListTile(
Widget removeFavoriteTrack(Track t, BuildContext context, {onUpdate}) =>
ListTile(
title: Text('Remove favorite'.i18n),
leading: const Icon(Icons.delete),
leading: const Icon(DeezerIcons.trash),
onTap: () async {
await deezerAPI.removeFavorite(t.id!);
//Check if favorites playlist is offline, update it
@@ -248,7 +277,9 @@ class MenuSheet {
//Remove from cache
cache.libraryTracks?.removeWhere((i) => i == t.id);
Fluttertoast.showToast(
msg: 'Track removed from library'.i18n, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM);
msg: 'Track removed from library'.i18n,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM);
if (onUpdate != null) onUpdate();
if (context.mounted) _close(context);
},
@@ -264,7 +295,8 @@ class MenuSheet {
leading: const Icon(Icons.recent_actors),
onTap: () {
if (context.mounted) _close(context);
customNavigatorKey.currentState?.push(MaterialPageRoute(builder: (context) => ArtistDetails(a)));
customNavigatorKey.currentState
?.push(MaterialPageRoute(builder: (context) => ArtistDetails(a)));
navigateCallback();
},
@@ -279,7 +311,8 @@ class MenuSheet {
leading: const Icon(Icons.album),
onTap: () {
if (context.mounted) _close(context);
customNavigatorKey.currentState?.push(MaterialPageRoute(builder: (context) => AlbumDetails(a)));
customNavigatorKey.currentState
?.push(MaterialPageRoute(builder: (context) => AlbumDetails(a)));
navigateCallback();
},
@@ -323,7 +356,9 @@ class MenuSheet {
//Default album options
void defaultAlbumMenu(Album album,
{required BuildContext context, List<Widget> options = const [], Function? onRemove}) {
{required BuildContext context,
List<Widget> options = const [],
Function? onRemove}) {
show(context, [
(album.library != null && onRemove != null)
? removeAlbum(album, context, onRemove: onRemove)
@@ -341,7 +376,7 @@ class MenuSheet {
Widget downloadAlbum(Album a, BuildContext context) => ListTile(
title: Text('Download'.i18n),
leading: const Icon(Icons.file_download),
leading: const Icon(DeezerIcons.download),
onTap: () async {
if (context.mounted) _close(context);
if (await downloadManager.addOfflineAlbum(a, private: false) != false) {
@@ -365,15 +400,18 @@ class MenuSheet {
leading: const Icon(Icons.library_music),
onTap: () async {
await deezerAPI.addFavoriteAlbum(a.id!);
Fluttertoast.showToast(msg: 'Added to library'.i18n, gravity: ToastGravity.BOTTOM);
Fluttertoast.showToast(
msg: 'Added to library'.i18n, gravity: ToastGravity.BOTTOM);
if (context.mounted) _close(context);
},
);
//Remove album from favorites
Widget removeAlbum(Album a, BuildContext context, {required Function onRemove}) => ListTile(
Widget removeAlbum(Album a, BuildContext context,
{required Function onRemove}) =>
ListTile(
title: Text('Remove album'.i18n),
leading: const Icon(Icons.delete),
leading: const Icon(DeezerIcons.trash),
onTap: () async {
await deezerAPI.removeAlbum(a.id!);
await downloadManager.removeOfflineAlbum(a.id!);
@@ -392,9 +430,13 @@ class MenuSheet {
//===================
void defaultArtistMenu(Artist artist,
{required BuildContext context, List<Widget> options = const [], Function? onRemove}) {
{required BuildContext context,
List<Widget> options = const [],
Function? onRemove}) {
show(context, [
(artist.library != null) ? removeArtist(artist, context, onRemove: onRemove) : favoriteArtist(artist, context),
(artist.library != null)
? removeArtist(artist, context, onRemove: onRemove)
: favoriteArtist(artist, context),
shareTile('artist', artist.id!),
...options
]);
@@ -404,13 +446,16 @@ class MenuSheet {
// ARTIST OPTIONS
//===================
Widget removeArtist(Artist a, BuildContext context, {Function? onRemove}) => ListTile(
Widget removeArtist(Artist a, BuildContext context, {Function? onRemove}) =>
ListTile(
title: Text('Remove from favorites'.i18n),
leading: const Icon(Icons.delete),
leading: const Icon(DeezerIcons.trash),
onTap: () async {
await deezerAPI.removeArtist(a.id!);
Fluttertoast.showToast(
msg: 'Artist removed from library'.i18n, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM);
msg: 'Artist removed from library'.i18n,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM);
if (onRemove != null) onRemove();
if (context.mounted) _close(context);
},
@@ -418,11 +463,13 @@ class MenuSheet {
Widget favoriteArtist(Artist a, BuildContext context) => ListTile(
title: Text('Add to favorites'.i18n),
leading: const Icon(Icons.favorite),
leading: const Icon(DeezerIcons.heart_fill),
onTap: () async {
await deezerAPI.addFavoriteArtist(a.id!);
Fluttertoast.showToast(
msg: 'Added to library'.i18n, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM);
msg: 'Added to library'.i18n,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM);
if (context.mounted) _close(context);
},
);
@@ -432,7 +479,10 @@ class MenuSheet {
//===================
void defaultPlaylistMenu(Playlist playlist,
{required BuildContext context, List<Widget> options = const [], Function? onRemove, Function? onUpdate}) {
{required BuildContext context,
List<Widget> options = const [],
Function? onRemove,
Function? onUpdate}) {
show(context, [
(playlist.library != null)
? removePlaylistLibrary(playlist, context, onRemove: onRemove)
@@ -440,7 +490,8 @@ class MenuSheet {
addPlaylistOffline(playlist, context),
downloadPlaylist(playlist, context),
shareTile('playlist', playlist.id!),
if (playlist.user?.id == deezerAPI.userId) editPlaylist(playlist, context: context, onUpdate: onUpdate),
if (playlist.user?.id == deezerAPI.userId)
editPlaylist(playlist, context: context, onUpdate: onUpdate),
...options
]);
}
@@ -449,9 +500,11 @@ class MenuSheet {
// PLAYLIST OPTIONS
//===================
Widget removePlaylistLibrary(Playlist p, BuildContext context, {Function? onRemove}) => ListTile(
Widget removePlaylistLibrary(Playlist p, BuildContext context,
{Function? onRemove}) =>
ListTile(
title: Text('Remove from library'.i18n),
leading: const Icon(Icons.delete),
leading: const Icon(DeezerIcons.trash),
onTap: () async {
if (p.user?.id?.trim() == deezerAPI.userId) {
//Delete playlist if own
@@ -468,10 +521,12 @@ class MenuSheet {
Widget addPlaylistLibrary(Playlist p, BuildContext context) => ListTile(
title: Text('Add playlist to library'.i18n),
leading: const Icon(Icons.favorite),
leading: const Icon(DeezerIcons.heart_fill),
onTap: () async {
await deezerAPI.addPlaylist(p.id!);
Fluttertoast.showToast(msg: 'Added playlist to library'.i18n, gravity: ToastGravity.BOTTOM);
Fluttertoast.showToast(
msg: 'Added playlist to library'.i18n,
gravity: ToastGravity.BOTTOM);
if (context.mounted) _close(context);
},
);
@@ -490,20 +545,25 @@ class MenuSheet {
Widget downloadPlaylist(Playlist p, BuildContext context) => ListTile(
title: Text('Download playlist'.i18n),
leading: const Icon(Icons.file_download),
leading: const Icon(DeezerIcons.download),
onTap: () async {
if (context.mounted) _close(context);
if (await downloadManager.addOfflinePlaylist(p, private: false) != false) {
if (await downloadManager.addOfflinePlaylist(p, private: false) !=
false) {
showDownloadStartedToast();
}
},
);
Widget editPlaylist(Playlist p, {required BuildContext context, Function? onUpdate}) => ListTile(
Widget editPlaylist(Playlist p,
{required BuildContext context, Function? onUpdate}) =>
ListTile(
title: Text('Edit playlist'.i18n),
leading: const Icon(Icons.edit),
leading: const Icon(DeezerIcons.pen),
onTap: () async {
await showDialog(context: context, builder: (context) => CreatePlaylistDialog(playlist: p));
await showDialog(
context: context,
builder: (context) => CreatePlaylistDialog(playlist: p));
if (context.mounted) _close(context);
if (onUpdate != null) onUpdate();
},
@@ -513,13 +573,19 @@ class MenuSheet {
// SHOW/EPISODE
//===================
defaultShowEpisodeMenu(Show s, ShowEpisode e, {required BuildContext context, List<Widget> options = const []}) {
show(context, [shareTile('episode', e.id!), shareShow(s.id!), downloadExternalEpisode(e), ...options]);
defaultShowEpisodeMenu(Show s, ShowEpisode e,
{required BuildContext context, List<Widget> options = const []}) {
show(context, [
shareTile('episode', e.id!),
shareShow(s.id!),
downloadExternalEpisode(e),
...options
]);
}
Widget shareShow(String id) => ListTile(
title: Text('Share show'.i18n),
leading: const Icon(Icons.share),
leading: const Icon(DeezerIcons.share_android),
onTap: () async {
Share.share('https://deezer.com/show/$id');
},
@@ -528,7 +594,7 @@ class MenuSheet {
//Open direct download link in browser
Widget downloadExternalEpisode(ShowEpisode e) => ListTile(
title: Text('Download externally'.i18n),
leading: const Icon(Icons.file_download),
leading: const Icon(DeezerIcons.download),
onTap: () async {
if (e.url != null) await launchUrlString(e.url!);
},
@@ -539,7 +605,10 @@ class MenuSheet {
//===================
showDownloadStartedToast() {
Fluttertoast.showToast(msg: 'Downloads added!'.i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT);
Fluttertoast.showToast(
msg: 'Downloads added!'.i18n,
gravity: ToastGravity.BOTTOM,
toastLength: Toast.LENGTH_SHORT);
}
//Create playlist
@@ -572,20 +641,24 @@ class MenuSheet {
);
Widget wakelock(BuildContext context) => ListTile(
title: Text(cache.wakelock ? 'Allow screen to turn off'.i18n : 'Keep the screen on'.i18n),
title: Text(cache.wakelock
? 'Allow screen to turn off'.i18n
: 'Keep the screen on'.i18n),
leading: const Icon(Icons.screen_lock_portrait),
onTap: () async {
_close(context);
//Enable
if (!cache.wakelock) {
WakelockPlus.enable();
Fluttertoast.showToast(msg: 'Wakelock enabled!'.i18n, gravity: ToastGravity.BOTTOM);
Fluttertoast.showToast(
msg: 'Wakelock enabled!'.i18n, gravity: ToastGravity.BOTTOM);
cache.wakelock = true;
return;
}
//Disable
WakelockPlus.disable();
Fluttertoast.showToast(msg: 'Wakelock disabled!'.i18n, gravity: ToastGravity.BOTTOM);
Fluttertoast.showToast(
msg: 'Wakelock disabled!'.i18n, gravity: ToastGravity.BOTTOM);
cache.wakelock = false;
},
);
@@ -622,7 +695,11 @@ class _SleepTimerDialogState extends State<SleepTimerDialog> {
mainAxisSize: MainAxisSize.min,
children: [
Text('Hours:'.i18n),
NumberPicker(value: hours, minValue: 0, maxValue: 69, onChanged: (v) => setState(() => hours = v)),
NumberPicker(
value: hours,
minValue: 0,
maxValue: 69,
onChanged: (v) => setState(() => hours = v)),
],
),
Column(
@@ -630,7 +707,10 @@ class _SleepTimerDialogState extends State<SleepTimerDialog> {
children: [
Text('Minutes:'.i18n),
NumberPicker(
value: minutes, minValue: 0, maxValue: 60, onChanged: (v) => setState(() => minutes = v)),
value: minutes,
minValue: 0,
maxValue: 60,
onChanged: (v) => setState(() => minutes = v)),
],
),
],
@@ -666,7 +746,8 @@ class _SleepTimerDialogState extends State<SleepTimerDialog> {
Duration duration = Duration(hours: hours, minutes: minutes);
cache.sleepTimer?.cancel();
//Create timer
cache.sleepTimer = Stream.fromFuture(Future.delayed(duration)).listen((_) {
cache.sleepTimer =
Stream.fromFuture(Future.delayed(duration)).listen((_) {
GetIt.I<AudioPlayerHandler>().pause();
cache.sleepTimer?.cancel();
cache.sleepTimerTime = null;
@@ -837,16 +918,20 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
onPressed: () async {
if (edit) {
//Update
await deezerAPI.updatePlaylist(
widget.playlist!.id!, _titleController!.value.text, _descController!.value.text,
await deezerAPI.updatePlaylist(widget.playlist!.id!,
_titleController!.value.text, _descController!.value.text,
status: _playlistType);
Fluttertoast.showToast(msg: 'Playlist updated!'.i18n, gravity: ToastGravity.BOTTOM);
Fluttertoast.showToast(
msg: 'Playlist updated!'.i18n, gravity: ToastGravity.BOTTOM);
} else {
List<String> tracks = [];
tracks = widget.tracks?.map<String>((t) => t.id!).toList() ?? [];
await deezerAPI.createPlaylist(_title,
status: _playlistType, description: _description, trackIds: tracks);
Fluttertoast.showToast(msg: 'Playlist created!'.i18n, gravity: ToastGravity.BOTTOM);
status: _playlistType,
description: _description,
trackIds: tracks);
Fluttertoast.showToast(
msg: 'Playlist created!'.i18n, gravity: ToastGravity.BOTTOM);
}
if (context.mounted) Navigator.of(context).pop();
},

View File

@@ -1,7 +1,12 @@
import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:refreezer/fonts/deezer_icons.dart';
import '../service/audio_service.dart';
import '../settings.dart';
@@ -10,6 +15,8 @@ import '../ui/router.dart';
import 'cached_image.dart';
import 'player_screen.dart';
late Function updateColor;
class PlayerBar extends StatefulWidget {
const PlayerBar({super.key});
@@ -18,110 +25,184 @@ class PlayerBar extends StatefulWidget {
}
class _PlayerBarState extends State<PlayerBar> {
final double iconSize = 28;
AudioPlayerHandler audioHandler = GetIt.I<AudioPlayerHandler>();
Color? _bgColor;
StreamSubscription? _mediaItemSub;
final double iconSize = 20;
//bool _gestureRegistered = false;
//Recover dominant color
Future _updateColor() async {
if (audioHandler.mediaItem.value == null) return;
PaletteGenerator palette = await PaletteGenerator.fromImageProvider(
CachedNetworkImageProvider(
audioHandler.mediaItem.value?.extras?['thumb'] ??
audioHandler.mediaItem.value?.artUri));
setState(() => _bgColor = palette.dominantColor!.color.withOpacity(1));
}
@override
void initState() {
_updateColor;
_mediaItemSub = audioHandler.mediaItem.listen((event) {
_updateColor();
});
updateColor = _updateColor;
super.initState();
}
@override
void dispose() {
_mediaItemSub?.cancel();
super.dispose();
}
double get _progress {
if (GetIt.I<AudioPlayerHandler>().playbackState.value.processingState == AudioProcessingState.idle) return 0.0;
if (GetIt.I<AudioPlayerHandler>().playbackState.value.processingState ==
AudioProcessingState.idle) return 0.0;
if (GetIt.I<AudioPlayerHandler>().mediaItem.value == null) return 0.0;
if (GetIt.I<AudioPlayerHandler>().mediaItem.value!.duration!.inSeconds == 0) return 0.0; //Division by 0
return GetIt.I<AudioPlayerHandler>().playbackState.value.position.inSeconds /
if (GetIt.I<AudioPlayerHandler>().mediaItem.value!.duration!.inSeconds == 0)
return 0.0; //Division by 0
return GetIt.I<AudioPlayerHandler>()
.playbackState
.value
.position
.inSeconds /
GetIt.I<AudioPlayerHandler>().mediaItem.value!.duration!.inSeconds;
}
@override
Widget build(BuildContext context) {
scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor;
var focusNode = FocusNode();
return GestureDetector(
key: UniqueKey(),
onHorizontalDragEnd: (DragEndDetails details) async {
if ((details.primaryVelocity ?? 0) < -100) {
// Swiped left
await GetIt.I<AudioPlayerHandler>().skipToPrevious();
} else if ((details.primaryVelocity ?? 0) > 100) {
// Swiped right
await GetIt.I<AudioPlayerHandler>().skipToNext();
}
},
onVerticalDragEnd: (DragEndDetails details) async {
if ((details.primaryVelocity ?? 0) < -100) {
// Swiped up
Navigator.of(context).push(SlideBottomRoute(widget: const PlayerScreen()));
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.scaffoldBackgroundColor,
));
} /*else if ((details.primaryVelocity ?? 0) > 100) {
return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: scaffoldBackgroundColor,
border: Border.all(
color: _bgColor != null
? _bgColor!.withOpacity(0.7)
: Theme.of(context).scaffoldBackgroundColor),
borderRadius: BorderRadius.circular(15)),
child: GestureDetector(
key: UniqueKey(),
onHorizontalDragEnd: (DragEndDetails details) async {
if ((details.primaryVelocity ?? 0) < -100) {
// Swiped left
await GetIt.I<AudioPlayerHandler>().skipToNext();
updateColor();
} else if ((details.primaryVelocity ?? 0) > 100) {
// Swiped right
await GetIt.I<AudioPlayerHandler>().skipToPrevious();
updateColor();
}
},
onVerticalDragEnd: (DragEndDetails details) async {
if ((details.primaryVelocity ?? 0) < -100) {
// Swiped up
Navigator.of(context)
.push(SlideBottomRoute(widget: const PlayerScreen()));
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: _bgColor,
));
} /*else if ((details.primaryVelocity ?? 0) > 100) {
// Swiped down => no action
}*/
},
child: StreamBuilder(
stream: Stream.periodic(const Duration(milliseconds: 250)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (GetIt.I<AudioPlayerHandler>().mediaItem.value == null) {
return const SizedBox(
width: 0,
height: 0,
);
}
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
// For Android TV: indicate focus by grey
color: focusNode.hasFocus ? Colors.black26 : Theme.of(context).bottomAppBarTheme.color,
child: ListTile(
dense: true,
focusNode: focusNode,
contentPadding: const EdgeInsets.symmetric(horizontal: 8.0),
onTap: () {
Navigator.of(context).push(SlideBottomRoute(widget: const PlayerScreen()));
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.scaffoldBackgroundColor,
));
},
leading: CachedImage(
width: 50,
height: 50,
url: GetIt.I<AudioPlayerHandler>().mediaItem.value?.extras?['thumb'] ??
GetIt.I<AudioPlayerHandler>().mediaItem.value?.artUri,
),
title: Text(
GetIt.I<AudioPlayerHandler>().mediaItem.value?.displayTitle ?? '',
overflow: TextOverflow.clip,
maxLines: 1,
),
subtitle: Text(
GetIt.I<AudioPlayerHandler>().mediaItem.value?.displaySubtitle ?? '',
overflow: TextOverflow.clip,
maxLines: 1,
),
trailing: IconTheme(
data: IconThemeData(color: settings.isDark ? Colors.white : Colors.grey[600]),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PrevNextButton(
iconSize,
prev: true,
hidePrev: true,
),
PlayPauseButton(iconSize),
PrevNextButton(iconSize)
],
),
)),
),
SizedBox(
height: 3.0,
child: LinearProgressIndicator(
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),
color: Theme.of(context).primaryColor,
value: _progress,
updateColor();
},
child: StreamBuilder(
stream: Stream.periodic(const Duration(milliseconds: 250)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (GetIt.I<AudioPlayerHandler>().mediaItem.value == null) {
return const SizedBox(
width: 0,
height: 0,
);
}
return Container(
decoration: BoxDecoration(
color: _bgColor?.withOpacity(0.7),
),
)
],
);
}),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
dense: true,
focusNode: focusNode,
contentPadding:
const EdgeInsets.symmetric(horizontal: 8.0),
onTap: () {
Navigator.of(context).push(
SlideBottomRoute(widget: const PlayerScreen()));
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor: Settings.deezerBottom,
));
},
leading: CachedImage(
width: 50,
height: 50,
url: GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.extras?['thumb'] ??
GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.artUri,
),
title: Text(
GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.displayTitle ??
'',
overflow: TextOverflow.clip,
maxLines: 1,
),
subtitle: Text(
GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.displaySubtitle ??
'',
overflow: TextOverflow.clip,
maxLines: 1,
),
trailing: IconTheme(
data: IconThemeData(
color: settings.isDark
? Colors.white
: Colors.grey[600]),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PrevNextButton(
iconSize,
prev: true,
hidePrev: true,
),
PlayPauseButton(iconSize),
PrevNextButton(iconSize)
],
),
)),
SizedBox(
height: 3.0,
child: LinearProgressIndicator(
backgroundColor: _bgColor!.withOpacity(0.1),
color: _bgColor,
value: _progress,
),
)
],
));
}),
),
);
}
}
@@ -131,7 +212,8 @@ class PrevNextButton extends StatelessWidget {
final bool prev;
final bool hidePrev;
const PrevNextButton(this.size, {super.key, this.prev = false, this.hidePrev = false});
const PrevNextButton(this.size,
{super.key, this.prev = false, this.hidePrev = false});
@override
Widget build(BuildContext context) {
@@ -143,7 +225,7 @@ class PrevNextButton extends StatelessWidget {
if (!(queueState?.hasNext ?? false)) {
return IconButton(
icon: Icon(
Icons.skip_next,
DeezerIcons.skip_next_fill,
semanticLabel: 'Play next'.i18n,
),
iconSize: size,
@@ -152,7 +234,7 @@ class PrevNextButton extends StatelessWidget {
}
return IconButton(
icon: Icon(
Icons.skip_next,
DeezerIcons.skip_next_fill,
semanticLabel: 'Play next'.i18n,
),
iconSize: size,
@@ -169,7 +251,7 @@ class PrevNextButton extends StatelessWidget {
}
return IconButton(
icon: Icon(
Icons.skip_previous,
DeezerIcons.skip_back,
semanticLabel: 'Play previous'.i18n,
),
iconSize: size,
@@ -178,7 +260,7 @@ class PrevNextButton extends StatelessWidget {
}
return IconButton(
icon: Icon(
Icons.skip_previous,
DeezerIcons.skip_back,
semanticLabel: 'Play previous'.i18n,
),
iconSize: size,
@@ -199,14 +281,17 @@ class PlayPauseButton extends StatefulWidget {
_PlayPauseButtonState createState() => _PlayPauseButtonState();
}
class _PlayPauseButtonState extends State<PlayPauseButton> with SingleTickerProviderStateMixin {
class _PlayPauseButtonState extends State<PlayPauseButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
_animation = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
_controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200));
_animation = Tween<double>(begin: 0, end: 1)
.animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
super.initState();
}
@@ -227,7 +312,9 @@ class _PlayPauseButtonState extends State<PlayPauseButton> with SingleTickerProv
// Animated icon by pato05
// Morph from pause to play or from play to pause
if (playing || processingState == AudioProcessingState.ready || processingState == AudioProcessingState.idle) {
if (playing ||
processingState == AudioProcessingState.ready ||
processingState == AudioProcessingState.idle) {
if (playing) {
_controller.forward();
} else {
@@ -236,14 +323,25 @@ class _PlayPauseButtonState extends State<PlayPauseButton> with SingleTickerProv
return IconButton(
splashRadius: widget.size,
icon: AnimatedIcon(
icon: AnimatedIcons.play_pause,
progress: _animation,
semanticLabel: playing ? 'Pause'.i18n : 'Play'.i18n,
),
icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, anim) => RotationTransition(
turns: child.key == ValueKey('icon1')
? Tween<double>(begin: 1, end: 0.75).animate(anim)
: Tween<double>(begin: 0.75, end: 1).animate(anim),
child: FadeTransition(opacity: anim, child: child),
),
child: !playing
? Icon(DeezerIcons.play_fill_small,
key: const ValueKey('Play'))
: Icon(
DeezerIcons.pause_fill_small,
key: const ValueKey('Pause'),
)),
iconSize: widget.size,
onPressed:
playing ? () => GetIt.I<AudioPlayerHandler>().pause() : () => GetIt.I<AudioPlayerHandler>().play());
onPressed: playing
? () => GetIt.I<AudioPlayerHandler>().pause()
: () => GetIt.I<AudioPlayerHandler>().play());
}
switch (processingState) {
@@ -256,7 +354,8 @@ class _PlayPauseButtonState extends State<PlayPauseButton> with SingleTickerProv
child: Center(
child: Transform.scale(
scale: 0.85, // Adjust the scale to 75% of the original size
child: const CircularProgressIndicator(),
child: CircularProgressIndicator(
color: Theme.of(context).primaryColor),
),
),
);

View File

@@ -5,6 +5,8 @@ import 'dart:ui';
import 'package:async/async.dart';
import 'package:audio_service/audio_service.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:refreezer/fonts/deezer_icons.dart';
import 'package:refreezer/utils/navigator_keys.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -13,15 +15,14 @@ import 'package:get_it/get_it.dart';
import 'package:just_audio/just_audio.dart';
import 'package:marquee/marquee.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:refreezer/utils/navigator_keys.dart';
import 'package:rxdart/rxdart.dart';
import 'package:share_plus/share_plus.dart';
import 'package:visibility_detector/visibility_detector.dart';
import '../api/cache.dart';
import '../api/deezer.dart';
import '../api/definitions.dart';
import '../api/download.dart';
import '../fonts/refreezer_icons.dart';
import '../service/audio_service.dart';
import '../settings.dart';
import '../translations.i18n.dart';
@@ -62,21 +63,25 @@ class _PlayerScreenState extends State<PlayerScreen> {
//BG Image
if (settings.blurPlayerBackground) {
setState(() {
_blurImage =
NetworkImage(audioHandler.mediaItem.value?.extras?['thumb'] ?? audioHandler.mediaItem.value?.artUri);
_blurImage = NetworkImage(
audioHandler.mediaItem.value?.extras?['thumb'] ??
audioHandler.mediaItem.value?.artUri);
});
}
//Run in isolate
PaletteGenerator palette = await PaletteGenerator.fromImageProvider(CachedNetworkImageProvider(
audioHandler.mediaItem.value?.extras?['thumb'] ?? audioHandler.mediaItem.value?.artUri));
PaletteGenerator palette = await PaletteGenerator.fromImageProvider(
CachedNetworkImageProvider(
audioHandler.mediaItem.value?.extras?['thumb'] ??
audioHandler.mediaItem.value?.artUri));
//Update notification
if (settings.blurPlayerBackground) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: palette.dominantColor!.color.withOpacity(0.25),
systemNavigationBarColor:
Color.alphaBlend(palette.dominantColor!.color.withOpacity(0.25), scaffoldBackgroundColor)));
systemNavigationBarColor: Color.alphaBlend(
palette.dominantColor!.color.withOpacity(0.25),
scaffoldBackgroundColor)));
}
//Color gradient
@@ -85,10 +90,16 @@ class _PlayerScreenState extends State<PlayerScreen> {
statusBarColor: palette.dominantColor!.color.withOpacity(0.7),
));
setState(() => _bgGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [palette.dominantColor!.color.withOpacity(0.7), const Color.fromARGB(0, 0, 0, 0)],
stops: const [0.0, 0.6]));
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
palette.dominantColor!.color.withOpacity(0.7),
Settings.deezerBg
],
stops: const [
0.0,
0.6
]));
}
}
@@ -108,7 +119,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
_mediaItemSub?.cancel();
//Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.bottomAppBarTheme.color, statusBarColor: Colors.transparent));
systemNavigationBarColor: settings.themeData.bottomAppBarTheme.color,
statusBarColor: Colors.transparent));
super.dispose();
}
@@ -120,7 +132,9 @@ class _PlayerScreenState extends State<PlayerScreen> {
return Scaffold(
body: SafeArea(
child: Container(
decoration: BoxDecoration(gradient: settings.blurPlayerBackground ? null : _bgGradient),
decoration: BoxDecoration(
gradient:
settings.blurPlayerBackground ? null : _bgGradient),
child: Stack(
children: [
if (settings.blurPlayerBackground)
@@ -130,7 +144,9 @@ class _PlayerScreenState extends State<PlayerScreen> {
image: DecorationImage(
image: _blurImage ?? const NetworkImage(''),
fit: BoxFit.fill,
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.25), BlendMode.dstATop))),
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.25),
BlendMode.dstATop))),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(color: Colors.transparent),
@@ -138,7 +154,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
),
),
StreamBuilder(
stream: StreamZip([audioHandler.playbackState, audioHandler.mediaItem]),
stream: StreamZip(
[audioHandler.playbackState, audioHandler.mediaItem]),
builder: (BuildContext context, AsyncSnapshot snapshot) {
//When disconnected
if (audioHandler.mediaItem.value == null) {
@@ -202,38 +219,50 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(4, 8, 4, 0),
child: PlayerScreenTopRow(
textSize: ScreenUtil().setSp(28),
iconSize: ScreenUtil().setSp(38),
textWidth: ScreenUtil().setWidth(150),
short: false)),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
height: ScreenUtil().setSp(50),
child: GetIt.I<AudioPlayerHandler>().mediaItem.value!.displayTitle!.length >= 22
child: GetIt.I<AudioPlayerHandler>()
.mediaItem
.value!
.displayTitle!
.length >=
22
? Marquee(
text: GetIt.I<AudioPlayerHandler>().mediaItem.value!.displayTitle!,
style: TextStyle(fontSize: ScreenUtil().setSp(40), fontWeight: FontWeight.bold),
text: GetIt.I<AudioPlayerHandler>()
.mediaItem
.value!
.displayTitle!,
style: TextStyle(
fontSize: ScreenUtil().setSp(30),
fontWeight: FontWeight.bold),
blankSpace: 32.0,
startPadding: 10.0,
accelerationDuration: const Duration(seconds: 1),
pauseAfterRound: const Duration(seconds: 2),
)
: Text(
GetIt.I<AudioPlayerHandler>().mediaItem.value!.displayTitle!,
GetIt.I<AudioPlayerHandler>()
.mediaItem
.value!
.displayTitle!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: ScreenUtil().setSp(40), fontWeight: FontWeight.bold),
style: TextStyle(
fontSize: ScreenUtil().setSp(30),
fontWeight: FontWeight.bold),
)),
Container(
height: 4,
),
Text(
GetIt.I<AudioPlayerHandler>().mediaItem.value!.displaySubtitle ?? '',
GetIt.I<AudioPlayerHandler>()
.mediaItem
.value!
.displaySubtitle ??
'',
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.clip,
@@ -248,7 +277,7 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: const SeekBar(24.0),
),
PlaybackControls(ScreenUtil().setSp(60)),
PlaybackControls(ScreenUtil().setSp(40)),
Padding(
//padding: EdgeInsets.fromLTRB(4, 0, 4, 8),
padding: const EdgeInsets.symmetric(vertical: 8.0),
@@ -261,13 +290,16 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
LyricsIconButton(12, afterOnPressed: updateColor),
IconButton(
icon: Icon(
Icons.file_download,
DeezerIcons.download,
size: ScreenUtil().setWidth(12),
semanticLabel: 'Download'.i18n,
),
onPressed: () async {
Track t = Track.fromMediaItem(GetIt.I<AudioPlayerHandler>().mediaItem.value!);
if (await downloadManager.addOfflineTrack(t, private: false, isSingleton: true) != false) {
Track t = Track.fromMediaItem(
GetIt.I<AudioPlayerHandler>().mediaItem.value!);
if (await downloadManager.addOfflineTrack(t,
private: false, isSingleton: true) !=
false) {
Fluttertoast.showToast(
msg: 'Downloads added!'.i18n,
gravity: ToastGravity.BOTTOM,
@@ -298,6 +330,8 @@ class PlayerScreenVertical extends StatefulWidget {
}
class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
final GlobalKey iconButtonKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Column(
@@ -323,67 +357,130 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
),
),
Container(height: 4.0),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
height: ScreenUtil().setSp(26),
child: (GetIt.I<AudioPlayerHandler>().mediaItem.value?.displayTitle ?? '').length >= 26
? Marquee(
text: GetIt.I<AudioPlayerHandler>().mediaItem.value?.displayTitle ?? '',
style: TextStyle(fontSize: ScreenUtil().setSp(22), fontWeight: FontWeight.bold),
blankSpace: 32.0,
startPadding: 10.0,
accelerationDuration: const Duration(seconds: 1),
pauseAfterRound: const Duration(seconds: 2),
)
: Text(
GetIt.I<AudioPlayerHandler>().mediaItem.value?.displayTitle ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: ScreenUtil().setSp(22), fontWeight: FontWeight.bold),
)),
Container(
height: 4,
),
Text(
GetIt.I<AudioPlayerHandler>().mediaItem.value?.displaySubtitle ?? '',
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.clip,
style: TextStyle(
fontSize: ScreenUtil().setSp(16),
color: Theme.of(context).primaryColor,
ActionControls(24.0),
Container(
padding: EdgeInsets.fromLTRB(18, 0, 18, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SeekBar(8.0),
Container(
height: 8.0,
),
),
],
SizedBox(
height: ScreenUtil().setSp(18),
child: (GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.displayTitle ??
'')
.length >=
26
? Marquee(
text: GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.displayTitle ??
'',
style: TextStyle(
fontSize: ScreenUtil().setSp(16),
fontWeight: FontWeight.bold),
blankSpace: 32.0,
startPadding: 0,
accelerationDuration: const Duration(seconds: 1),
pauseAfterRound: const Duration(seconds: 2),
)
: Text(
GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.displayTitle ??
'',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: ScreenUtil().setSp(16),
fontWeight: FontWeight.bold),
)),
Container(
height: 4,
),
Text(
GetIt.I<AudioPlayerHandler>()
.mediaItem
.value
?.displaySubtitle ??
'',
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.clip,
style: TextStyle(
fontSize: ScreenUtil().setSp(12),
color: Theme.of(context).primaryColor,
),
),
],
),
),
const SeekBar(12.0),
PlaybackControls(ScreenUtil().setSp(36)),
PlaybackControls(ScreenUtil().setSp(25)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16.0),
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LyricsIconButton(20, afterOnPressed: updateColor),
LyricsIconButton(24, afterOnPressed: updateColor),
IconButton(
icon: Icon(
Icons.file_download,
size: ScreenUtil().setWidth(20),
DeezerIcons.download,
size: ScreenUtil().setWidth(24),
semanticLabel: 'Download'.i18n,
),
onPressed: () async {
Track t = Track.fromMediaItem(GetIt.I<AudioPlayerHandler>().mediaItem.value!);
if (await downloadManager.addOfflineTrack(t, private: false, isSingleton: true) != false) {
Track t = Track.fromMediaItem(
GetIt.I<AudioPlayerHandler>().mediaItem.value!);
if (await downloadManager.addOfflineTrack(t,
private: false, isSingleton: true) !=
false) {
Fluttertoast.showToast(
msg: 'Downloads added!'.i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT);
msg: 'Downloads added!'.i18n,
gravity: ToastGravity.BOTTOM,
toastLength: Toast.LENGTH_SHORT);
}
},
),
const QualityInfoWidget(),
RepeatButton(ScreenUtil().setWidth(20)),
const PlayerMenuButton()
RepeatButton(ScreenUtil().setWidth(24)),
IconButton(
key: iconButtonKey,
icon: Icon(
//Icons.menu,
DeezerIcons.queue,
semanticLabel: 'Queue'.i18n,
),
iconSize: ScreenUtil().setWidth(24),
onPressed: () async {
//Fix bottom buttons (Not needed anymore?)
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent));
// Calculate the center of the icon
final RenderBox buttonRenderBox =
iconButtonKey.currentContext!.findRenderObject()
as RenderBox;
final Offset buttonOffset = buttonRenderBox
.localToGlobal(buttonRenderBox.size.center(Offset.zero));
//Navigate
//await Navigator.of(context).push(MaterialPageRoute(builder: (context) => QueueScreen()));
await Navigator.of(context).push(CircularExpansionRoute(
widget: const QueueScreen(),
//centerAlignment: Alignment.topRight,
centerOffset: buttonOffset)); // Expand from icon
//Fix colors
updateColor();
},
),
],
),
)
@@ -407,7 +504,8 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
//Load data from native
void _load() async {
if (audioHandler.mediaItem.value == null) return;
Map? data = await DownloadManager.platform.invokeMethod('getStreamInfo', {'id': audioHandler.mediaItem.value!.id});
Map? data = await DownloadManager.platform.invokeMethod(
'getStreamInfo', {'id': audioHandler.mediaItem.value!.id});
//N/A
if (data == null) {
if (mounted) setState(() => value = '');
@@ -449,7 +547,8 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
return TextButton(
child: Text(value),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const QualitySettings()));
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const QualitySettings()));
},
);
}
@@ -475,26 +574,30 @@ class LyricsIconButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
Track track = Track.fromMediaItem(GetIt.I<AudioPlayerHandler>().mediaItem.value!);
Track track =
Track.fromMediaItem(GetIt.I<AudioPlayerHandler>().mediaItem.value!);
bool isEnabled = (track.lyrics?.id ?? '0') != '0';
return Opacity(
opacity: isEnabled ? 1.0 : 0.7, // Full opacity for enabled, reduced for disabled
opacity: isEnabled
? 1.0
: 0.7, // Full opacity for enabled, reduced for disabled
child: IconButton(
icon: Icon(
//Icons.lyrics,
ReFreezerIcons.lyrics_mic,
DeezerIcons.microphone,
size: ScreenUtil().setWidth(width),
semanticLabel: 'Lyrics'.i18n,
),
onPressed: isEnabled
? () async {
//Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent));
await Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => LyricsScreen(trackId: track.id!)));
await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => LyricsScreen(trackId: track.id!)));
if (afterOnPressed != null) {
afterOnPressed!();
@@ -519,16 +622,24 @@ class PlayerMenuButton extends StatelessWidget {
semanticLabel: 'Options'.i18n,
),
onPressed: () {
Track t = Track.fromMediaItem(GetIt.I<AudioPlayerHandler>().mediaItem.value!);
Track t =
Track.fromMediaItem(GetIt.I<AudioPlayerHandler>().mediaItem.value!);
MenuSheet m = MenuSheet(navigateCallback: () {
Navigator.of(context).pop();
});
if (GetIt.I<AudioPlayerHandler>().mediaItem.value!.extras?['show'] == null) {
m.defaultTrackMenu(t, context: context, options: [m.sleepTimer(context), m.wakelock(context)]);
if (GetIt.I<AudioPlayerHandler>().mediaItem.value!.extras?['show'] ==
null) {
m.defaultTrackMenu(t,
context: context,
options: [m.sleepTimer(context), m.wakelock(context)]);
} else {
m.defaultShowEpisodeMenu(
Show.fromJson(jsonDecode(GetIt.I<AudioPlayerHandler>().mediaItem.value!.extras?['show'])),
ShowEpisode.fromMediaItem(GetIt.I<AudioPlayerHandler>().mediaItem.value!),
Show.fromJson(jsonDecode(GetIt.I<AudioPlayerHandler>()
.mediaItem
.value!
.extras?['show'])),
ShowEpisode.fromMediaItem(
GetIt.I<AudioPlayerHandler>().mediaItem.value!),
context: context,
options: [m.sleepTimer(context), m.wakelock(context)]);
}
@@ -550,20 +661,20 @@ class _RepeatButtonState extends State<RepeatButton> {
switch (GetIt.I<AudioPlayerHandler>().getLoopMode()) {
case LoopMode.off:
return Icon(
Icons.repeat,
DeezerIcons.repeat,
size: widget.iconSize,
semanticLabel: 'Repeat off'.i18n,
);
case LoopMode.all:
return Icon(
Icons.repeat,
DeezerIcons.repeat,
color: Theme.of(context).primaryColor,
size: widget.iconSize,
semanticLabel: 'Repeat'.i18n,
);
case LoopMode.one:
return Icon(
Icons.repeat_one,
DeezerIcons.repeat_one,
color: Theme.of(context).primaryColor,
size: widget.iconSize,
semanticLabel: 'Repeat one'.i18n,
@@ -583,6 +694,123 @@ class _RepeatButtonState extends State<RepeatButton> {
}
}
class ActionControls extends StatefulWidget {
final double iconSize;
const ActionControls(this.iconSize, {super.key});
@override
_ActionControls createState() => _ActionControls();
}
class _ActionControls extends State<ActionControls> {
AudioPlayerHandler audioHandler = GetIt.I<AudioPlayerHandler>();
Icon get libraryIcon {
if (cache.checkTrackFavorite(
Track.fromMediaItem(audioHandler.mediaItem.value!))) {
return Icon(
DeezerIcons.heart_fill,
color: settings.primaryColor,
size: widget.iconSize,
semanticLabel: 'Unlove'.i18n,
);
}
return Icon(
DeezerIcons.heart,
size: widget.iconSize,
semanticLabel: 'Love'.i18n,
);
}
@override
Widget build(BuildContext context) {
String? id = Track.fromMediaItem(audioHandler.mediaItem.value!).id;
return Container(
padding: EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
onPressed: () async {
Share.share('https://deezer.com/track/$id');
},
icon: Icon(
DeezerIcons.share_android,
size: widget.iconSize,
semanticLabel: 'Share'.i18n,
)),
Container(
margin: EdgeInsets.fromLTRB(12, 0, 12, 0),
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
border: Border.all(color: Settings.secondaryText, width: 1),
borderRadius: BorderRadius.circular(100),
),
alignment: Alignment.center,
child: IconButton(
icon: Icon(
DeezerIcons.more_vert,
size: widget.iconSize * 1.25,
semanticLabel: 'Options'.i18n,
),
onPressed: () {
Track t = Track.fromMediaItem(
GetIt.I<AudioPlayerHandler>().mediaItem.value!);
MenuSheet m = MenuSheet(navigateCallback: () {
Navigator.of(context).pop();
});
if (GetIt.I<AudioPlayerHandler>()
.mediaItem
.value!
.extras?['show'] ==
null) {
m.defaultTrackMenu(t,
context: context,
options: [m.sleepTimer(context), m.wakelock(context)]);
} else {
m.defaultShowEpisodeMenu(
Show.fromJson(jsonDecode(GetIt.I<AudioPlayerHandler>()
.mediaItem
.value!
.extras?['show'])),
ShowEpisode.fromMediaItem(
GetIt.I<AudioPlayerHandler>().mediaItem.value!),
context: context,
options: [m.sleepTimer(context), m.wakelock(context)]);
}
},
),
),
IconButton(
icon: libraryIcon,
onPressed: () async {
cache.libraryTracks ??= [];
if (cache.checkTrackFavorite(
Track.fromMediaItem(audioHandler.mediaItem.value!))) {
//Remove from library
setState(() => cache.libraryTracks
?.remove(audioHandler.mediaItem.value!.id));
await deezerAPI
.removeFavorite(audioHandler.mediaItem.value!.id);
await cache.save();
} else {
//Add
setState(() =>
cache.libraryTracks?.add(audioHandler.mediaItem.value!.id));
await deezerAPI
.addFavoriteTrack(audioHandler.mediaItem.value!.id);
await cache.save();
}
},
)
],
),
);
}
}
class PlaybackControls extends StatefulWidget {
final double iconSize;
const PlaybackControls(this.iconSize, {super.key});
@@ -593,32 +821,18 @@ class PlaybackControls extends StatefulWidget {
class _PlaybackControlsState extends State<PlaybackControls> {
AudioPlayerHandler audioHandler = GetIt.I<AudioPlayerHandler>();
Icon get libraryIcon {
if (cache.checkTrackFavorite(Track.fromMediaItem(audioHandler.mediaItem.value!))) {
return Icon(
Icons.favorite,
size: widget.iconSize * 0.44,
semanticLabel: 'Unlove'.i18n,
);
}
return Icon(
Icons.favorite_border,
size: widget.iconSize * 0.44,
semanticLabel: 'Love'.i18n,
);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
padding: const EdgeInsets.symmetric(horizontal: 64.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
IconButton(
/*IconButton(
icon: Icon(
Icons.sentiment_very_dissatisfied,
DeezerIcons.angry_face,
size: widget.iconSize * 0.44,
semanticLabel: 'Dislike'.i18n,
),
@@ -627,27 +841,15 @@ class _PlaybackControlsState extends State<PlaybackControls> {
if (audioHandler.queueState.hasNext) {
audioHandler.skipToNext();
}
}),
PrevNextButton(widget.iconSize, prev: true),
PlayPauseButton(widget.iconSize * 1.25),
PrevNextButton(widget.iconSize),
IconButton(
icon: libraryIcon,
onPressed: () async {
cache.libraryTracks ??= [];
if (cache.checkTrackFavorite(Track.fromMediaItem(audioHandler.mediaItem.value!))) {
//Remove from library
setState(() => cache.libraryTracks?.remove(audioHandler.mediaItem.value!.id));
await deezerAPI.removeFavorite(audioHandler.mediaItem.value!.id);
await cache.save();
} else {
//Add
setState(() => cache.libraryTracks?.add(audioHandler.mediaItem.value!.id));
await deezerAPI.addFavoriteTrack(audioHandler.mediaItem.value!.id);
await cache.save();
}
},
}),*/
Padding(
padding: EdgeInsets.only(right: 12),
child: PrevNextButton(widget.iconSize, prev: true),
),
PlayPauseButton(widget.iconSize),
Padding(
padding: EdgeInsets.only(left: 12),
child: PrevNextButton(widget.iconSize),
)
],
),
@@ -679,7 +881,8 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
_imageList = _getImageList(audioHandler.queue.value);
_currentItemAndQueueSub = Rx.combineLatest2<MediaItem?, List<MediaItem>, void>(
_currentItemAndQueueSub =
Rx.combineLatest2<MediaItem?, List<MediaItem>, void>(
audioHandler.mediaItem,
audioHandler.queue,
(mediaItem, queue) {
@@ -698,7 +901,9 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
}
List<ZoomableImage> _getImageList(List<MediaItem> queue) {
return queue.map((item) => ZoomableImage(url: item.artUri?.toString() ?? '')).toList();
return queue
.map((item) => ZoomableImage(url: item.artUri?.toString() ?? ''))
.toList();
}
bool _didQueueChange(List<MediaItem> newQueue) {
@@ -718,7 +923,8 @@ class _BigAlbumArtState extends State<BigAlbumArt> with WidgetsBindingObserver {
void _handleMediaItemChange(MediaItem? item) async {
final targetItemId = item?.id ?? '';
final targetPage = audioHandler.queue.value.indexWhere((item) => item.id == targetItemId);
final targetPage =
audioHandler.queue.value.indexWhere((item) => item.id == targetItemId);
if (targetPage == -1) return;
// No need to animating to the same page
@@ -799,7 +1005,8 @@ class PlayerScreenTopRow extends StatelessWidget {
final double? textWidth;
final bool? short;
final GlobalKey iconButtonKey = GlobalKey();
PlayerScreenTopRow({super.key, this.textSize, this.iconSize, this.textWidth, this.short});
PlayerScreenTopRow(
{super.key, this.textSize, this.iconSize, this.textWidth, this.short});
@override
Widget build(BuildContext context) {
@@ -825,7 +1032,9 @@ class PlayerScreenTopRow extends StatelessWidget {
child: Text(
(short ?? false)
? (GetIt.I<AudioPlayerHandler>().queueSource?.text ?? '')
: 'Playing from:'.i18n + ' ' + (GetIt.I<AudioPlayerHandler>().queueSource?.text ?? ''),
: 'Playing from:'.i18n +
' ' +
(GetIt.I<AudioPlayerHandler>().queueSource?.text ?? ''),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
@@ -833,32 +1042,6 @@ class PlayerScreenTopRow extends StatelessWidget {
),
),
),
IconButton(
key: iconButtonKey,
icon: Icon(
//Icons.menu,
Icons.queue_music,
semanticLabel: 'Queue'.i18n,
),
iconSize: iconSize ?? ScreenUtil().setSp(52),
splashRadius: iconSize ?? ScreenUtil().setWidth(52),
onPressed: () async {
//Fix bottom buttons (Not needed anymore?)
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
// Calculate the center of the icon
final RenderBox buttonRenderBox = iconButtonKey.currentContext!.findRenderObject() as RenderBox;
final Offset buttonOffset = buttonRenderBox.localToGlobal(buttonRenderBox.size.center(Offset.zero));
//Navigate
//await Navigator.of(context).push(MaterialPageRoute(builder: (context) => QueueScreen()));
await Navigator.of(context).push(CircularExpansionRoute(
widget: const QueueScreen(),
//centerAlignment: Alignment.topRight,
centerOffset: buttonOffset)); // Expand from icon
//Fix colors
updateColor();
},
),
],
);
}
@@ -879,7 +1062,8 @@ class _SeekBarState extends State<SeekBar> {
double get position {
if (_seeking) return _pos;
double p = audioHandler.playbackState.value.position.inMilliseconds.toDouble();
double p =
audioHandler.playbackState.value.position.inMilliseconds.toDouble();
if (p > duration) return duration;
return p;
}
@@ -900,55 +1084,64 @@ class _SeekBarState extends State<SeekBar> {
return StreamBuilder(
stream: Stream.periodic(const Duration(milliseconds: 250)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0, horizontal: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
_timeString(position),
style: TextStyle(fontSize: ScreenUtil().setSp(widget.relativeTextSize)),
return Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(
vertical: 0.0, horizontal: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
_timeString(position),
style: TextStyle(
fontSize:
ScreenUtil().setSp(widget.relativeTextSize)),
),
Text(
_timeString(duration),
style: TextStyle(
fontSize:
ScreenUtil().setSp(widget.relativeTextSize)),
)
],
),
Text(
_timeString(duration),
style: TextStyle(fontSize: ScreenUtil().setSp(widget.relativeTextSize)),
)
],
),
),
SizedBox(
height: 32.0,
child: Slider(
focusNode: FocusNode(
canRequestFocus: false,
skipTraversal: true), // Don't focus on Slider - it doesn't work (and not needed)
value: position,
max: duration,
onChangeStart: (double d) {
setState(() {
_seeking = true;
_pos = d;
});
},
onChanged: (double d) {
setState(() {
_pos = d;
});
},
onChangeEnd: (double d) async {
await audioHandler.seek(Duration(milliseconds: d.round()));
setState(() {
_pos = d;
_seeking = false;
});
},
),
)
],
);
),
SizedBox(
height: 32.0,
child: Slider(
focusNode: FocusNode(
canRequestFocus: false,
skipTraversal:
true), // Don't focus on Slider - it doesn't work (and not needed)
value: position,
max: duration,
onChangeStart: (double d) {
setState(() {
_seeking = true;
_pos = d;
});
},
onChanged: (double d) {
setState(() {
_pos = d;
});
},
onChangeEnd: (double d) async {
await audioHandler
.seek(Duration(milliseconds: d.round()));
setState(() {
_pos = d;
_seeking = false;
});
},
),
)
],
));
},
);
}
@@ -1003,7 +1196,8 @@ class _QueueScreenState extends State<QueueScreen> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
final queueState = audioHandler.queueState;
final shuffleModeEnabled = queueState.shuffleMode == AudioServiceShuffleMode.all;
final shuffleModeEnabled =
queueState.shuffleMode == AudioServiceShuffleMode.all;
return Scaffold(
appBar: FreezerAppBar(
@@ -1014,9 +1208,10 @@ class _QueueScreenState extends State<QueueScreen> with WidgetsBindingObserver {
child: IconButton(
icon: Icon(
//cons.shuffle,
ReFreezerIcons.shuffle,
DeezerIcons.shuffle,
semanticLabel: 'Shuffle'.i18n,
color: shuffleModeEnabled ? Theme.of(context).primaryColor : null,
color:
shuffleModeEnabled ? Theme.of(context).primaryColor : null,
),
onPressed: () async {
await audioHandler.toggleShuffle();
@@ -1027,12 +1222,13 @@ class _QueueScreenState extends State<QueueScreen> with WidgetsBindingObserver {
padding: const EdgeInsets.fromLTRB(0, 4, 16, 0),
child: IconButton(
icon: Icon(
Icons.close,
DeezerIcons.trash,
semanticLabel: 'Clear all'.i18n,
),
onPressed: () async {
await audioHandler.clearQueue();
mainNavigatorKey.currentState!.popUntil((route) => route.isFirst);
mainNavigatorKey.currentState!
.popUntil((route) => route.isFirst);
},
),
)

View File

@@ -5,6 +5,8 @@ import 'package:flutter/services.dart';
import 'package:fluttericon/font_awesome5_icons.dart';
import 'package:fluttericon/typicons_icons.dart';
import 'package:get_it/get_it.dart';
import 'package:refreezer/fonts/deezer_icons.dart';
import 'package:refreezer/settings.dart';
import '../api/cache.dart';
import '../api/deezer.dart';
@@ -161,7 +163,7 @@ class _SearchScreenState extends State<SearchScreen> {
actions: <Widget>[
IconButton(
icon: Icon(
Icons.file_download,
DeezerIcons.download,
semanticLabel: 'Download'.i18n,
),
onPressed: () {
@@ -171,7 +173,7 @@ class _SearchScreenState extends State<SearchScreen> {
),
IconButton(
icon: Icon(
Icons.settings,
DeezerIcons.settings,
semanticLabel: 'Settings'.i18n,
),
onPressed: () {
@@ -218,6 +220,7 @@ class _SearchScreenState extends State<SearchScreen> {
focusNode: _textFieldFocusNode,
decoration: InputDecoration(
labelText: 'Search or paste URL'.i18n,
hintStyle: TextStyle(color: settings.primaryColor),
fillColor:
Theme.of(context).bottomAppBarTheme.color,
filled: true,
@@ -446,7 +449,7 @@ class _SearchScreenState extends State<SearchScreen> {
_suggestions.length,
(i) => ListTile(
title: Text(_suggestions[i]),
leading: const Icon(Icons.search),
leading: const Icon(DeezerIcons.search),
onTap: () {
setState(() => _query = _suggestions[i]);
_submit(context);

File diff suppressed because it is too large Load Diff

Submodule marquee deleted from 4241bdf46d

View File

@@ -153,6 +153,8 @@ flutter:
- assets/icon.png
- assets/favorites_thumb.jpg
- assets/browse_icon.png
- assets/DJDoubleD.jpg
- assets/PetitPrince.png
fonts:
# - family: Montserrat
@@ -174,6 +176,9 @@ flutter:
- family: ReFreezerIcons
fonts:
- asset: assets/fonts/ReFreezerIcons.ttf
- family: Deezer
fonts:
- asset: assets/fonts/Deezer.ttf
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.

Submodule scrobblenaut deleted from 6966422e6a