use get_it + work on the new download backend + migrate to isar for cookie store
This commit is contained in:
parent
475787f433
commit
f88e43cad9
|
@ -52,6 +52,7 @@ android/app/.cxx
|
||||||
/build/
|
/build/
|
||||||
.gradle/
|
.gradle/
|
||||||
*.g.dart
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
# Web related
|
# Web related
|
||||||
lib/generated_plugin_registrant.dart
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class DeezerAudioSource extends StreamAudioSource {
|
||||||
int? trackTokenExpiration,
|
int? trackTokenExpiration,
|
||||||
this.onStreamObtained,
|
this.onStreamObtained,
|
||||||
}) : _deezerAudio = DeezerAudio(
|
}) : _deezerAudio = DeezerAudio(
|
||||||
deezerAPI: deezerAPI,
|
deezerAPI: DeezerAPI.instance,
|
||||||
quality: getQuality.call(),
|
quality: getQuality.call(),
|
||||||
trackId: trackId,
|
trackId: trackId,
|
||||||
) {
|
) {
|
||||||
|
@ -78,7 +78,7 @@ class DeezerAudioSource extends StreamAudioSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.fine("authorizing...");
|
_logger.fine("authorizing...");
|
||||||
if (!await deezerAPI.authorize()) {
|
if (!await DeezerAPI.instance.authorize()) {
|
||||||
_logger.severe("authorization failed! cannot continue!");
|
_logger.severe("authorization failed! cannot continue!");
|
||||||
throw Exception("Authorization failed!");
|
throw Exception("Authorization failed!");
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ class DeezerAudioSource extends StreamAudioSource {
|
||||||
if (_downloadUrl == null) {
|
if (_downloadUrl == null) {
|
||||||
if (_trackToken == null) {
|
if (_trackToken == null) {
|
||||||
// TODO: get new track token?
|
// TODO: get new track token?
|
||||||
final track = await deezerAPI.track(trackId);
|
final track = await DeezerAPI.instance.track(trackId);
|
||||||
_trackToken = track.trackToken;
|
_trackToken = track.trackToken;
|
||||||
_trackTokenExpiration = track.trackTokenExpiration;
|
_trackTokenExpiration = track.trackTokenExpiration;
|
||||||
_mediaVersion = track.playbackDetails![1];
|
_mediaVersion = track.playbackDetails![1];
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import 'package:cookie_jar/cookie_jar.dart';
|
|
||||||
import 'package:hive_flutter/adapters.dart';
|
|
||||||
|
|
||||||
class HiveStorage implements Storage {
|
|
||||||
final String boxName;
|
|
||||||
final String? boxPath;
|
|
||||||
HiveStorage(this.boxName, {this.boxPath});
|
|
||||||
|
|
||||||
bool _initialized = false;
|
|
||||||
late final Box<String> _box;
|
|
||||||
|
|
||||||
Future<void>? _initFuture;
|
|
||||||
|
|
||||||
Future<void> init(bool persistSession, bool ignoreExpires) =>
|
|
||||||
_initFuture ??= _init(persistSession, ignoreExpires);
|
|
||||||
|
|
||||||
Future<void> _init(bool persistSession, bool ignoreExpires) async {
|
|
||||||
if (_initialized) return;
|
|
||||||
_initialized = true;
|
|
||||||
_box = await Hive.openBox(boxName, path: boxPath);
|
|
||||||
print('init() finished');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String?> read(String key) async {
|
|
||||||
await _initFuture;
|
|
||||||
return _box.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> write(String key, String value) => _box.put(key, value);
|
|
||||||
@override
|
|
||||||
Future<void> delete(String key) => _box.delete(key);
|
|
||||||
@override
|
|
||||||
Future<void> deleteAll(List<String> keys) => _box.deleteAll(keys);
|
|
||||||
}
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import 'package:cookie_jar/cookie_jar.dart';
|
||||||
|
import 'package:freezer/utils.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
part 'cookie_jar_isar_storage.g.dart';
|
||||||
|
|
||||||
|
class IsarStorage implements Storage {
|
||||||
|
final String dbName;
|
||||||
|
final String dbPath;
|
||||||
|
IsarStorage(this.dbName, this.dbPath);
|
||||||
|
|
||||||
|
late final Isar _isar;
|
||||||
|
|
||||||
|
Future<void>? _initFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> init(bool persistSession, bool ignoreExpires) =>
|
||||||
|
_initFuture ??= _init(persistSession, ignoreExpires);
|
||||||
|
|
||||||
|
Future<void> _init(bool persistSession, bool ignoreExpires) async {
|
||||||
|
_isar = await Isar.open([CookieSchema], directory: dbPath, name: dbName);
|
||||||
|
print('init() finished');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> read(String key) async {
|
||||||
|
await _initFuture;
|
||||||
|
final cookie = await _isar.cookies.get(Utils.fastHash(key));
|
||||||
|
if (cookie == null) return null;
|
||||||
|
return cookie.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> write(String key, String value) =>
|
||||||
|
_isar.writeTxn(() => _isar.cookies.put(Cookie()
|
||||||
|
..key = key
|
||||||
|
..value = value));
|
||||||
|
@override
|
||||||
|
Future<void> delete(String key) =>
|
||||||
|
_isar.writeTxn(() => _isar.cookies.delete(Utils.fastHash(key)));
|
||||||
|
@override
|
||||||
|
Future<void> deleteAll(List<String> keys) =>
|
||||||
|
_isar.writeTxn(() => _isar.cookies
|
||||||
|
.deleteAll(keys.map(Utils.fastHash).toList(growable: false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@collection
|
||||||
|
class Cookie {
|
||||||
|
Id get isarId => Utils.fastHash(key);
|
||||||
|
|
||||||
|
late String key;
|
||||||
|
late String value;
|
||||||
|
}
|
|
@ -8,17 +8,17 @@ import 'package:freezer/api/cache.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/spotify.dart';
|
import 'package:freezer/api/spotify.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import 'cookie_jar_hive_storage.dart';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
final deezerAPI = DeezerAPI();
|
|
||||||
final cookieJar = PersistCookieJar(storage: HiveStorage('cookies'));
|
|
||||||
|
|
||||||
class DeezerAPI {
|
class DeezerAPI {
|
||||||
|
/// Shorthand for GetIt.instance<DeezerAPI>()
|
||||||
|
static DeezerAPI get instance => GetIt.instance<DeezerAPI>();
|
||||||
|
|
||||||
// from deemix: https://gitlab.com/RemixDev/deemix-js/-/blob/main/deemix/utils/deezer.js?ref_type=heads#L6
|
// from deemix: https://gitlab.com/RemixDev/deemix-js/-/blob/main/deemix/utils/deezer.js?ref_type=heads#L6
|
||||||
static const CLIENT_ID = "172365";
|
static const CLIENT_ID = "172365";
|
||||||
static const CLIENT_SECRET = "fb0bec7ccc063dab0417eb7b0d847f34";
|
static const CLIENT_SECRET = "fb0bec7ccc063dab0417eb7b0d847f34";
|
||||||
|
@ -40,7 +40,9 @@ class DeezerAPI {
|
||||||
static String get userAgent => USER_AGENTS[defaultTargetPlatform]!;
|
static String get userAgent => USER_AGENTS[defaultTargetPlatform]!;
|
||||||
|
|
||||||
static final _logger = Logger('DeezerAPI');
|
static final _logger = Logger('DeezerAPI');
|
||||||
DeezerAPI();
|
|
||||||
|
final CookieJar cookieJar;
|
||||||
|
DeezerAPI(this.cookieJar);
|
||||||
|
|
||||||
set arl(String? arl) {
|
set arl(String? arl) {
|
||||||
if (arl == null) {
|
if (arl == null) {
|
||||||
|
@ -62,6 +64,9 @@ class DeezerAPI {
|
||||||
String? favoritesPlaylistId;
|
String? favoritesPlaylistId;
|
||||||
String? sid;
|
String? sid;
|
||||||
|
|
||||||
|
late String deezerLanguage;
|
||||||
|
late String deezerCountry;
|
||||||
|
|
||||||
late String licenseToken;
|
late String licenseToken;
|
||||||
late bool canStreamLossless;
|
late bool canStreamLossless;
|
||||||
late bool canStreamHQ;
|
late bool canStreamHQ;
|
||||||
|
@ -77,13 +82,12 @@ class DeezerAPI {
|
||||||
//Get headers
|
//Get headers
|
||||||
Map<String, String> get headers => {
|
Map<String, String> get headers => {
|
||||||
"User-Agent": userAgent,
|
"User-Agent": userAgent,
|
||||||
"Content-Language":
|
"Content-Language": '$deezerLanguage-$deezerCountry',
|
||||||
'${settings.deezerLanguage}-${settings.deezerCountry}',
|
|
||||||
"Cache-Control": "max-age=0",
|
"Cache-Control": "max-age=0",
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
"Accept-Charset": "utf-8,ISO-8859-1;q=0.7,*;q=0.3",
|
"Accept-Charset": "utf-8,ISO-8859-1;q=0.7,*;q=0.3",
|
||||||
"Accept-Language":
|
"Accept-Language":
|
||||||
"${settings.deezerLanguage}-${settings.deezerCountry},${settings.deezerLanguage};q=0.9,en-US;q=0.8,en;q=0.7",
|
"$deezerLanguage-$deezerCountry,$deezerLanguage;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
"Connection": "keep-alive",
|
"Connection": "keep-alive",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -292,6 +296,9 @@ class DeezerAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get single track url
|
||||||
|
///
|
||||||
|
/// Shorcut for [getTracksUrl([trackToken], format)[0]]
|
||||||
Future<GetTrackUrlResponse> getTrackUrl(
|
Future<GetTrackUrlResponse> getTrackUrl(
|
||||||
String trackToken, String format) async =>
|
String trackToken, String format) async =>
|
||||||
(await getTracksUrl([trackToken], format))[0];
|
(await getTracksUrl([trackToken], format))[0];
|
||||||
|
@ -306,7 +313,11 @@ class DeezerAPI {
|
||||||
{
|
{
|
||||||
"type": "FULL",
|
"type": "FULL",
|
||||||
"formats": [
|
"formats": [
|
||||||
{"cipher": "BF_CBC_STRIPE", "format": format}
|
{"cipher": "BF_CBC_STRIPE", "format": format},
|
||||||
|
{
|
||||||
|
"cipher": "BF_CBC_STRIPE",
|
||||||
|
"format": "MP3_MISC" // allow for custom MP3s
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -771,7 +782,7 @@ class DeezerAPI {
|
||||||
'nb': 1000,
|
'nb': 1000,
|
||||||
'show_id': showId,
|
'show_id': showId,
|
||||||
'start': 0,
|
'start': 0,
|
||||||
'user_id': int.parse(deezerAPI.userId!)
|
'user_id': int.parse(userId!)
|
||||||
});
|
});
|
||||||
return data['results']['EPISODES']['data']
|
return data['results']['EPISODES']['data']
|
||||||
.map<ShowEpisode>((e) => ShowEpisode.fromPrivateJson(e))
|
.map<ShowEpisode>((e) => ShowEpisode.fromPrivateJson(e))
|
||||||
|
|
|
@ -174,7 +174,7 @@ class DownloadManager {
|
||||||
if (track.artists == null ||
|
if (track.artists == null ||
|
||||||
track.artists!.isEmpty ||
|
track.artists!.isEmpty ||
|
||||||
track.album == null) {
|
track.album == null) {
|
||||||
track = await deezerAPI.track(track.id);
|
track = await DeezerAPI.instance.track(track.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add to DB
|
//Add to DB
|
||||||
|
@ -212,7 +212,7 @@ class DownloadManager {
|
||||||
|
|
||||||
//Get from API if no tracks
|
//Get from API if no tracks
|
||||||
if (album!.tracks == null || album.tracks!.isEmpty) {
|
if (album!.tracks == null || album.tracks!.isEmpty) {
|
||||||
album = await deezerAPI.album(album.id);
|
album = await DeezerAPI.instance.album(album.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add to DB
|
//Add to DB
|
||||||
|
@ -258,7 +258,7 @@ class DownloadManager {
|
||||||
//Get tracks if missing
|
//Get tracks if missing
|
||||||
if (playlist!.tracks == null ||
|
if (playlist!.tracks == null ||
|
||||||
playlist.tracks!.length < playlist.trackCount!) {
|
playlist.tracks!.length < playlist.trackCount!) {
|
||||||
playlist = await deezerAPI.fullPlaylist(playlist.id);
|
playlist = await DeezerAPI.instance.fullPlaylist(playlist.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add to DB
|
//Add to DB
|
||||||
|
@ -643,6 +643,7 @@ class DownloadManager {
|
||||||
|
|
||||||
//Check storage permission
|
//Check storage permission
|
||||||
static Future<bool> checkPermission() async {
|
static Future<bool> checkPermission() async {
|
||||||
|
if (Platform.isLinux || Platform.isWindows) return true;
|
||||||
if (await Permission.storage.request().isGranted) {
|
if (await Permission.storage.request().isGranted) {
|
||||||
return true;
|
return true;
|
||||||
} else if ( // android 12 or later
|
} else if ( // android 12 or later
|
||||||
|
@ -748,7 +749,7 @@ class Download {
|
||||||
{private = true, AudioQuality? quality}) async {
|
{private = true, AudioQuality? quality}) async {
|
||||||
//Get download info
|
//Get download info
|
||||||
if (t.playbackDetails == null || t.playbackDetails == []) {
|
if (t.playbackDetails == null || t.playbackDetails == []) {
|
||||||
t = await deezerAPI.track(t.id);
|
t = await DeezerAPI.instance.track(t.id);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"private": private,
|
"private": private,
|
||||||
|
|
|
@ -21,6 +21,7 @@ class Track {
|
||||||
late final bool favorite;
|
late final bool favorite;
|
||||||
late final int? diskNumber;
|
late final int? diskNumber;
|
||||||
late final bool explicit;
|
late final bool explicit;
|
||||||
|
late final String localPath;
|
||||||
|
|
||||||
Track();
|
Track();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:flutter/src/widgets/framework.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/definitions.dart' as d;
|
import 'package:freezer/api/definitions.dart' as d;
|
||||||
|
@ -20,8 +21,19 @@ class DownloadManager {
|
||||||
|
|
||||||
late Isar _isar;
|
late Isar _isar;
|
||||||
|
|
||||||
SendPort? _sendPort;
|
|
||||||
Isolate? _isolate;
|
Isolate? _isolate;
|
||||||
|
ServiceInterface? _service;
|
||||||
|
|
||||||
|
bool _started = false;
|
||||||
|
|
||||||
|
Future<void> startDebug() async {
|
||||||
|
if (_started) return;
|
||||||
|
_started = true;
|
||||||
|
|
||||||
|
await configure();
|
||||||
|
await startService();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> configure() async {
|
Future<bool> configure() async {
|
||||||
_isar = await Isar.open(
|
_isar = await Isar.open(
|
||||||
|
@ -56,13 +68,28 @@ class DownloadManager {
|
||||||
|
|
||||||
Future<bool> startService() async {
|
Future<bool> startService() async {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
return FlutterBackgroundService().startService();
|
final didStart = await FlutterBackgroundService().startService();
|
||||||
|
|
||||||
|
if (!didStart) return false;
|
||||||
|
} else {
|
||||||
|
// UI -> Service communication
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
_isolate = await Isolate.spawn(_startIsolate, receivePort.sendPort);
|
||||||
|
_service = ServiceInterface(receivePort: receivePort);
|
||||||
|
_service!.on('sendPort', (args) {
|
||||||
|
_service!.sendPort = args!['s'];
|
||||||
|
_service!.no('sendPort');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final receivePort = ReceivePort();
|
final completer = Completer<void>();
|
||||||
_sendPort = receivePort.sendPort;
|
on('ready', (args) => completer.complete());
|
||||||
_isolate = await Isolate.spawn(
|
await completer.future;
|
||||||
_startService, ServiceInterface(receivePort: receivePort));
|
invoke('updateSettings', {
|
||||||
|
'downloadFilename': settings.downloadFilename,
|
||||||
|
'deezerLanguage': settings.deezerLanguage,
|
||||||
|
'deezerCountry': settings.deezerCountry,
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -72,34 +99,44 @@ class DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
void invoke(String method, [Map<String, dynamic>? args]) {
|
void invoke(String method, [Map<String, dynamic>? args]) {
|
||||||
if (_sendPort != null) {
|
if (_service != null) {
|
||||||
_sendPort!.send({
|
_service!.send(method, args);
|
||||||
'method': method,
|
|
||||||
if (args != null) ...args,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlutterBackgroundService().invoke(method, args);
|
FlutterBackgroundService().invoke(method, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addOfflineTrack(d.Track track,
|
void on(String method, ListenerCallback listener) {
|
||||||
{bool private = true, BuildContext? context, isSingleton = false}) async {
|
if (_service != null) {
|
||||||
|
_service!.on(method, listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlutterBackgroundService().on(method).listen(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> checkOffline(String trackId) async {
|
||||||
|
final c =
|
||||||
|
await _isar.tracks.where().isarIdEqualTo(int.parse(trackId)).count();
|
||||||
|
return c > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> addOfflineTrack(d.Track track, AudioQuality downloadQuality,
|
||||||
|
{bool private = true}) async {
|
||||||
//Permission
|
//Permission
|
||||||
if (!private && !(await dl.DownloadManager.checkCanDownload())) {
|
if (!private && !(await dl.DownloadManager.checkCanDownload())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Ask for quality
|
if (downloadQuality == AudioQuality.ASK) {
|
||||||
//AudioQuality? quality;
|
throw Exception('Invalid quality.');
|
||||||
if (!private && settings.downloadQuality == AudioQuality.ASK) {
|
|
||||||
// quality = await qualitySelect(context!);
|
|
||||||
// if (quality == null) return false;
|
|
||||||
}
|
}
|
||||||
if (private) {
|
if (private) {
|
||||||
if (track.artists == null ||
|
if (track.artists == null ||
|
||||||
track.artists!.isEmpty ||
|
track.artists!.isEmpty ||
|
||||||
track.album == null) {
|
track.album == null) {
|
||||||
track = await deezerAPI.track(track.id);
|
track = await DeezerAPI.instance.track(track.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache album art
|
// cache album art
|
||||||
|
@ -120,7 +157,12 @@ class DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// logic for downloading the track
|
// logic for downloading the track
|
||||||
invoke('addDownloads', {'track': track.toJson()});
|
invoke(
|
||||||
|
'addDownloads',
|
||||||
|
DownloadInfo(
|
||||||
|
trackId: track.id,
|
||||||
|
path: private ? null : settings.downloadPath,
|
||||||
|
).toJson());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -128,6 +170,14 @@ class DownloadManager {
|
||||||
static void _startNative(ServiceInstance service) =>
|
static void _startNative(ServiceInstance service) =>
|
||||||
_startService(ServiceInterface(service: service));
|
_startService(ServiceInterface(service: service));
|
||||||
|
|
||||||
|
static void _startIsolate(SendPort sendPort) async {
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
final service = ServiceInterface(receivePort: receivePort)
|
||||||
|
..sendPort = sendPort;
|
||||||
|
service.send('sendPort', {'s': receivePort.sendPort});
|
||||||
|
return _startService(service);
|
||||||
|
}
|
||||||
|
|
||||||
static void _startService(ServiceInterface service) =>
|
static void _startService(ServiceInterface service) =>
|
||||||
DownloadService(service).run();
|
DownloadService(service).run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:freezer/api/deezer.dart';
|
||||||
|
import 'package:freezer/api/deezer_audio.dart';
|
||||||
|
import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/api/download_manager/service_interface.dart';
|
import 'package:freezer/api/download_manager/service_interface.dart';
|
||||||
|
import 'package:freezer/main.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
|
part 'download_service.freezed.dart';
|
||||||
|
part 'download_service.g.dart';
|
||||||
|
|
||||||
class DownloadService {
|
class DownloadService {
|
||||||
static const NOTIFICATION_ID = 6969;
|
static const NOTIFICATION_ID = 6969;
|
||||||
|
@ -12,12 +24,18 @@ class DownloadService {
|
||||||
AudioQuality? _downloadQuality;
|
AudioQuality? _downloadQuality;
|
||||||
bool useGetURL = false;
|
bool useGetURL = false;
|
||||||
|
|
||||||
void run() {
|
late String downloadFilename;
|
||||||
service.on('addDownloads').listen((event) {});
|
|
||||||
service.on('updateQuality').listen((event) {
|
late DeezerAPI _deezerAPI;
|
||||||
|
|
||||||
|
void run() async {
|
||||||
|
service.on('addDownloads', (event) {
|
||||||
|
downloadTrack(DownloadInfo.fromJson(event!));
|
||||||
|
});
|
||||||
|
service.on('updateQuality', (event) {
|
||||||
_preferredQuality = AudioQuality.values[event!['q']!];
|
_preferredQuality = AudioQuality.values[event!['q']!];
|
||||||
});
|
});
|
||||||
service.on('updateCapabilities').listen((event) {
|
service.on('updateCapabilities', (event) {
|
||||||
final bool canStreamHQ = event!['canStreamHQ'];
|
final bool canStreamHQ = event!['canStreamHQ'];
|
||||||
final bool canStreamLossless = event['canStreamLossless'];
|
final bool canStreamLossless = event['canStreamLossless'];
|
||||||
|
|
||||||
|
@ -26,12 +44,67 @@ class DownloadService {
|
||||||
_downloadQuality = settings.maxQualityFor(
|
_downloadQuality = settings.maxQualityFor(
|
||||||
_preferredQuality!, canStreamHQ, canStreamLossless);
|
_preferredQuality!, canStreamHQ, canStreamLossless);
|
||||||
});
|
});
|
||||||
|
service.on('updateSettings', (event) {
|
||||||
|
if (event!['downloadFilename'] != null) {
|
||||||
|
downloadFilename = event['downloadFilename'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event['deezerLanguage'] != null) {
|
||||||
|
_deezerAPI.deezerLanguage = event['deezerLanguage'];
|
||||||
|
}
|
||||||
|
if (event['deezerCountry'] != null) {
|
||||||
|
_deezerAPI.deezerCountry = event['deezerCountry'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_deezerAPI = DeezerAPI(await getCookieJar());
|
||||||
|
await _deezerAPI.authorize();
|
||||||
|
|
||||||
|
service.send('ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
void downloadTrack(String trackId) {
|
void downloadTrack(DownloadInfo info) async {
|
||||||
// final deezerAudio = DeezerAudio(deezerAPI: deezerAPI, md5origin: md5origin, quality: quality, trackId: trackId, mediaVersion: mediaVersion)
|
final trackId = info.trackId;
|
||||||
// if (useGetURL) {
|
final deezerAudio = DeezerAudio(
|
||||||
// final url =
|
deezerAPI: _deezerAPI,
|
||||||
// }
|
quality: _downloadQuality ?? AudioQuality.MP3_128,
|
||||||
|
trackId: trackId);
|
||||||
|
|
||||||
|
final track = await _deezerAPI.track(trackId);
|
||||||
|
final file = File(join(
|
||||||
|
info.path!,
|
||||||
|
downloadFilename
|
||||||
|
.replaceAll('%artist%', track.artists?.join(',') ?? '')
|
||||||
|
.replaceAll('%title%', track.title!)));
|
||||||
|
print('downloading to ${file.path}');
|
||||||
|
final Uri uri;
|
||||||
|
if (useGetURL) {
|
||||||
|
// TODO: use pipe API to get track token!
|
||||||
|
final res = await deezerAudio.getUrl(
|
||||||
|
track.trackToken!, track.trackTokenExpiration!);
|
||||||
|
uri = res!.$1;
|
||||||
|
} else {
|
||||||
|
final res = await deezerAudio.fallback(
|
||||||
|
md5origin: track.playbackDetails![0],
|
||||||
|
mediaVersion: track.playbackDetails![1]);
|
||||||
|
uri = res.uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// download url and decrypt
|
||||||
|
final req = await _deezerAPI.dio.get(uri.toString(),
|
||||||
|
options: Options(responseType: ResponseType.bytes));
|
||||||
|
final stream =
|
||||||
|
DeezerAudio.decryptionStream(req.data, start: 0, trackId: trackId);
|
||||||
|
final fWrite = file.openWrite();
|
||||||
|
await stream.pipe(fWrite);
|
||||||
|
print('download complete!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class DownloadInfo with _$DownloadInfo {
|
||||||
|
const factory DownloadInfo({required String trackId, String? path}) =
|
||||||
|
_DownloadInfo;
|
||||||
|
factory DownloadInfo.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DownloadInfoFromJson(json);
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
|
import 'dart:collection';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
|
|
||||||
|
typedef ListenerCallback = void Function(Map<String, dynamic>? args);
|
||||||
|
|
||||||
class ServiceInterface {
|
class ServiceInterface {
|
||||||
final ReceivePort? receivePort;
|
final ReceivePort? receivePort;
|
||||||
|
late SendPort sendPort;
|
||||||
final ServiceInstance? service;
|
final ServiceInstance? service;
|
||||||
|
|
||||||
|
bool _isListening = false;
|
||||||
|
final _listeners = HashMap<String, ListenerCallback>();
|
||||||
|
|
||||||
ServiceInterface({this.receivePort, this.service})
|
ServiceInterface({this.receivePort, this.service})
|
||||||
: assert(receivePort != null || service != null);
|
: assert(receivePort != null || service != null);
|
||||||
|
|
||||||
Stream<Map<String, dynamic>?> on(String method) {
|
void on(String method, ListenerCallback listener) {
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
return service!.on(method);
|
service!.on(method).listen(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
return receivePort!
|
if (!_isListening) {
|
||||||
.where((event) => event['method'] == method)
|
_isListening = true;
|
||||||
.map((event) => (event as Map?)?.cast<String, dynamic>());
|
receivePort!.listen((message) {
|
||||||
|
final method = message['_'];
|
||||||
|
_listeners[method]!.call(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_listeners[method] = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void no(String method) {
|
||||||
|
_listeners.remove(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(String method, [Map<String, dynamic>? args]) {
|
||||||
|
if (service != null) {
|
||||||
|
return service!.invoke(method, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPort!.send({'_': method, if (args != null) ...args});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/download.dart';
|
import 'package:freezer/api/download.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
Importer importer = Importer();
|
Importer importer = Importer();
|
||||||
|
|
||||||
|
@ -29,6 +30,8 @@ class Importer {
|
||||||
int get error =>
|
int get error =>
|
||||||
tracks.fold(0, (v, t) => (t.state == TrackImportState.ERROR) ? v + 1 : v);
|
tracks.fold(0, (v, t) => (t.state == TrackImportState.ERROR) ? v + 1 : v);
|
||||||
|
|
||||||
|
final deezerAPI = GetIt.instance<DeezerAPI>();
|
||||||
|
|
||||||
Importer();
|
Importer();
|
||||||
|
|
||||||
//Start importing wrapper
|
//Start importing wrapper
|
||||||
|
@ -45,8 +48,8 @@ class Importer {
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
//Create playlist
|
//Create playlist
|
||||||
playlistId =
|
playlistId = await DeezerAPI.instance
|
||||||
await deezerAPI.createPlaylist(title, description: description);
|
.createPlaylist(title, description: description);
|
||||||
|
|
||||||
busy = true;
|
busy = true;
|
||||||
done = false;
|
done = false;
|
||||||
|
|
|
@ -61,4 +61,12 @@ class Paths {
|
||||||
|
|
||||||
return (await getTemporaryDirectory()).path;
|
return (await getTemporaryDirectory()).path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<String> offlineDir() async {
|
||||||
|
final topDir = Platform.isLinux || Platform.isWindows
|
||||||
|
? await dataDirectory()
|
||||||
|
: (await getExternalStorageDirectory())!.path;
|
||||||
|
final target = await Directory(path.join(topDir, 'offline')).create();
|
||||||
|
return target.path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class PipeAPI {
|
||||||
|
|
||||||
final _logger = Logger('PipeAPI');
|
final _logger = Logger('PipeAPI');
|
||||||
|
|
||||||
Dio get dio => deezerAPI.dio;
|
Dio get dio => DeezerAPI.instance.dio;
|
||||||
|
|
||||||
Future<void> authorize({bool force = false}) async {
|
Future<void> authorize({bool force = false}) async {
|
||||||
// authorize on pipe.deezer.com
|
// authorize on pipe.deezer.com
|
||||||
|
|
|
@ -27,7 +27,6 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
PlayerHelper playerHelper = PlayerHelper();
|
PlayerHelper playerHelper = PlayerHelper();
|
||||||
late AudioPlayerTask audioHandler;
|
|
||||||
bool failsafe = false;
|
bool failsafe = false;
|
||||||
|
|
||||||
class AudioPlayerTaskInitArguments {
|
class AudioPlayerTaskInitArguments {
|
||||||
|
@ -57,15 +56,6 @@ class AudioPlayerTaskInitArguments {
|
||||||
lastFMUsername: settings.lastFMUsername,
|
lastFMUsername: settings.lastFMUsername,
|
||||||
lastFMPassword: settings.lastFMPassword);
|
lastFMPassword: settings.lastFMPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<AudioPlayerTaskInitArguments> loadSettings() async {
|
|
||||||
final settings = await Settings.load();
|
|
||||||
|
|
||||||
final deezerAPI = DeezerAPI()..arl = settings.arl;
|
|
||||||
await deezerAPI.authorize();
|
|
||||||
|
|
||||||
return from(settings: settings, deezerAPI: deezerAPI);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AudioPlayerTask extends BaseAudioHandler {
|
class AudioPlayerTask extends BaseAudioHandler {
|
||||||
|
@ -145,11 +135,7 @@ class AudioPlayerTask extends BaseAudioHandler {
|
||||||
late final LazyBox _box;
|
late final LazyBox _box;
|
||||||
|
|
||||||
AudioPlayerTask([AudioPlayerTaskInitArguments? initArgs]) {
|
AudioPlayerTask([AudioPlayerTaskInitArguments? initArgs]) {
|
||||||
if (initArgs == null) {
|
unawaited(_start(initArgs!));
|
||||||
unawaited(AudioPlayerTaskInitArguments.loadSettings().then(_start));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unawaited(_start(initArgs));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _start(AudioPlayerTaskInitArguments initArgs) async {
|
Future<void> _start(AudioPlayerTaskInitArguments initArgs) async {
|
||||||
|
|
|
@ -8,9 +8,12 @@ import 'package:freezer/api/player/audio_handler.dart';
|
||||||
import 'package:freezer/main.dart';
|
import 'package:freezer/main.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
AudioPlayerTask get audioHandler => GetIt.instance<AudioPlayerTask>();
|
||||||
|
|
||||||
class PlayerHelper {
|
class PlayerHelper {
|
||||||
late StreamSubscription _customEventSubscription;
|
late StreamSubscription _customEventSubscription;
|
||||||
late StreamSubscription _mediaItemSubscription;
|
late StreamSubscription _mediaItemSubscription;
|
||||||
|
@ -66,9 +69,9 @@ class PlayerHelper {
|
||||||
failsafe = true;
|
failsafe = true;
|
||||||
|
|
||||||
final initArgs = AudioPlayerTaskInitArguments.from(
|
final initArgs = AudioPlayerTaskInitArguments.from(
|
||||||
settings: settings, deezerAPI: deezerAPI);
|
settings: settings, deezerAPI: DeezerAPI.instance);
|
||||||
// initialize our audiohandler instance
|
// initialize our audiohandler instance
|
||||||
audioHandler = await AudioService.init<AudioPlayerTask>(
|
final audioHandler = await AudioService.init<AudioPlayerTask>(
|
||||||
builder: () => AudioPlayerTask(initArgs),
|
builder: () => AudioPlayerTask(initArgs),
|
||||||
config: AudioServiceConfig(
|
config: AudioServiceConfig(
|
||||||
notificationColor: settings.primaryColor,
|
notificationColor: settings.primaryColor,
|
||||||
|
@ -87,6 +90,8 @@ class PlayerHelper {
|
||||||
),
|
),
|
||||||
cacheManager: cacheManager,
|
cacheManager: cacheManager,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
GetIt.instance.registerSingleton<AudioPlayerTask>(audioHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> start() async {
|
Future<void> start() async {
|
||||||
|
@ -214,7 +219,7 @@ class PlayerHelper {
|
||||||
|
|
||||||
//Play mix by track
|
//Play mix by track
|
||||||
Future<void> playMix(String trackId, String trackTitle) async {
|
Future<void> playMix(String trackId, String trackTitle) async {
|
||||||
List<Track> tracks = await deezerAPI.playMix(trackId);
|
List<Track> tracks = await DeezerAPI.instance.playMix(trackId);
|
||||||
await playFromTrackList(
|
await playFromTrackList(
|
||||||
tracks,
|
tracks,
|
||||||
tracks[0].id,
|
tracks[0].id,
|
||||||
|
@ -232,7 +237,8 @@ class PlayerHelper {
|
||||||
id: track.id,
|
id: track.id,
|
||||||
text: 'Mix based on %s'.i18n.fill([track.title!]),
|
text: 'Mix based on %s'.i18n.fill([track.title!]),
|
||||||
source: 'searchMix'));
|
source: 'searchMix'));
|
||||||
List<Track> tracks = await deezerAPI.getSearchTrackMix(track.id, false);
|
List<Track> tracks =
|
||||||
|
await DeezerAPI.instance.getSearchTrackMix(track.id, false);
|
||||||
// discard first track (if it is the searched track)
|
// discard first track (if it is the searched track)
|
||||||
if (tracks[0].id == track.id) tracks.removeAt(0);
|
if (tracks[0].id == track.id) tracks.removeAt(0);
|
||||||
await playFuture; // avoid race conditions
|
await playFuture; // avoid race conditions
|
||||||
|
@ -243,7 +249,8 @@ class PlayerHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> playSearchMix(String trackId, String trackTitle) async {
|
Future<void> playSearchMix(String trackId, String trackTitle) async {
|
||||||
List<Track> tracks = await deezerAPI.getSearchTrackMix(trackId, true);
|
List<Track> tracks =
|
||||||
|
await DeezerAPI.instance.getSearchTrackMix(trackId, true);
|
||||||
await playFromTrackList(
|
await playFromTrackList(
|
||||||
tracks,
|
tracks,
|
||||||
null, // we can avoid passing it, as the index is 0
|
null, // we can avoid passing it, as the index is 0
|
||||||
|
@ -309,9 +316,9 @@ class PlayerHelper {
|
||||||
|
|
||||||
//Flow songs cannot be accessed by smart track list call
|
//Flow songs cannot be accessed by smart track list call
|
||||||
if (stl.id! == 'flow') {
|
if (stl.id! == 'flow') {
|
||||||
stl.tracks = await deezerAPI.flow(stl.flowConfig);
|
stl.tracks = await DeezerAPI.instance.flow(stl.flowConfig);
|
||||||
} else {
|
} else {
|
||||||
stl = await deezerAPI.smartTrackList(stl.id);
|
stl = await DeezerAPI.instance.smartTrackList(stl.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QueueSource queueSource = QueueSource(
|
QueueSource queueSource = QueueSource(
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:freezer/api/player/audio_handler.dart';
|
import 'package:freezer/api/player/audio_handler.dart';
|
||||||
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/importer.dart';
|
import 'package:freezer/api/importer.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:html/dom.dart' as dom;
|
import 'package:html/dom.dart' as dom;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
@ -13,6 +14,7 @@ import 'dart:io';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class SpotifyScrapper {
|
class SpotifyScrapper {
|
||||||
|
static final deezerAPI = GetIt.instance<DeezerAPI>();
|
||||||
//Parse spotify URL to URI (spotify:track:1234)
|
//Parse spotify URL to URI (spotify:track:1234)
|
||||||
static String? parseUrl(String url) {
|
static String? parseUrl(String url) {
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:cookie_jar/cookie_jar.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -13,6 +14,7 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:freezer/api/cache.dart';
|
import 'package:freezer/api/cache.dart';
|
||||||
|
import 'package:freezer/api/cookie_jar_isar_storage.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/paths.dart';
|
import 'package:freezer/api/paths.dart';
|
||||||
import 'package:freezer/icons.dart';
|
import 'package:freezer/icons.dart';
|
||||||
|
@ -28,6 +30,7 @@ import 'package:freezer/ui/login_screen.dart';
|
||||||
import 'package:freezer/ui/player_screen.dart';
|
import 'package:freezer/ui/player_screen.dart';
|
||||||
import 'package:freezer/ui/search.dart';
|
import 'package:freezer/ui/search.dart';
|
||||||
import 'package:freezer/ui/settings_screen.dart';
|
import 'package:freezer/ui/settings_screen.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:i18n_extension/i18n_widget.dart';
|
import 'package:i18n_extension/i18n_widget.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
@ -94,7 +97,17 @@ void main() async {
|
||||||
..registerAdapter(NavigationRailAppearanceAdapter())
|
..registerAdapter(NavigationRailAppearanceAdapter())
|
||||||
..registerAdapter(HiveCacheObjectAdapter(typeId: 35)); // not working?
|
..registerAdapter(HiveCacheObjectAdapter(typeId: 35)); // not working?
|
||||||
|
|
||||||
Hive.init(await Paths.dataDirectory());
|
final dataDir = await Paths.dataDirectory();
|
||||||
|
|
||||||
|
Hive.init(dataDir);
|
||||||
|
|
||||||
|
// photos
|
||||||
|
cacheManager = CacheManager(Config(
|
||||||
|
DefaultCacheManager.key,
|
||||||
|
// cache aggressively
|
||||||
|
stalePeriod: const Duration(days: 30),
|
||||||
|
maxNrOfCacheObjects: 5000,
|
||||||
|
));
|
||||||
|
|
||||||
//Initialize globals
|
//Initialize globals
|
||||||
try {
|
try {
|
||||||
|
@ -110,16 +123,16 @@ void main() async {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
downloadManager.init();
|
downloadManager.init();
|
||||||
// photos
|
|
||||||
cacheManager = CacheManager(Config(
|
|
||||||
DefaultCacheManager.key,
|
|
||||||
// cache aggressively
|
|
||||||
stalePeriod: const Duration(days: 30),
|
|
||||||
maxNrOfCacheObjects: 5000,
|
|
||||||
));
|
|
||||||
// cacheManager = HiveCacheManager(
|
// cacheManager = HiveCacheManager(
|
||||||
// boxName: 'freezer-images', boxPath: await Paths.cacheDir());
|
// boxName: 'freezer-images', boxPath: await Paths.cacheDir());
|
||||||
// TODO: WA
|
// TODO: WA
|
||||||
|
final cookieJar =
|
||||||
|
GetIt.instance.registerSingleton<CookieJar>(await getCookieJar());
|
||||||
|
final deezerAPI =
|
||||||
|
GetIt.instance.registerSingleton<DeezerAPI>(DeezerAPI(cookieJar));
|
||||||
|
deezerAPI.deezerCountry = settings.deezerCountry;
|
||||||
|
deezerAPI.deezerLanguage = settings.deezerLanguage;
|
||||||
deezerAPI.favoritesPlaylistId = cache.favoritesPlaylistId;
|
deezerAPI.favoritesPlaylistId = cache.favoritesPlaylistId;
|
||||||
|
|
||||||
Logger.root.onRecord.listen((record) {
|
Logger.root.onRecord.listen((record) {
|
||||||
|
@ -138,6 +151,9 @@ void main() async {
|
||||||
runApp(const FreezerApp());
|
runApp(const FreezerApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<PersistCookieJar> getCookieJar() async => PersistCookieJar(
|
||||||
|
storage: IsarStorage('cookies', await Paths.dataDirectory()));
|
||||||
|
|
||||||
class FreezerApp extends StatefulWidget {
|
class FreezerApp extends StatefulWidget {
|
||||||
const FreezerApp({super.key});
|
const FreezerApp({super.key});
|
||||||
|
|
||||||
|
@ -276,6 +292,7 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (settings.arl != null) {
|
if (settings.arl != null) {
|
||||||
|
final deezerAPI = GetIt.instance<DeezerAPI>();
|
||||||
//Load token on background
|
//Load token on background
|
||||||
deezerAPI.arl = settings.arl!;
|
deezerAPI.arl = settings.arl!;
|
||||||
settings.offlineMode = true;
|
settings.offlineMode = true;
|
||||||
|
@ -297,7 +314,7 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _logOut() async {
|
Future _logOut() async {
|
||||||
await deezerAPI.logout();
|
await GetIt.instance<DeezerAPI>().logout();
|
||||||
setState(() {
|
setState(() {
|
||||||
settings.arl = null;
|
settings.arl = null;
|
||||||
settings.offlineMode = false;
|
settings.offlineMode = false;
|
||||||
|
@ -423,13 +440,14 @@ class MainScreenState extends State<MainScreen>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startPreload(String type) async {
|
void _startPreload(String type) async {
|
||||||
await deezerAPI.authorize();
|
await DeezerAPI.instance.authorize();
|
||||||
if (type == 'flow') {
|
if (type == 'flow') {
|
||||||
await playerHelper.playFromSmartTrackList(SmartTrackList(id: 'flow'));
|
await playerHelper.playFromSmartTrackList(SmartTrackList(id: 'flow'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type == 'favorites') {
|
if (type == 'favorites') {
|
||||||
Playlist p = await deezerAPI.fullPlaylist(deezerAPI.favoritesPlaylistId);
|
Playlist p = await DeezerAPI.instance
|
||||||
|
.fullPlaylist(DeezerAPI.instance.favoritesPlaylistId);
|
||||||
playerHelper.playFromPlaylist(p, p.tracks![0].id);
|
playerHelper.playFromPlaylist(p, p.tracks![0].id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,7 +458,7 @@ class MainScreenState extends State<MainScreen>
|
||||||
await DownloadManager.platform.invokeMethod('getPreloadInfo');
|
await DownloadManager.platform.invokeMethod('getPreloadInfo');
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
//Used if started from android auto
|
//Used if started from android auto
|
||||||
await deezerAPI.authorize();
|
await DeezerAPI.instance.authorize();
|
||||||
_startPreload(info);
|
_startPreload(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
|
||||||
//Get album from API, if doesn't have tracks
|
//Get album from API, if doesn't have tracks
|
||||||
if (album!.tracks == null || album!.tracks!.isEmpty) {
|
if (album!.tracks == null || album!.tracks!.isEmpty) {
|
||||||
try {
|
try {
|
||||||
Album a = await deezerAPI.album(album!.id);
|
Album a = await DeezerAPI.instance.album(album!.id);
|
||||||
//Preserve library
|
//Preserve library
|
||||||
a.library = album!.library;
|
a.library = album!.library;
|
||||||
setState(() => album = a);
|
setState(() => album = a);
|
||||||
|
@ -185,14 +185,15 @@ class _AlbumDetailsState extends State<AlbumDetails> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
//Add to library
|
//Add to library
|
||||||
if (!album!.library!) {
|
if (!album!.library!) {
|
||||||
await deezerAPI.addFavoriteAlbum(album!.id);
|
await DeezerAPI.instance
|
||||||
|
.addFavoriteAlbum(album!.id);
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.snack('Added to library'.i18n);
|
.snack('Added to library'.i18n);
|
||||||
setState(() => album!.library = true);
|
setState(() => album!.library = true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//Remove
|
//Remove
|
||||||
await deezerAPI.removeAlbum(album!.id);
|
await DeezerAPI.instance.removeAlbum(album!.id);
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.snack('Album removed from library!'.i18n);
|
.snack('Album removed from library!'.i18n);
|
||||||
setState(() => album!.library = false);
|
setState(() => album!.library = false);
|
||||||
|
@ -278,7 +279,7 @@ class _MakeAlbumOfflineState extends State<MakeAlbumOffline> {
|
||||||
onChanged: (v) async {
|
onChanged: (v) async {
|
||||||
if (v) {
|
if (v) {
|
||||||
//Add to offline
|
//Add to offline
|
||||||
await deezerAPI.addFavoriteAlbum(widget.album!.id);
|
await DeezerAPI.instance.addFavoriteAlbum(widget.album!.id);
|
||||||
downloadManager.addOfflineAlbum(widget.album, private: true);
|
downloadManager.addOfflineAlbum(widget.album, private: true);
|
||||||
MenuSheet(context).showDownloadStartedToast();
|
MenuSheet(context).showDownloadStartedToast();
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -325,7 +326,7 @@ class _ArtistDetailsState extends State<ArtistDetails> {
|
||||||
Future<Artist> _loadArtist(Artist artist) async {
|
Future<Artist> _loadArtist(Artist artist) async {
|
||||||
//Load artist from api if no albums
|
//Load artist from api if no albums
|
||||||
if ((artist.albums ?? []).isEmpty) {
|
if ((artist.albums ?? []).isEmpty) {
|
||||||
return await deezerAPI.artist(artist.id);
|
return await DeezerAPI.instance.artist(artist.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return artist;
|
return artist;
|
||||||
|
@ -428,7 +429,8 @@ class _ArtistDetailsState extends State<ArtistDetails> {
|
||||||
icon: const Icon(Icons.favorite),
|
icon: const Icon(Icons.favorite),
|
||||||
label: Text('Library'.i18n),
|
label: Text('Library'.i18n),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await deezerAPI.addFavoriteArtist(widget.artist.id);
|
await DeezerAPI.instance
|
||||||
|
.addFavoriteArtist(widget.artist.id);
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.snack('Added to library'.i18n);
|
.snack('Added to library'.i18n);
|
||||||
},
|
},
|
||||||
|
@ -439,7 +441,7 @@ class _ArtistDetailsState extends State<ArtistDetails> {
|
||||||
label: Text('Radio'.i18n),
|
label: Text('Radio'.i18n),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
List<Track> tracks =
|
List<Track> tracks =
|
||||||
(await deezerAPI.smartRadio(artist.id))!;
|
(await DeezerAPI.instance.smartRadio(artist.id))!;
|
||||||
playerHelper.playFromTrackList(
|
playerHelper.playFromTrackList(
|
||||||
tracks,
|
tracks,
|
||||||
tracks[0].id,
|
tracks[0].id,
|
||||||
|
@ -593,8 +595,8 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
|
||||||
//Fetch data
|
//Fetch data
|
||||||
List<Album>? data;
|
List<Album>? data;
|
||||||
try {
|
try {
|
||||||
data = await deezerAPI.discographyPage(artist!.id,
|
data = await DeezerAPI.instance
|
||||||
start: artist!.albums!.length);
|
.discographyPage(artist!.id, start: artist!.albums!.length);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = true;
|
_error = true;
|
||||||
|
@ -787,7 +789,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
//Get another page of tracks
|
//Get another page of tracks
|
||||||
List<Track>? tracks;
|
List<Track>? tracks;
|
||||||
try {
|
try {
|
||||||
tracks = await deezerAPI.playlistTracksPage(playlist!.id, pos);
|
tracks = await DeezerAPI.instance.playlistTracksPage(playlist!.id, pos);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = true;
|
_error = true;
|
||||||
|
@ -816,7 +818,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
|
|
||||||
//Preload tracks
|
//Preload tracks
|
||||||
if (playlist!.tracks!.length < playlist!.trackCount!) {
|
if (playlist!.tracks!.length < playlist!.trackCount!) {
|
||||||
playlist = await deezerAPI.fullPlaylist(playlist!.id);
|
playlist = await DeezerAPI.instance.fullPlaylist(playlist!.id);
|
||||||
}
|
}
|
||||||
setState(() => _sort = cache.sorts[index]);
|
setState(() => _sort = cache.sorts[index]);
|
||||||
}
|
}
|
||||||
|
@ -834,7 +836,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
|
|
||||||
//Preload for sorting
|
//Preload for sorting
|
||||||
if (playlist!.tracks!.length < playlist!.trackCount!) {
|
if (playlist!.tracks!.length < playlist!.trackCount!) {
|
||||||
playlist = await deezerAPI.fullPlaylist(playlist!.id);
|
playlist = await DeezerAPI.instance.fullPlaylist(playlist!.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,7 +854,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
//Load if no tracks
|
//Load if no tracks
|
||||||
if (playlist!.tracks!.isEmpty) {
|
if (playlist!.tracks!.isEmpty) {
|
||||||
//Get correct metadata
|
//Get correct metadata
|
||||||
deezerAPI.playlist(playlist!.id).then((Playlist p) {
|
DeezerAPI.instance.playlist(playlist!.id).then((Playlist p) {
|
||||||
setState(() {
|
setState(() {
|
||||||
playlist = p;
|
playlist = p;
|
||||||
});
|
});
|
||||||
|
@ -987,7 +989,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
MakePlaylistOffline(playlist),
|
MakePlaylistOffline(playlist),
|
||||||
if (playlist!.user!.name != deezerAPI.userName)
|
if (playlist!.user!.name != DeezerAPI.instance.userName)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
playlist!.library!
|
playlist!.library!
|
||||||
|
@ -1000,14 +1002,14 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
//Add to library
|
//Add to library
|
||||||
if (!playlist!.library!) {
|
if (!playlist!.library!) {
|
||||||
await deezerAPI.addPlaylist(playlist!.id);
|
await DeezerAPI.instance.addPlaylist(playlist!.id);
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.snack('Added to library'.i18n);
|
.snack('Added to library'.i18n);
|
||||||
setState(() => playlist!.library = true);
|
setState(() => playlist!.library = true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//Remove
|
//Remove
|
||||||
await deezerAPI.removePlaylist(playlist!.id);
|
await DeezerAPI.instance.removePlaylist(playlist!.id);
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.snack('Playlist removed from library!'.i18n);
|
.snack('Playlist removed from library!'.i18n);
|
||||||
setState(() => playlist!.library = false);
|
setState(() => playlist!.library = false);
|
||||||
|
@ -1030,7 +1032,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
onSelected: (SortType s) async {
|
onSelected: (SortType s) async {
|
||||||
if (playlist!.tracks!.length < playlist!.trackCount!) {
|
if (playlist!.tracks!.length < playlist!.trackCount!) {
|
||||||
//Preload whole playlist
|
//Preload whole playlist
|
||||||
playlist = await deezerAPI.fullPlaylist(playlist!.id);
|
playlist =
|
||||||
|
await DeezerAPI.instance.fullPlaylist(playlist!.id);
|
||||||
}
|
}
|
||||||
setState(() => _sort!.type = s);
|
setState(() => _sort!.type = s);
|
||||||
|
|
||||||
|
@ -1097,7 +1100,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
}, onSecondary: (details) {
|
}, onSecondary: (details) {
|
||||||
MenuSheet m = MenuSheet(context);
|
MenuSheet m = MenuSheet(context);
|
||||||
m.defaultTrackMenu(t, details: details, options: [
|
m.defaultTrackMenu(t, details: details, options: [
|
||||||
if (playlist!.user!.id == deezerAPI.userId)
|
if (playlist!.user!.id == DeezerAPI.instance.userId)
|
||||||
m.removeFromPlaylist(t, playlist)
|
m.removeFromPlaylist(t, playlist)
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -1150,8 +1153,8 @@ class _MakePlaylistOfflineState extends State<MakePlaylistOffline> {
|
||||||
if (v) {
|
if (v) {
|
||||||
//Add to offline
|
//Add to offline
|
||||||
if (widget.playlist!.user != null &&
|
if (widget.playlist!.user != null &&
|
||||||
widget.playlist!.user!.id != deezerAPI.userId) {
|
widget.playlist!.user!.id != DeezerAPI.instance.userId) {
|
||||||
await deezerAPI.addPlaylist(widget.playlist!.id);
|
await DeezerAPI.instance.addPlaylist(widget.playlist!.id);
|
||||||
}
|
}
|
||||||
downloadManager.addOfflinePlaylist(widget.playlist,
|
downloadManager.addOfflinePlaylist(widget.playlist,
|
||||||
private: true);
|
private: true);
|
||||||
|
@ -1197,7 +1200,7 @@ class _ShowScreenState extends State<ShowScreen> {
|
||||||
//Fetch
|
//Fetch
|
||||||
List<ShowEpisode>? e;
|
List<ShowEpisode>? e;
|
||||||
try {
|
try {
|
||||||
e = await deezerAPI.allShowEpisodes(_show!.id);
|
e = await DeezerAPI.instance.allShowEpisodes(_show!.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_loading = false;
|
_loading = false;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cookie_jar/cookie_jar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
class ExternalLinkRoute extends StatefulWidget {
|
class ExternalLinkRoute extends StatefulWidget {
|
||||||
|
@ -45,7 +46,8 @@ class _ExternalLinkRouteState extends State<ExternalLinkRoute> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, String>> _resolveHeaders(Uri uri) async {
|
Future<Map<String, String>> _resolveHeaders(Uri uri) async {
|
||||||
List<Cookie> cookies = await cookieJar.loadForRequest(uri);
|
List<Cookie> cookies =
|
||||||
|
await GetIt.instance<CookieJar>().loadForRequest(uri);
|
||||||
print(cookies);
|
print(cookies);
|
||||||
return {'Cookie': cookies.join(';')};
|
return {'Cookie': cookies.join(';')};
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,9 +148,9 @@ class _HomePageWidgetState extends State<HomePageWidget> {
|
||||||
//Fetch channel from api
|
//Fetch channel from api
|
||||||
try {
|
try {
|
||||||
if (widget.channel == null) {
|
if (widget.channel == null) {
|
||||||
homePage = await deezerAPI.homePage();
|
homePage = await DeezerAPI.instance.homePage();
|
||||||
} else {
|
} else {
|
||||||
homePage = await deezerAPI.getChannel(widget.channel!.target);
|
homePage = await DeezerAPI.instance.getChannel(widget.channel!.target);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
homePage = null;
|
homePage = null;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:freezer/ui/error.dart';
|
||||||
import 'package:freezer/ui/importer_screen.dart';
|
import 'package:freezer/ui/importer_screen.dart';
|
||||||
import 'package:freezer/ui/tiles.dart';
|
import 'package:freezer/ui/tiles.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
import 'menu.dart';
|
import 'menu.dart';
|
||||||
import '../api/download.dart';
|
import '../api/download.dart';
|
||||||
|
@ -241,6 +242,8 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
int? trackCount;
|
int? trackCount;
|
||||||
Sorting? _sort = Sorting(sourceType: SortSourceTypes.TRACKS);
|
Sorting? _sort = Sorting(sourceType: SortSourceTypes.TRACKS);
|
||||||
|
|
||||||
|
final deezerAPI = DeezerAPI.instance;
|
||||||
|
|
||||||
Playlist get _playlist => Playlist(id: deezerAPI.favoritesPlaylistId!);
|
Playlist get _playlist => Playlist(id: deezerAPI.favoritesPlaylistId!);
|
||||||
|
|
||||||
List<Track> get _sorted {
|
List<Track> get _sorted {
|
||||||
|
@ -595,7 +598,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
||||||
Future _load() async {
|
Future _load() async {
|
||||||
if (settings.offlineMode) return;
|
if (settings.offlineMode) return;
|
||||||
try {
|
try {
|
||||||
List<Album> albums = await deezerAPI.getAlbums();
|
List<Album> albums = await DeezerAPI.instance.getAlbums();
|
||||||
setState(() => _albums = albums);
|
setState(() => _albums = albums);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
@ -799,7 +802,7 @@ class _LibraryArtistsState extends State<LibraryArtists> {
|
||||||
//Fetch
|
//Fetch
|
||||||
List<Artist>? data;
|
List<Artist>? data;
|
||||||
try {
|
try {
|
||||||
data = await deezerAPI.getArtists();
|
data = await DeezerAPI.instance.getArtists();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
//Update UI
|
//Update UI
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -929,6 +932,8 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
String _filter = '';
|
String _filter = '';
|
||||||
|
|
||||||
|
final deezerAPI = DeezerAPI.instance;
|
||||||
|
|
||||||
List<Playlist> get _sorted {
|
List<Playlist> get _sorted {
|
||||||
List<Playlist> playlists = List.from(_playlists!
|
List<Playlist> playlists = List.from(_playlists!
|
||||||
.where((p) => p.title!.toLowerCase().contains(_filter.toLowerCase())));
|
.where((p) => p.title!.toLowerCase().contains(_filter.toLowerCase())));
|
||||||
|
|
|
@ -31,6 +31,7 @@ class LoginWidget extends StatefulWidget {
|
||||||
class _LoginWidgetState extends State<LoginWidget> {
|
class _LoginWidgetState extends State<LoginWidget> {
|
||||||
late String _arl;
|
late String _arl;
|
||||||
String? _error;
|
String? _error;
|
||||||
|
final deezerAPI = DeezerAPI.instance;
|
||||||
|
|
||||||
//Initialize deezer etc
|
//Initialize deezer etc
|
||||||
Future _init() async {
|
Future _init() async {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/pipe_api.dart';
|
import 'package:freezer/api/pipe_api.dart';
|
||||||
import 'package:freezer/api/player/audio_handler.dart';
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
|
@ -38,7 +38,7 @@ class LyricsScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LyricsWidget extends StatefulWidget {
|
class LyricsWidget extends StatefulWidget {
|
||||||
const LyricsWidget({Key? key}) : super(key: key);
|
const LyricsWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LyricsWidget> createState() => _LyricsWidgetState();
|
State<LyricsWidget> createState() => _LyricsWidgetState();
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/main.dart';
|
import 'package:freezer/main.dart';
|
||||||
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:freezer/api/cache.dart';
|
import 'package:freezer/api/cache.dart';
|
||||||
|
@ -15,6 +17,7 @@ import 'package:freezer/ui/cached_image.dart';
|
||||||
import 'package:numberpicker/numberpicker.dart';
|
import 'package:numberpicker/numberpicker.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:freezer/api/download_manager/download_manager.dart' as newDl;
|
||||||
|
|
||||||
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
|
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
|
||||||
final Track track;
|
final Track track;
|
||||||
|
@ -267,21 +270,21 @@ class MenuSheet {
|
||||||
MenuSheetOption(Text('Play next'.i18n),
|
MenuSheetOption(Text('Play next'.i18n),
|
||||||
icon: const Icon(Icons.playlist_play), onTap: () async {
|
icon: const Icon(Icons.playlist_play), onTap: () async {
|
||||||
//-1 = next
|
//-1 = next
|
||||||
await audioHandler.insertQueueItem(-1, await t.toMediaItem());
|
await audioHandler.insertQueueItem(-1, t.toMediaItem());
|
||||||
});
|
});
|
||||||
|
|
||||||
MenuSheetOption addToQueue(Track t) =>
|
MenuSheetOption addToQueue(Track t) =>
|
||||||
MenuSheetOption(Text('Add to queue'.i18n),
|
MenuSheetOption(Text('Add to queue'.i18n),
|
||||||
icon: const Icon(Icons.playlist_add), onTap: () async {
|
icon: const Icon(Icons.playlist_add), onTap: () async {
|
||||||
await audioHandler.addQueueItem(await t.toMediaItem());
|
await audioHandler.addQueueItem(t.toMediaItem());
|
||||||
});
|
});
|
||||||
|
|
||||||
MenuSheetOption addTrackFavorite(Track t) =>
|
MenuSheetOption addTrackFavorite(Track t) =>
|
||||||
MenuSheetOption(Text('Add track to favorites'.i18n),
|
MenuSheetOption(Text('Add track to favorites'.i18n),
|
||||||
icon: const Icon(Icons.favorite), onTap: () async {
|
icon: const Icon(Icons.favorite), onTap: () async {
|
||||||
await deezerAPI.addFavoriteTrack(t.id);
|
await DeezerAPI.instance.addFavoriteTrack(t.id);
|
||||||
//Make track offline, if favorites are offline
|
//Make track offline, if favorites are offline
|
||||||
Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!);
|
Playlist p = Playlist(id: DeezerAPI.instance.favoritesPlaylistId!);
|
||||||
if (await downloadManager.checkOffline(playlist: p)) {
|
if (await downloadManager.checkOffline(playlist: p)) {
|
||||||
downloadManager.addOfflinePlaylist(p);
|
downloadManager.addOfflinePlaylist(p);
|
||||||
}
|
}
|
||||||
|
@ -294,9 +297,12 @@ class MenuSheet {
|
||||||
Text('Download'.i18n),
|
Text('Download'.i18n),
|
||||||
icon: const Icon(Icons.file_download),
|
icon: const Icon(Icons.file_download),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (await downloadManager.addOfflineTrack(t,
|
final dl = newDl.DownloadManager();
|
||||||
private: false, context: context, isSingleton: true) !=
|
await dl.startDebug();
|
||||||
false) showDownloadStartedToast();
|
dl.addOfflineTrack(t, settings.downloadQuality, private: false);
|
||||||
|
//if (await downloadManager.addOfflineTrack(t,
|
||||||
|
// private: false, context: context, isSingleton: true) !=
|
||||||
|
// false) showDownloadStartedToast();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -311,7 +317,7 @@ class MenuSheet {
|
||||||
return SelectPlaylistDialog(
|
return SelectPlaylistDialog(
|
||||||
track: t,
|
track: t,
|
||||||
callback: (Playlist p) async {
|
callback: (Playlist p) async {
|
||||||
await deezerAPI.addToPlaylist(t.id, p.id);
|
await DeezerAPI.instance.addToPlaylist(t.id, p.id);
|
||||||
//Update the playlist if offline
|
//Update the playlist if offline
|
||||||
if (await downloadManager.checkOffline(playlist: p)) {
|
if (await downloadManager.checkOffline(playlist: p)) {
|
||||||
downloadManager.addOfflinePlaylist(p);
|
downloadManager.addOfflinePlaylist(p);
|
||||||
|
@ -327,7 +333,7 @@ class MenuSheet {
|
||||||
Text('Remove from playlist'.i18n),
|
Text('Remove from playlist'.i18n),
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeFromPlaylist(t.id, p!.id);
|
await DeezerAPI.instance.removeFromPlaylist(t.id, p!.id);
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.snack('${'Track removed from'.i18n} ${p.title}');
|
.snack('${'Track removed from'.i18n} ${p.title}');
|
||||||
},
|
},
|
||||||
|
@ -337,9 +343,9 @@ class MenuSheet {
|
||||||
Text('Remove favorite'.i18n),
|
Text('Remove favorite'.i18n),
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeFavorite(t.id);
|
await DeezerAPI.instance.removeFavorite(t.id);
|
||||||
//Check if favorites playlist is offline, update it
|
//Check if favorites playlist is offline, update it
|
||||||
Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!);
|
Playlist p = Playlist(id: DeezerAPI.instance.favoritesPlaylistId!);
|
||||||
if (await downloadManager.checkOffline(playlist: p)) {
|
if (await downloadManager.checkOffline(playlist: p)) {
|
||||||
await downloadManager.addOfflinePlaylist(p);
|
await downloadManager.addOfflinePlaylist(p);
|
||||||
}
|
}
|
||||||
|
@ -454,7 +460,7 @@ class MenuSheet {
|
||||||
Text('Make offline'.i18n),
|
Text('Make offline'.i18n),
|
||||||
icon: const Icon(Icons.offline_pin),
|
icon: const Icon(Icons.offline_pin),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addFavoriteAlbum(a.id);
|
await DeezerAPI.instance.addFavoriteAlbum(a.id);
|
||||||
await downloadManager.addOfflineAlbum(a, private: true);
|
await downloadManager.addOfflineAlbum(a, private: true);
|
||||||
|
|
||||||
showDownloadStartedToast();
|
showDownloadStartedToast();
|
||||||
|
@ -465,7 +471,7 @@ class MenuSheet {
|
||||||
Text('Add to library'.i18n),
|
Text('Add to library'.i18n),
|
||||||
icon: const Icon(Icons.library_music),
|
icon: const Icon(Icons.library_music),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addFavoriteAlbum(a.id);
|
await DeezerAPI.instance.addFavoriteAlbum(a.id);
|
||||||
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
|
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -475,7 +481,7 @@ class MenuSheet {
|
||||||
Text('Remove album'.i18n),
|
Text('Remove album'.i18n),
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeAlbum(a.id);
|
await DeezerAPI.instance.removeAlbum(a.id);
|
||||||
await downloadManager.removeOfflineAlbum(a.id);
|
await downloadManager.removeOfflineAlbum(a.id);
|
||||||
ScaffoldMessenger.of(context).snack('Album removed'.i18n);
|
ScaffoldMessenger.of(context).snack('Album removed'.i18n);
|
||||||
if (onRemove != null) onRemove();
|
if (onRemove != null) onRemove();
|
||||||
|
@ -508,7 +514,7 @@ class MenuSheet {
|
||||||
Text('Remove from favorites'.i18n),
|
Text('Remove from favorites'.i18n),
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeArtist(a.id);
|
await DeezerAPI.instance.removeArtist(a.id);
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.snack('Artist removed from library'.i18n);
|
.snack('Artist removed from library'.i18n);
|
||||||
if (onRemove != null) onRemove();
|
if (onRemove != null) onRemove();
|
||||||
|
@ -519,7 +525,7 @@ class MenuSheet {
|
||||||
Text('Add to favorites'.i18n),
|
Text('Add to favorites'.i18n),
|
||||||
icon: const Icon(Icons.favorite),
|
icon: const Icon(Icons.favorite),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addFavoriteArtist(a.id);
|
await DeezerAPI.instance.addFavoriteArtist(a.id);
|
||||||
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
|
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -541,7 +547,7 @@ class MenuSheet {
|
||||||
addPlaylistOffline(playlist),
|
addPlaylistOffline(playlist),
|
||||||
downloadPlaylist(playlist),
|
downloadPlaylist(playlist),
|
||||||
shareTile('playlist', playlist.id),
|
shareTile('playlist', playlist.id),
|
||||||
if (playlist.user!.id == deezerAPI.userId)
|
if (playlist.user!.id == DeezerAPI.instance.userId)
|
||||||
editPlaylist(playlist, onUpdate: onUpdate),
|
editPlaylist(playlist, onUpdate: onUpdate),
|
||||||
...options
|
...options
|
||||||
]);
|
]);
|
||||||
|
@ -556,12 +562,12 @@ class MenuSheet {
|
||||||
Text('Remove from library'.i18n),
|
Text('Remove from library'.i18n),
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (p.user!.id!.trim() == deezerAPI.userId) {
|
if (p.user!.id!.trim() == DeezerAPI.instance.userId) {
|
||||||
//Delete playlist if own
|
//Delete playlist if own
|
||||||
await deezerAPI.deletePlaylist(p.id);
|
await DeezerAPI.instance.deletePlaylist(p.id);
|
||||||
} else {
|
} else {
|
||||||
//Just remove from library
|
//Just remove from library
|
||||||
await deezerAPI.removePlaylist(p.id);
|
await DeezerAPI.instance.removePlaylist(p.id);
|
||||||
}
|
}
|
||||||
downloadManager.removeOfflinePlaylist(p.id);
|
downloadManager.removeOfflinePlaylist(p.id);
|
||||||
if (onRemove != null) onRemove();
|
if (onRemove != null) onRemove();
|
||||||
|
@ -572,7 +578,7 @@ class MenuSheet {
|
||||||
Text('Add playlist to library'.i18n),
|
Text('Add playlist to library'.i18n),
|
||||||
icon: const Icon(Icons.favorite),
|
icon: const Icon(Icons.favorite),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addPlaylist(p.id);
|
await DeezerAPI.instance.addPlaylist(p.id);
|
||||||
ScaffoldMessenger.of(context).snack('Added playlist to library'.i18n);
|
ScaffoldMessenger.of(context).snack('Added playlist to library'.i18n);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -582,7 +588,7 @@ class MenuSheet {
|
||||||
icon: const Icon(Icons.offline_pin),
|
icon: const Icon(Icons.offline_pin),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
//Add to library
|
//Add to library
|
||||||
await deezerAPI.addPlaylist(p.id);
|
await DeezerAPI.instance.addPlaylist(p.id);
|
||||||
downloadManager.addOfflinePlaylist(p, private: true);
|
downloadManager.addOfflinePlaylist(p, private: true);
|
||||||
|
|
||||||
showDownloadStartedToast();
|
showDownloadStartedToast();
|
||||||
|
@ -825,7 +831,7 @@ class _SelectPlaylistDialogState extends State<SelectPlaylistDialog> {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Select playlist'.i18n),
|
title: Text('Select playlist'.i18n),
|
||||||
content: FutureBuilder(
|
content: FutureBuilder(
|
||||||
future: deezerAPI.getPlaylists(),
|
future: DeezerAPI.instance.getPlaylists(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -957,7 +963,7 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (edit) {
|
if (edit) {
|
||||||
//Update
|
//Update
|
||||||
await deezerAPI.updatePlaylist(widget.playlist!.id,
|
await DeezerAPI.instance.updatePlaylist(widget.playlist!.id,
|
||||||
_titleController!.value.text, _descController!.value.text,
|
_titleController!.value.text, _descController!.value.text,
|
||||||
status: _playlistType);
|
status: _playlistType);
|
||||||
ScaffoldMessenger.of(context).snack('Playlist updated!'.i18n);
|
ScaffoldMessenger.of(context).snack('Playlist updated!'.i18n);
|
||||||
|
@ -966,7 +972,7 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
||||||
if (widget.tracks != null) {
|
if (widget.tracks != null) {
|
||||||
tracks = widget.tracks!.map<String>((t) => t!.id).toList();
|
tracks = widget.tracks!.map<String>((t) => t!.id).toList();
|
||||||
}
|
}
|
||||||
await deezerAPI.createPlaylist(_title,
|
await DeezerAPI.instance.createPlaylist(_title,
|
||||||
status: _playlistType,
|
status: _playlistType,
|
||||||
description: _description,
|
description: _description,
|
||||||
trackIds: tracks);
|
trackIds: tracks);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
@ -15,12 +16,12 @@ class PlayerBar extends StatelessWidget {
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
const PlayerBar({
|
const PlayerBar({
|
||||||
Key? key,
|
super.key,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.shouldHaveHero = true,
|
this.shouldHaveHero = true,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
final double iconSize = 28;
|
final double iconSize = 28;
|
||||||
|
|
||||||
|
@ -193,12 +194,12 @@ class PlayPauseButton extends StatefulWidget {
|
||||||
final Color? color;
|
final Color? color;
|
||||||
const PlayPauseButton(
|
const PlayPauseButton(
|
||||||
this.size, {
|
this.size, {
|
||||||
Key? key,
|
super.key,
|
||||||
this.filled = false,
|
this.filled = false,
|
||||||
this.material3 = true,
|
this.material3 = true,
|
||||||
this.color,
|
this.color,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PlayPauseButton> createState() => _PlayPauseButtonState();
|
State<PlayPauseButton> createState() => _PlayPauseButtonState();
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:freezer/api/cache.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/player/audio_handler.dart';
|
import 'package:freezer/api/player/audio_handler.dart';
|
||||||
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/main.dart';
|
import 'package:freezer/main.dart';
|
||||||
import 'package:freezer/page_routes/fade.dart';
|
import 'package:freezer/page_routes/fade.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
|
@ -750,12 +751,12 @@ class _FavoriteButtonState extends State<FavoriteButton> {
|
||||||
if (cache.checkTrackFavorite(Track.fromMediaItem(mediaItem))) {
|
if (cache.checkTrackFavorite(Track.fromMediaItem(mediaItem))) {
|
||||||
//Remove from library
|
//Remove from library
|
||||||
setState(() => cache.libraryTracks.remove(mediaItem.id));
|
setState(() => cache.libraryTracks.remove(mediaItem.id));
|
||||||
await deezerAPI.removeFavorite(mediaItem.id);
|
await DeezerAPI.instance.removeFavorite(mediaItem.id);
|
||||||
await cache.save();
|
await cache.save();
|
||||||
} else {
|
} else {
|
||||||
//Add
|
//Add
|
||||||
setState(() => cache.libraryTracks.add(mediaItem.id));
|
setState(() => cache.libraryTracks.add(mediaItem.id));
|
||||||
await deezerAPI.addFavoriteTrack(mediaItem.id);
|
await DeezerAPI.instance.addFavoriteTrack(mediaItem.id);
|
||||||
await cache.save();
|
await cache.save();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1266,8 +1267,8 @@ class BottomBarControls extends StatelessWidget {
|
||||||
),
|
),
|
||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
unawaited(
|
unawaited(DeezerAPI.instance
|
||||||
deezerAPI.dislikeTrack(audioHandler.mediaItem.value!.id));
|
.dislikeTrack(audioHandler.mediaItem.value!.id));
|
||||||
if (playerHelper.queueIndex <
|
if (playerHelper.queueIndex <
|
||||||
audioHandler.queue.value.length - 1) {
|
audioHandler.queue.value.length - 1) {
|
||||||
audioHandler.skipToNext();
|
audioHandler.skipToNext();
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/player/audio_handler.dart';
|
import 'package:freezer/api/player/audio_handler.dart';
|
||||||
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:freezer/ui/menu.dart';
|
import 'package:freezer/ui/menu.dart';
|
||||||
import 'package:freezer/ui/tiles.dart';
|
import 'package:freezer/ui/tiles.dart';
|
||||||
|
|
|
@ -22,6 +22,7 @@ import '../api/definitions.dart';
|
||||||
import 'error.dart';
|
import 'error.dart';
|
||||||
|
|
||||||
FutureOr openScreenByURL(BuildContext context, String url) async {
|
FutureOr openScreenByURL(BuildContext context, String url) async {
|
||||||
|
final deezerAPI = DeezerAPI.instance;
|
||||||
DeezerLinkResponse? res = await deezerAPI.parseLink(Uri.parse(url));
|
DeezerLinkResponse? res = await deezerAPI.parseLink(Uri.parse(url));
|
||||||
if (res == null) return;
|
if (res == null) return;
|
||||||
|
|
||||||
|
@ -138,8 +139,8 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||||
final List<String>? suggestions;
|
final List<String>? suggestions;
|
||||||
try {
|
try {
|
||||||
_searchCancelToken = CancelToken();
|
_searchCancelToken = CancelToken();
|
||||||
suggestions = await deezerAPI.searchSuggestions(_controller.text,
|
suggestions = await DeezerAPI.instance
|
||||||
cancelToken: _searchCancelToken);
|
.searchSuggestions(_controller.text, cancelToken: _searchCancelToken);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.type != DioExceptionType.cancel) rethrow;
|
if (e.type != DioExceptionType.cancel) rethrow;
|
||||||
return;
|
return;
|
||||||
|
@ -456,7 +457,7 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
|
||||||
if (widget.offline ?? false) {
|
if (widget.offline ?? false) {
|
||||||
results = await downloadManager.search(widget.query);
|
results = await downloadManager.search(widget.query);
|
||||||
} else {
|
} else {
|
||||||
results = await deezerAPI.search(widget.query);
|
results = await DeezerAPI.instance.search(widget.query);
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_results = results;
|
_results = results;
|
||||||
|
@ -896,8 +897,10 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
//Load entire show, then play
|
//Load entire show, then play
|
||||||
List<ShowEpisode> episodes =
|
List<ShowEpisode> episodes =
|
||||||
(await deezerAPI.allShowEpisodes(
|
(await DeezerAPI
|
||||||
episode.show!.id))!;
|
.instance
|
||||||
|
.allShowEpisodes(
|
||||||
|
episode.show!.id))!;
|
||||||
await playerHelper.playShowEpisode(
|
await playerHelper.playShowEpisode(
|
||||||
episode.show!, episodes,
|
episode.show!, episodes,
|
||||||
index: episodes.indexWhere(
|
index: episodes.indexWhere(
|
||||||
|
@ -1051,7 +1054,7 @@ class EpisodeListScreen extends StatelessWidget {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
//Load entire show, then play
|
//Load entire show, then play
|
||||||
List<ShowEpisode> episodes =
|
List<ShowEpisode> episodes =
|
||||||
(await deezerAPI.allShowEpisodes(e.show!.id))!;
|
(await DeezerAPI.instance.allShowEpisodes(e.show!.id))!;
|
||||||
await playerHelper.playShowEpisode(e.show!, episodes,
|
await playerHelper.playShowEpisode(e.show!, episodes,
|
||||||
index: episodes.indexWhere((ep) => e.id == ep.id));
|
index: episodes.indexWhere((ep) => e.id == ep.id));
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/api/player/systray.dart';
|
import 'package:freezer/api/player/systray.dart';
|
||||||
import 'package:freezer/icons.dart';
|
import 'package:freezer/icons.dart';
|
||||||
import 'package:freezer/ui/login_on_other_device.dart';
|
import 'package:freezer/ui/login_on_other_device.dart';
|
||||||
|
@ -876,10 +877,13 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
||||||
title: Text(ContentLanguage.all[i].name),
|
title: Text(ContentLanguage.all[i].name),
|
||||||
subtitle: Text(ContentLanguage.all[i].code),
|
subtitle: Text(ContentLanguage.all[i].code),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
setState(() => settings.deezerLanguage =
|
settings.deezerLanguage =
|
||||||
ContentLanguage.all[i].code);
|
ContentLanguage.all[i].code;
|
||||||
|
setState(() {});
|
||||||
await settings.save();
|
await settings.save();
|
||||||
deezerAPI.updateHeaders();
|
DeezerAPI.instance.deezerLanguage =
|
||||||
|
settings.deezerLanguage;
|
||||||
|
DeezerAPI.instance.updateHeaders();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
@ -898,9 +902,10 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
||||||
titlePadding: const EdgeInsets.all(8.0),
|
titlePadding: const EdgeInsets.all(8.0),
|
||||||
isSearchable: true,
|
isSearchable: true,
|
||||||
onValuePicked: (Country country) {
|
onValuePicked: (Country country) {
|
||||||
setState(
|
DeezerAPI.instance.deezerCountry =
|
||||||
() => settings.deezerCountry = country.isoCode);
|
settings.deezerCountry = country.isoCode;
|
||||||
deezerAPI.updateHeaders();
|
setState(() {});
|
||||||
|
DeezerAPI.instance.updateHeaders();
|
||||||
settings.save();
|
settings.save();
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -1390,7 +1395,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
deezerAPI.authorize().then((v) {
|
DeezerAPI.instance.authorize().then((v) {
|
||||||
if (v) {
|
if (v) {
|
||||||
setState(() => settings.offlineMode = false);
|
setState(() => settings.offlineMode = false);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/download.dart';
|
import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/api/player/audio_handler.dart';
|
import 'package:freezer/api/player/audio_handler.dart';
|
||||||
|
import 'package:freezer/api/player/player_helper.dart';
|
||||||
import 'package:freezer/icons.dart';
|
import 'package:freezer/icons.dart';
|
||||||
import 'package:freezer/main.dart';
|
import 'package:freezer/main.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
@ -246,7 +247,7 @@ class PlaylistTile extends StatelessWidget {
|
||||||
if (playlist!.user == null ||
|
if (playlist!.user == null ||
|
||||||
playlist!.user!.name == null ||
|
playlist!.user!.name == null ||
|
||||||
playlist!.user!.name == '' ||
|
playlist!.user!.name == '' ||
|
||||||
playlist!.user!.id == deezerAPI.userId) {
|
playlist!.user!.id == DeezerAPI.instance.userId) {
|
||||||
if (playlist!.trackCount == null) return '';
|
if (playlist!.trackCount == null) return '';
|
||||||
return '${playlist!.trackCount} ${'Tracks'.i18n}';
|
return '${playlist!.trackCount} ${'Tracks'.i18n}';
|
||||||
}
|
}
|
||||||
|
@ -341,8 +342,9 @@ class PlaylistCardTile extends StatelessWidget {
|
||||||
left: 8.0,
|
left: 8.0,
|
||||||
child: PlayItemButton(
|
child: PlayItemButton(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final Playlist fullPlaylist =
|
final Playlist fullPlaylist = await DeezerAPI
|
||||||
await deezerAPI.fullPlaylist(playlist!.id);
|
.instance
|
||||||
|
.fullPlaylist(playlist!.id);
|
||||||
await playerHelper.playFromPlaylist(fullPlaylist);
|
await playerHelper.playFromPlaylist(fullPlaylist);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
@ -639,7 +641,8 @@ class AlbumCard extends StatelessWidget {
|
||||||
left: 8.0,
|
left: 8.0,
|
||||||
child: PlayItemButton(
|
child: PlayItemButton(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final fullAlbum = await deezerAPI.album(album.id);
|
final fullAlbum =
|
||||||
|
await DeezerAPI.instance.album(album.id);
|
||||||
await playerHelper.playFromAlbum(fullAlbum);
|
await playerHelper.playFromAlbum(fullAlbum);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -27,4 +27,21 @@ class Utils {
|
||||||
}
|
}
|
||||||
return bi;
|
return bi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FOR ISAR, FROM ISAR DOCUMENTATION
|
||||||
|
/// FNV-1a 64bit hash algorithm optimized for Dart Strings
|
||||||
|
static int fastHash(String string) {
|
||||||
|
var hash = 0xcbf29ce484222325;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < string.length) {
|
||||||
|
final codeUnit = string.codeUnitAt(i++);
|
||||||
|
hash ^= codeUnit >> 8;
|
||||||
|
hash *= 0x100000001b3;
|
||||||
|
hash ^= codeUnit & 0xFF;
|
||||||
|
hash *= 0x100000001b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
26
pubspec.lock
26
pubspec.lock
|
@ -567,6 +567,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.4"
|
version: "8.2.4"
|
||||||
|
freezed:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: freezed
|
||||||
|
sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.7"
|
||||||
|
freezed_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: freezed_annotation
|
||||||
|
sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -575,6 +591,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.0"
|
||||||
|
get_it:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: get_it
|
||||||
|
sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.6.7"
|
||||||
gettext_parser:
|
gettext_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -789,7 +813,7 @@ packages:
|
||||||
path: "../just_audio_media_kit"
|
path: "../just_audio_media_kit"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "2.0.1"
|
version: "2.0.0"
|
||||||
just_audio_platform_interface:
|
just_audio_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -107,6 +107,8 @@ dependencies:
|
||||||
tray_manager: ^0.2.1
|
tray_manager: ^0.2.1
|
||||||
window_manager:
|
window_manager:
|
||||||
^0.3.8
|
^0.3.8
|
||||||
|
get_it: ^7.6.7
|
||||||
|
freezed_annotation: ^2.4.1
|
||||||
#deezcryptor:
|
#deezcryptor:
|
||||||
#path: deezcryptor/
|
#path: deezcryptor/
|
||||||
|
|
||||||
|
@ -119,6 +121,7 @@ dev_dependencies:
|
||||||
hive_generator: ^2.0.0
|
hive_generator: ^2.0.0
|
||||||
flutter_lints: ^3.0.1
|
flutter_lints: ^3.0.1
|
||||||
isar_generator: ^3.1.0+1
|
isar_generator: ^3.1.0+1
|
||||||
|
freezed: ^2.4.7
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
Loading…
Reference in New Issue