use get_it + work on the new download backend + migrate to isar for cookie store

This commit is contained in:
Pato05 2024-03-01 19:22:57 +01:00
parent 475787f433
commit f88e43cad9
No known key found for this signature in database
GPG Key ID: F53CA394104BA0CB
34 changed files with 483 additions and 204 deletions

1
.gitignore vendored
View File

@ -52,6 +52,7 @@ android/app/.cxx
/build/
.gradle/
*.g.dart
*.freezed.dart
# Web related
lib/generated_plugin_registrant.dart

View File

@ -45,7 +45,7 @@ class DeezerAudioSource extends StreamAudioSource {
int? trackTokenExpiration,
this.onStreamObtained,
}) : _deezerAudio = DeezerAudio(
deezerAPI: deezerAPI,
deezerAPI: DeezerAPI.instance,
quality: getQuality.call(),
trackId: trackId,
) {
@ -78,7 +78,7 @@ class DeezerAudioSource extends StreamAudioSource {
}
_logger.fine("authorizing...");
if (!await deezerAPI.authorize()) {
if (!await DeezerAPI.instance.authorize()) {
_logger.severe("authorization failed! cannot continue!");
throw Exception("Authorization failed!");
}
@ -96,7 +96,7 @@ class DeezerAudioSource extends StreamAudioSource {
if (_downloadUrl == null) {
if (_trackToken == null) {
// TODO: get new track token?
final track = await deezerAPI.track(trackId);
final track = await DeezerAPI.instance.track(trackId);
_trackToken = track.trackToken;
_trackTokenExpiration = track.trackTokenExpiration;
_mediaVersion = track.playbackDetails![1];

View File

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

View File

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

View File

@ -8,17 +8,17 @@ import 'package:freezer/api/cache.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/spotify.dart';
import 'package:freezer/settings.dart';
import 'package:get_it/get_it.dart';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'cookie_jar_hive_storage.dart';
import 'dart:convert';
import 'dart:async';
final deezerAPI = DeezerAPI();
final cookieJar = PersistCookieJar(storage: HiveStorage('cookies'));
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
static const CLIENT_ID = "172365";
static const CLIENT_SECRET = "fb0bec7ccc063dab0417eb7b0d847f34";
@ -40,7 +40,9 @@ class DeezerAPI {
static String get userAgent => USER_AGENTS[defaultTargetPlatform]!;
static final _logger = Logger('DeezerAPI');
DeezerAPI();
final CookieJar cookieJar;
DeezerAPI(this.cookieJar);
set arl(String? arl) {
if (arl == null) {
@ -62,6 +64,9 @@ class DeezerAPI {
String? favoritesPlaylistId;
String? sid;
late String deezerLanguage;
late String deezerCountry;
late String licenseToken;
late bool canStreamLossless;
late bool canStreamHQ;
@ -77,13 +82,12 @@ class DeezerAPI {
//Get headers
Map<String, String> get headers => {
"User-Agent": userAgent,
"Content-Language":
'${settings.deezerLanguage}-${settings.deezerCountry}',
"Content-Language": '$deezerLanguage-$deezerCountry',
"Cache-Control": "max-age=0",
"Accept": "*/*",
"Accept-Charset": "utf-8,ISO-8859-1;q=0.7,*;q=0.3",
"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",
};
@ -292,6 +296,9 @@ class DeezerAPI {
}
}
/// Get single track url
///
/// Shorcut for [getTracksUrl([trackToken], format)[0]]
Future<GetTrackUrlResponse> getTrackUrl(
String trackToken, String format) async =>
(await getTracksUrl([trackToken], format))[0];
@ -306,7 +313,11 @@ class DeezerAPI {
{
"type": "FULL",
"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,
'show_id': showId,
'start': 0,
'user_id': int.parse(deezerAPI.userId!)
'user_id': int.parse(userId!)
});
return data['results']['EPISODES']['data']
.map<ShowEpisode>((e) => ShowEpisode.fromPrivateJson(e))

View File

@ -174,7 +174,7 @@ class DownloadManager {
if (track.artists == null ||
track.artists!.isEmpty ||
track.album == null) {
track = await deezerAPI.track(track.id);
track = await DeezerAPI.instance.track(track.id);
}
//Add to DB
@ -212,7 +212,7 @@ class DownloadManager {
//Get from API if no tracks
if (album!.tracks == null || album.tracks!.isEmpty) {
album = await deezerAPI.album(album.id);
album = await DeezerAPI.instance.album(album.id);
}
//Add to DB
@ -258,7 +258,7 @@ class DownloadManager {
//Get tracks if missing
if (playlist!.tracks == null ||
playlist.tracks!.length < playlist.trackCount!) {
playlist = await deezerAPI.fullPlaylist(playlist.id);
playlist = await DeezerAPI.instance.fullPlaylist(playlist.id);
}
//Add to DB
@ -643,6 +643,7 @@ class DownloadManager {
//Check storage permission
static Future<bool> checkPermission() async {
if (Platform.isLinux || Platform.isWindows) return true;
if (await Permission.storage.request().isGranted) {
return true;
} else if ( // android 12 or later
@ -748,7 +749,7 @@ class Download {
{private = true, AudioQuality? quality}) async {
//Get download info
if (t.playbackDetails == null || t.playbackDetails == []) {
t = await deezerAPI.track(t.id);
t = await DeezerAPI.instance.track(t.id);
}
return {
"private": private,

View File

@ -21,6 +21,7 @@ class Track {
late final bool favorite;
late final int? diskNumber;
late final bool explicit;
late final String localPath;
Track();

View File

@ -1,7 +1,8 @@
import 'dart:async';
import 'dart:io';
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:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart' as d;
@ -20,8 +21,19 @@ class DownloadManager {
late Isar _isar;
SendPort? _sendPort;
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 {
_isar = await Isar.open(
@ -56,13 +68,28 @@ class DownloadManager {
Future<bool> startService() async {
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();
_sendPort = receivePort.sendPort;
_isolate = await Isolate.spawn(
_startService, ServiceInterface(receivePort: receivePort));
final completer = Completer<void>();
on('ready', (args) => completer.complete());
await completer.future;
invoke('updateSettings', {
'downloadFilename': settings.downloadFilename,
'deezerLanguage': settings.deezerLanguage,
'deezerCountry': settings.deezerCountry,
});
return true;
}
@ -72,34 +99,44 @@ class DownloadManager {
}
void invoke(String method, [Map<String, dynamic>? args]) {
if (_sendPort != null) {
_sendPort!.send({
'method': method,
if (args != null) ...args,
});
if (_service != null) {
_service!.send(method, args);
return;
}
FlutterBackgroundService().invoke(method, args);
}
Future<bool> addOfflineTrack(d.Track track,
{bool private = true, BuildContext? context, isSingleton = false}) async {
void on(String method, ListenerCallback listener) {
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
if (!private && !(await dl.DownloadManager.checkCanDownload())) {
return false;
}
//Ask for quality
//AudioQuality? quality;
if (!private && settings.downloadQuality == AudioQuality.ASK) {
// quality = await qualitySelect(context!);
// if (quality == null) return false;
if (downloadQuality == AudioQuality.ASK) {
throw Exception('Invalid quality.');
}
if (private) {
if (track.artists == null ||
track.artists!.isEmpty ||
track.album == null) {
track = await deezerAPI.track(track.id);
track = await DeezerAPI.instance.track(track.id);
}
// cache album art
@ -120,7 +157,12 @@ class DownloadManager {
}
// logic for downloading the track
invoke('addDownloads', {'track': track.toJson()});
invoke(
'addDownloads',
DownloadInfo(
trackId: track.id,
path: private ? null : settings.downloadPath,
).toJson());
return true;
}
@ -128,6 +170,14 @@ class DownloadManager {
static void _startNative(ServiceInstance 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) =>
DownloadService(service).run();
}

View File

@ -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/main.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 {
static const NOTIFICATION_ID = 6969;
@ -12,12 +24,18 @@ class DownloadService {
AudioQuality? _downloadQuality;
bool useGetURL = false;
void run() {
service.on('addDownloads').listen((event) {});
service.on('updateQuality').listen((event) {
late String downloadFilename;
late DeezerAPI _deezerAPI;
void run() async {
service.on('addDownloads', (event) {
downloadTrack(DownloadInfo.fromJson(event!));
});
service.on('updateQuality', (event) {
_preferredQuality = AudioQuality.values[event!['q']!];
});
service.on('updateCapabilities').listen((event) {
service.on('updateCapabilities', (event) {
final bool canStreamHQ = event!['canStreamHQ'];
final bool canStreamLossless = event['canStreamLossless'];
@ -26,12 +44,67 @@ class DownloadService {
_downloadQuality = settings.maxQualityFor(
_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) {
// final deezerAudio = DeezerAudio(deezerAPI: deezerAPI, md5origin: md5origin, quality: quality, trackId: trackId, mediaVersion: mediaVersion)
// if (useGetURL) {
// final url =
// }
void downloadTrack(DownloadInfo info) async {
final trackId = info.trackId;
final deezerAudio = DeezerAudio(
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);
}

View File

@ -1,21 +1,46 @@
import 'dart:collection';
import 'dart:isolate';
import 'package:flutter_background_service/flutter_background_service.dart';
typedef ListenerCallback = void Function(Map<String, dynamic>? args);
class ServiceInterface {
final ReceivePort? receivePort;
late SendPort sendPort;
final ServiceInstance? service;
bool _isListening = false;
final _listeners = HashMap<String, ListenerCallback>();
ServiceInterface({this.receivePort, this.service})
: assert(receivePort != null || service != null);
Stream<Map<String, dynamic>?> on(String method) {
void on(String method, ListenerCallback listener) {
if (service != null) {
return service!.on(method);
service!.on(method).listen(listener);
}
return receivePort!
.where((event) => event['method'] == method)
.map((event) => (event as Map?)?.cast<String, dynamic>());
if (!_isListening) {
_isListening = true;
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});
}
}

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/download.dart';
import 'package:get_it/get_it.dart';
Importer importer = Importer();
@ -29,6 +30,8 @@ class Importer {
int get error =>
tracks.fold(0, (v, t) => (t.state == TrackImportState.ERROR) ? v + 1 : v);
final deezerAPI = GetIt.instance<DeezerAPI>();
Importer();
//Start importing wrapper
@ -45,8 +48,8 @@ class Importer {
}).toList();
//Create playlist
playlistId =
await deezerAPI.createPlaylist(title, description: description);
playlistId = await DeezerAPI.instance
.createPlaylist(title, description: description);
busy = true;
done = false;

View File

@ -61,4 +61,12 @@ class Paths {
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;
}
}

View File

@ -16,7 +16,7 @@ class PipeAPI {
final _logger = Logger('PipeAPI');
Dio get dio => deezerAPI.dio;
Dio get dio => DeezerAPI.instance.dio;
Future<void> authorize({bool force = false}) async {
// authorize on pipe.deezer.com

View File

@ -27,7 +27,6 @@ import 'dart:async';
import 'dart:convert';
PlayerHelper playerHelper = PlayerHelper();
late AudioPlayerTask audioHandler;
bool failsafe = false;
class AudioPlayerTaskInitArguments {
@ -57,15 +56,6 @@ class AudioPlayerTaskInitArguments {
lastFMUsername: settings.lastFMUsername,
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 {
@ -145,11 +135,7 @@ class AudioPlayerTask extends BaseAudioHandler {
late final LazyBox _box;
AudioPlayerTask([AudioPlayerTaskInitArguments? initArgs]) {
if (initArgs == null) {
unawaited(AudioPlayerTaskInitArguments.loadSettings().then(_start));
return;
}
unawaited(_start(initArgs));
unawaited(_start(initArgs!));
}
Future<void> _start(AudioPlayerTaskInitArguments initArgs) async {

View File

@ -8,9 +8,12 @@ import 'package:freezer/api/player/audio_handler.dart';
import 'package:freezer/main.dart';
import 'package:freezer/settings.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
AudioPlayerTask get audioHandler => GetIt.instance<AudioPlayerTask>();
class PlayerHelper {
late StreamSubscription _customEventSubscription;
late StreamSubscription _mediaItemSubscription;
@ -66,9 +69,9 @@ class PlayerHelper {
failsafe = true;
final initArgs = AudioPlayerTaskInitArguments.from(
settings: settings, deezerAPI: deezerAPI);
settings: settings, deezerAPI: DeezerAPI.instance);
// initialize our audiohandler instance
audioHandler = await AudioService.init<AudioPlayerTask>(
final audioHandler = await AudioService.init<AudioPlayerTask>(
builder: () => AudioPlayerTask(initArgs),
config: AudioServiceConfig(
notificationColor: settings.primaryColor,
@ -87,6 +90,8 @@ class PlayerHelper {
),
cacheManager: cacheManager,
);
GetIt.instance.registerSingleton<AudioPlayerTask>(audioHandler);
}
Future<void> start() async {
@ -214,7 +219,7 @@ class PlayerHelper {
//Play mix by track
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(
tracks,
tracks[0].id,
@ -232,7 +237,8 @@ class PlayerHelper {
id: track.id,
text: 'Mix based on %s'.i18n.fill([track.title!]),
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)
if (tracks[0].id == track.id) tracks.removeAt(0);
await playFuture; // avoid race conditions
@ -243,7 +249,8 @@ class PlayerHelper {
}
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(
tracks,
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
if (stl.id! == 'flow') {
stl.tracks = await deezerAPI.flow(stl.flowConfig);
stl.tracks = await DeezerAPI.instance.flow(stl.flowConfig);
} else {
stl = await deezerAPI.smartTrackList(stl.id);
stl = await DeezerAPI.instance.smartTrackList(stl.id);
}
}
QueueSource queueSource = QueueSource(

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/services.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/translations.i18n.dart';
import 'package:tray_manager/tray_manager.dart';

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/importer.dart';
import 'package:freezer/settings.dart';
import 'package:get_it/get_it.dart';
import 'package:html/parser.dart';
import 'package:html/dom.dart' as dom;
import 'package:http/http.dart' as http;
@ -13,6 +14,7 @@ import 'dart:io';
import 'package:url_launcher/url_launcher.dart';
class SpotifyScrapper {
static final deezerAPI = GetIt.instance<DeezerAPI>();
//Parse spotify URL to URI (spotify:track:1234)
static String? parseUrl(String url) {
Uri uri = Uri.parse(url);

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:audio_service/audio_service.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/cupertino.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_screenutil/flutter_screenutil.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/paths.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/search.dart';
import 'package:freezer/ui/settings_screen.dart';
import 'package:get_it/get_it.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:i18n_extension/i18n_widget.dart';
import 'package:logging/logging.dart';
@ -94,7 +97,17 @@ void main() async {
..registerAdapter(NavigationRailAppearanceAdapter())
..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
try {
@ -110,16 +123,16 @@ void main() async {
exit(1);
}
downloadManager.init();
// photos
cacheManager = CacheManager(Config(
DefaultCacheManager.key,
// cache aggressively
stalePeriod: const Duration(days: 30),
maxNrOfCacheObjects: 5000,
));
// cacheManager = HiveCacheManager(
// boxName: 'freezer-images', boxPath: await Paths.cacheDir());
// 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;
Logger.root.onRecord.listen((record) {
@ -138,6 +151,9 @@ void main() async {
runApp(const FreezerApp());
}
Future<PersistCookieJar> getCookieJar() async => PersistCookieJar(
storage: IsarStorage('cookies', await Paths.dataDirectory()));
class FreezerApp extends StatefulWidget {
const FreezerApp({super.key});
@ -276,6 +292,7 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
@override
void initState() {
if (settings.arl != null) {
final deezerAPI = GetIt.instance<DeezerAPI>();
//Load token on background
deezerAPI.arl = settings.arl!;
settings.offlineMode = true;
@ -297,7 +314,7 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
}
Future _logOut() async {
await deezerAPI.logout();
await GetIt.instance<DeezerAPI>().logout();
setState(() {
settings.arl = null;
settings.offlineMode = false;
@ -423,13 +440,14 @@ class MainScreenState extends State<MainScreen>
}
void _startPreload(String type) async {
await deezerAPI.authorize();
await DeezerAPI.instance.authorize();
if (type == 'flow') {
await playerHelper.playFromSmartTrackList(SmartTrackList(id: 'flow'));
return;
}
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);
}
}
@ -440,7 +458,7 @@ class MainScreenState extends State<MainScreen>
await DownloadManager.platform.invokeMethod('getPreloadInfo');
if (info != null) {
//Used if started from android auto
await deezerAPI.authorize();
await DeezerAPI.instance.authorize();
_startPreload(info);
}
}

View File

@ -34,7 +34,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
//Get album from API, if doesn't have tracks
if (album!.tracks == null || album!.tracks!.isEmpty) {
try {
Album a = await deezerAPI.album(album!.id);
Album a = await DeezerAPI.instance.album(album!.id);
//Preserve library
a.library = album!.library;
setState(() => album = a);
@ -185,14 +185,15 @@ class _AlbumDetailsState extends State<AlbumDetails> {
onPressed: () async {
//Add to library
if (!album!.library!) {
await deezerAPI.addFavoriteAlbum(album!.id);
await DeezerAPI.instance
.addFavoriteAlbum(album!.id);
ScaffoldMessenger.of(context)
.snack('Added to library'.i18n);
setState(() => album!.library = true);
return;
}
//Remove
await deezerAPI.removeAlbum(album!.id);
await DeezerAPI.instance.removeAlbum(album!.id);
ScaffoldMessenger.of(context)
.snack('Album removed from library!'.i18n);
setState(() => album!.library = false);
@ -278,7 +279,7 @@ class _MakeAlbumOfflineState extends State<MakeAlbumOffline> {
onChanged: (v) async {
if (v) {
//Add to offline
await deezerAPI.addFavoriteAlbum(widget.album!.id);
await DeezerAPI.instance.addFavoriteAlbum(widget.album!.id);
downloadManager.addOfflineAlbum(widget.album, private: true);
MenuSheet(context).showDownloadStartedToast();
setState(() {
@ -325,7 +326,7 @@ class _ArtistDetailsState extends State<ArtistDetails> {
Future<Artist> _loadArtist(Artist artist) async {
//Load artist from api if no albums
if ((artist.albums ?? []).isEmpty) {
return await deezerAPI.artist(artist.id);
return await DeezerAPI.instance.artist(artist.id);
}
return artist;
@ -428,7 +429,8 @@ class _ArtistDetailsState extends State<ArtistDetails> {
icon: const Icon(Icons.favorite),
label: Text('Library'.i18n),
onPressed: () async {
await deezerAPI.addFavoriteArtist(widget.artist.id);
await DeezerAPI.instance
.addFavoriteArtist(widget.artist.id);
ScaffoldMessenger.of(context)
.snack('Added to library'.i18n);
},
@ -439,7 +441,7 @@ class _ArtistDetailsState extends State<ArtistDetails> {
label: Text('Radio'.i18n),
onPressed: () async {
List<Track> tracks =
(await deezerAPI.smartRadio(artist.id))!;
(await DeezerAPI.instance.smartRadio(artist.id))!;
playerHelper.playFromTrackList(
tracks,
tracks[0].id,
@ -593,8 +595,8 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
//Fetch data
List<Album>? data;
try {
data = await deezerAPI.discographyPage(artist!.id,
start: artist!.albums!.length);
data = await DeezerAPI.instance
.discographyPage(artist!.id, start: artist!.albums!.length);
} catch (e) {
setState(() {
_error = true;
@ -787,7 +789,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
//Get another page of tracks
List<Track>? tracks;
try {
tracks = await deezerAPI.playlistTracksPage(playlist!.id, pos);
tracks = await DeezerAPI.instance.playlistTracksPage(playlist!.id, pos);
} catch (e) {
setState(() {
_error = true;
@ -816,7 +818,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
//Preload tracks
if (playlist!.tracks!.length < playlist!.trackCount!) {
playlist = await deezerAPI.fullPlaylist(playlist!.id);
playlist = await DeezerAPI.instance.fullPlaylist(playlist!.id);
}
setState(() => _sort = cache.sorts[index]);
}
@ -834,7 +836,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
//Preload for sorting
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
if (playlist!.tracks!.isEmpty) {
//Get correct metadata
deezerAPI.playlist(playlist!.id).then((Playlist p) {
DeezerAPI.instance.playlist(playlist!.id).then((Playlist p) {
setState(() {
playlist = p;
});
@ -987,7 +989,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
MakePlaylistOffline(playlist),
if (playlist!.user!.name != deezerAPI.userName)
if (playlist!.user!.name != DeezerAPI.instance.userName)
IconButton(
icon: Icon(
playlist!.library!
@ -1000,14 +1002,14 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
onPressed: () async {
//Add to library
if (!playlist!.library!) {
await deezerAPI.addPlaylist(playlist!.id);
await DeezerAPI.instance.addPlaylist(playlist!.id);
ScaffoldMessenger.of(context)
.snack('Added to library'.i18n);
setState(() => playlist!.library = true);
return;
}
//Remove
await deezerAPI.removePlaylist(playlist!.id);
await DeezerAPI.instance.removePlaylist(playlist!.id);
ScaffoldMessenger.of(context)
.snack('Playlist removed from library!'.i18n);
setState(() => playlist!.library = false);
@ -1030,7 +1032,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
onSelected: (SortType s) async {
if (playlist!.tracks!.length < playlist!.trackCount!) {
//Preload whole playlist
playlist = await deezerAPI.fullPlaylist(playlist!.id);
playlist =
await DeezerAPI.instance.fullPlaylist(playlist!.id);
}
setState(() => _sort!.type = s);
@ -1097,7 +1100,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
}, onSecondary: (details) {
MenuSheet m = MenuSheet(context);
m.defaultTrackMenu(t, details: details, options: [
if (playlist!.user!.id == deezerAPI.userId)
if (playlist!.user!.id == DeezerAPI.instance.userId)
m.removeFromPlaylist(t, playlist)
]);
});
@ -1150,8 +1153,8 @@ class _MakePlaylistOfflineState extends State<MakePlaylistOffline> {
if (v) {
//Add to offline
if (widget.playlist!.user != null &&
widget.playlist!.user!.id != deezerAPI.userId) {
await deezerAPI.addPlaylist(widget.playlist!.id);
widget.playlist!.user!.id != DeezerAPI.instance.userId) {
await DeezerAPI.instance.addPlaylist(widget.playlist!.id);
}
downloadManager.addOfflinePlaylist(widget.playlist,
private: true);
@ -1197,7 +1200,7 @@ class _ShowScreenState extends State<ShowScreen> {
//Fetch
List<ShowEpisode>? e;
try {
e = await deezerAPI.allShowEpisodes(_show!.id);
e = await DeezerAPI.instance.allShowEpisodes(_show!.id);
} catch (e) {
setState(() {
_loading = false;

View File

@ -2,6 +2,7 @@ import 'package:cookie_jar/cookie_jar.dart';
import 'package:flutter/material.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/settings.dart';
import 'package:get_it/get_it.dart';
import 'package:webview_flutter/webview_flutter.dart';
class ExternalLinkRoute extends StatefulWidget {
@ -45,7 +46,8 @@ class _ExternalLinkRouteState extends State<ExternalLinkRoute> {
}
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);
return {'Cookie': cookies.join(';')};
}

View File

@ -148,9 +148,9 @@ class _HomePageWidgetState extends State<HomePageWidget> {
//Fetch channel from api
try {
if (widget.channel == null) {
homePage = await deezerAPI.homePage();
homePage = await DeezerAPI.instance.homePage();
} else {
homePage = await deezerAPI.getChannel(widget.channel!.target);
homePage = await DeezerAPI.instance.getChannel(widget.channel!.target);
}
} catch (e) {
homePage = null;

View File

@ -13,6 +13,7 @@ import 'package:freezer/ui/error.dart';
import 'package:freezer/ui/importer_screen.dart';
import 'package:freezer/ui/tiles.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:get_it/get_it.dart';
import 'menu.dart';
import '../api/download.dart';
@ -241,6 +242,8 @@ class _LibraryTracksState extends State<LibraryTracks> {
int? trackCount;
Sorting? _sort = Sorting(sourceType: SortSourceTypes.TRACKS);
final deezerAPI = DeezerAPI.instance;
Playlist get _playlist => Playlist(id: deezerAPI.favoritesPlaylistId!);
List<Track> get _sorted {
@ -595,7 +598,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
Future _load() async {
if (settings.offlineMode) return;
try {
List<Album> albums = await deezerAPI.getAlbums();
List<Album> albums = await DeezerAPI.instance.getAlbums();
setState(() => _albums = albums);
} catch (e) {}
}
@ -799,7 +802,7 @@ class _LibraryArtistsState extends State<LibraryArtists> {
//Fetch
List<Artist>? data;
try {
data = await deezerAPI.getArtists();
data = await DeezerAPI.instance.getArtists();
} catch (e) {}
//Update UI
setState(() {
@ -929,6 +932,8 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
final ScrollController _scrollController = ScrollController();
String _filter = '';
final deezerAPI = DeezerAPI.instance;
List<Playlist> get _sorted {
List<Playlist> playlists = List.from(_playlists!
.where((p) => p.title!.toLowerCase().contains(_filter.toLowerCase())));

View File

@ -31,6 +31,7 @@ class LoginWidget extends StatefulWidget {
class _LoginWidgetState extends State<LoginWidget> {
late String _arl;
String? _error;
final deezerAPI = DeezerAPI.instance;
//Initialize deezer etc
Future _init() async {

View File

@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:freezer/api/definitions.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/translations.i18n.dart';
import 'package:freezer/ui/error.dart';
@ -38,7 +38,7 @@ class LyricsScreen extends StatelessWidget {
}
class LyricsWidget extends StatefulWidget {
const LyricsWidget({Key? key}) : super(key: key);
const LyricsWidget({super.key});
@override
State<LyricsWidget> createState() => _LyricsWidgetState();

View File

@ -1,6 +1,8 @@
import 'dart:async';
import 'package:freezer/api/player/player_helper.dart';
import 'package:freezer/main.dart';
import 'package:freezer/settings.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:flutter/material.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:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:freezer/api/download_manager/download_manager.dart' as newDl;
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
final Track track;
@ -267,21 +270,21 @@ class MenuSheet {
MenuSheetOption(Text('Play next'.i18n),
icon: const Icon(Icons.playlist_play), onTap: () async {
//-1 = next
await audioHandler.insertQueueItem(-1, await t.toMediaItem());
await audioHandler.insertQueueItem(-1, t.toMediaItem());
});
MenuSheetOption addToQueue(Track t) =>
MenuSheetOption(Text('Add to queue'.i18n),
icon: const Icon(Icons.playlist_add), onTap: () async {
await audioHandler.addQueueItem(await t.toMediaItem());
await audioHandler.addQueueItem(t.toMediaItem());
});
MenuSheetOption addTrackFavorite(Track t) =>
MenuSheetOption(Text('Add track to favorites'.i18n),
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
Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!);
Playlist p = Playlist(id: DeezerAPI.instance.favoritesPlaylistId!);
if (await downloadManager.checkOffline(playlist: p)) {
downloadManager.addOfflinePlaylist(p);
}
@ -294,9 +297,12 @@ class MenuSheet {
Text('Download'.i18n),
icon: const Icon(Icons.file_download),
onTap: () async {
if (await downloadManager.addOfflineTrack(t,
private: false, context: context, isSingleton: true) !=
false) showDownloadStartedToast();
final dl = newDl.DownloadManager();
await dl.startDebug();
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(
track: t,
callback: (Playlist p) async {
await deezerAPI.addToPlaylist(t.id, p.id);
await DeezerAPI.instance.addToPlaylist(t.id, p.id);
//Update the playlist if offline
if (await downloadManager.checkOffline(playlist: p)) {
downloadManager.addOfflinePlaylist(p);
@ -327,7 +333,7 @@ class MenuSheet {
Text('Remove from playlist'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeFromPlaylist(t.id, p!.id);
await DeezerAPI.instance.removeFromPlaylist(t.id, p!.id);
ScaffoldMessenger.of(context)
.snack('${'Track removed from'.i18n} ${p.title}');
},
@ -337,9 +343,9 @@ class MenuSheet {
Text('Remove favorite'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeFavorite(t.id);
await DeezerAPI.instance.removeFavorite(t.id);
//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)) {
await downloadManager.addOfflinePlaylist(p);
}
@ -454,7 +460,7 @@ class MenuSheet {
Text('Make offline'.i18n),
icon: const Icon(Icons.offline_pin),
onTap: () async {
await deezerAPI.addFavoriteAlbum(a.id);
await DeezerAPI.instance.addFavoriteAlbum(a.id);
await downloadManager.addOfflineAlbum(a, private: true);
showDownloadStartedToast();
@ -465,7 +471,7 @@ class MenuSheet {
Text('Add to library'.i18n),
icon: const Icon(Icons.library_music),
onTap: () async {
await deezerAPI.addFavoriteAlbum(a.id);
await DeezerAPI.instance.addFavoriteAlbum(a.id);
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
},
);
@ -475,7 +481,7 @@ class MenuSheet {
Text('Remove album'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeAlbum(a.id);
await DeezerAPI.instance.removeAlbum(a.id);
await downloadManager.removeOfflineAlbum(a.id);
ScaffoldMessenger.of(context).snack('Album removed'.i18n);
if (onRemove != null) onRemove();
@ -508,7 +514,7 @@ class MenuSheet {
Text('Remove from favorites'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeArtist(a.id);
await DeezerAPI.instance.removeArtist(a.id);
ScaffoldMessenger.of(context)
.snack('Artist removed from library'.i18n);
if (onRemove != null) onRemove();
@ -519,7 +525,7 @@ class MenuSheet {
Text('Add to favorites'.i18n),
icon: const Icon(Icons.favorite),
onTap: () async {
await deezerAPI.addFavoriteArtist(a.id);
await DeezerAPI.instance.addFavoriteArtist(a.id);
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
},
);
@ -541,7 +547,7 @@ class MenuSheet {
addPlaylistOffline(playlist),
downloadPlaylist(playlist),
shareTile('playlist', playlist.id),
if (playlist.user!.id == deezerAPI.userId)
if (playlist.user!.id == DeezerAPI.instance.userId)
editPlaylist(playlist, onUpdate: onUpdate),
...options
]);
@ -556,12 +562,12 @@ class MenuSheet {
Text('Remove from library'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
if (p.user!.id!.trim() == deezerAPI.userId) {
if (p.user!.id!.trim() == DeezerAPI.instance.userId) {
//Delete playlist if own
await deezerAPI.deletePlaylist(p.id);
await DeezerAPI.instance.deletePlaylist(p.id);
} else {
//Just remove from library
await deezerAPI.removePlaylist(p.id);
await DeezerAPI.instance.removePlaylist(p.id);
}
downloadManager.removeOfflinePlaylist(p.id);
if (onRemove != null) onRemove();
@ -572,7 +578,7 @@ class MenuSheet {
Text('Add playlist to library'.i18n),
icon: const Icon(Icons.favorite),
onTap: () async {
await deezerAPI.addPlaylist(p.id);
await DeezerAPI.instance.addPlaylist(p.id);
ScaffoldMessenger.of(context).snack('Added playlist to library'.i18n);
},
);
@ -582,7 +588,7 @@ class MenuSheet {
icon: const Icon(Icons.offline_pin),
onTap: () async {
//Add to library
await deezerAPI.addPlaylist(p.id);
await DeezerAPI.instance.addPlaylist(p.id);
downloadManager.addOfflinePlaylist(p, private: true);
showDownloadStartedToast();
@ -825,7 +831,7 @@ class _SelectPlaylistDialogState extends State<SelectPlaylistDialog> {
return AlertDialog(
title: Text('Select playlist'.i18n),
content: FutureBuilder(
future: deezerAPI.getPlaylists(),
future: DeezerAPI.instance.getPlaylists(),
builder: (context, snapshot) {
if (snapshot.hasError) {
const SizedBox(
@ -957,7 +963,7 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
onPressed: () async {
if (edit) {
//Update
await deezerAPI.updatePlaylist(widget.playlist!.id,
await DeezerAPI.instance.updatePlaylist(widget.playlist!.id,
_titleController!.value.text, _descController!.value.text,
status: _playlistType);
ScaffoldMessenger.of(context).snack('Playlist updated!'.i18n);
@ -966,7 +972,7 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
if (widget.tracks != null) {
tracks = widget.tracks!.map<String>((t) => t!.id).toList();
}
await deezerAPI.createPlaylist(_title,
await DeezerAPI.instance.createPlaylist(_title,
status: _playlistType,
description: _description,
trackIds: tracks);

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:freezer/api/player/player_helper.dart';
import 'package:freezer/settings.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:rxdart/rxdart.dart';
@ -15,12 +16,12 @@ class PlayerBar extends StatelessWidget {
final Color? backgroundColor;
final FocusNode? focusNode;
const PlayerBar({
Key? key,
super.key,
this.onTap,
this.shouldHaveHero = true,
this.backgroundColor,
this.focusNode,
}) : super(key: key);
});
final double iconSize = 28;
@ -193,12 +194,12 @@ class PlayPauseButton extends StatefulWidget {
final Color? color;
const PlayPauseButton(
this.size, {
Key? key,
super.key,
this.filled = false,
this.material3 = true,
this.color,
this.iconColor,
}) : super(key: key);
});
@override
State<PlayPauseButton> createState() => _PlayPauseButtonState();

View File

@ -13,6 +13,7 @@ import 'package:freezer/api/cache.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.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/page_routes/fade.dart';
import 'package:freezer/settings.dart';
@ -750,12 +751,12 @@ class _FavoriteButtonState extends State<FavoriteButton> {
if (cache.checkTrackFavorite(Track.fromMediaItem(mediaItem))) {
//Remove from library
setState(() => cache.libraryTracks.remove(mediaItem.id));
await deezerAPI.removeFavorite(mediaItem.id);
await DeezerAPI.instance.removeFavorite(mediaItem.id);
await cache.save();
} else {
//Add
setState(() => cache.libraryTracks.add(mediaItem.id));
await deezerAPI.addFavoriteTrack(mediaItem.id);
await DeezerAPI.instance.addFavoriteTrack(mediaItem.id);
await cache.save();
}
},
@ -1266,8 +1267,8 @@ class BottomBarControls extends StatelessWidget {
),
iconSize: iconSize,
onPressed: () async {
unawaited(
deezerAPI.dislikeTrack(audioHandler.mediaItem.value!.id));
unawaited(DeezerAPI.instance
.dislikeTrack(audioHandler.mediaItem.value!.id));
if (playerHelper.queueIndex <
audioHandler.queue.value.length - 1) {
audioHandler.skipToNext();

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:freezer/api/definitions.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/ui/menu.dart';
import 'package:freezer/ui/tiles.dart';

View File

@ -22,6 +22,7 @@ import '../api/definitions.dart';
import 'error.dart';
FutureOr openScreenByURL(BuildContext context, String url) async {
final deezerAPI = DeezerAPI.instance;
DeezerLinkResponse? res = await deezerAPI.parseLink(Uri.parse(url));
if (res == null) return;
@ -138,8 +139,8 @@ class _SearchScreenState extends State<SearchScreen> {
final List<String>? suggestions;
try {
_searchCancelToken = CancelToken();
suggestions = await deezerAPI.searchSuggestions(_controller.text,
cancelToken: _searchCancelToken);
suggestions = await DeezerAPI.instance
.searchSuggestions(_controller.text, cancelToken: _searchCancelToken);
} on DioException catch (e) {
if (e.type != DioExceptionType.cancel) rethrow;
return;
@ -456,7 +457,7 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
if (widget.offline ?? false) {
results = await downloadManager.search(widget.query);
} else {
results = await deezerAPI.search(widget.query);
results = await DeezerAPI.instance.search(widget.query);
}
setState(() {
_results = results;
@ -896,8 +897,10 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
onTap: () async {
//Load entire show, then play
List<ShowEpisode> episodes =
(await deezerAPI.allShowEpisodes(
episode.show!.id))!;
(await DeezerAPI
.instance
.allShowEpisodes(
episode.show!.id))!;
await playerHelper.playShowEpisode(
episode.show!, episodes,
index: episodes.indexWhere(
@ -1051,7 +1054,7 @@ class EpisodeListScreen extends StatelessWidget {
onTap: () async {
//Load entire show, then play
List<ShowEpisode> episodes =
(await deezerAPI.allShowEpisodes(e.show!.id))!;
(await DeezerAPI.instance.allShowEpisodes(e.show!.id))!;
await playerHelper.playShowEpisode(e.show!, episodes,
index: episodes.indexWhere((ep) => e.id == ep.id));
},

View File

@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:fluttertoast/fluttertoast.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/icons.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),
subtitle: Text(ContentLanguage.all[i].code),
onTap: () async {
setState(() => settings.deezerLanguage =
ContentLanguage.all[i].code);
settings.deezerLanguage =
ContentLanguage.all[i].code;
setState(() {});
await settings.save();
deezerAPI.updateHeaders();
DeezerAPI.instance.deezerLanguage =
settings.deezerLanguage;
DeezerAPI.instance.updateHeaders();
Navigator.of(context).pop();
},
)),
@ -898,9 +902,10 @@ class _DeezerSettingsState extends State<DeezerSettings> {
titlePadding: const EdgeInsets.all(8.0),
isSearchable: true,
onValuePicked: (Country country) {
setState(
() => settings.deezerCountry = country.isoCode);
deezerAPI.updateHeaders();
DeezerAPI.instance.deezerCountry =
settings.deezerCountry = country.isoCode;
setState(() {});
DeezerAPI.instance.updateHeaders();
settings.save();
},
));
@ -1390,7 +1395,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
showDialog(
context: context,
builder: (context) {
deezerAPI.authorize().then((v) {
DeezerAPI.instance.authorize().then((v) {
if (v) {
setState(() => settings.offlineMode = false);
} else {

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/download.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/main.dart';
import 'package:freezer/translations.i18n.dart';
@ -246,7 +247,7 @@ class PlaylistTile extends StatelessWidget {
if (playlist!.user == null ||
playlist!.user!.name == null ||
playlist!.user!.name == '' ||
playlist!.user!.id == deezerAPI.userId) {
playlist!.user!.id == DeezerAPI.instance.userId) {
if (playlist!.trackCount == null) return '';
return '${playlist!.trackCount} ${'Tracks'.i18n}';
}
@ -341,8 +342,9 @@ class PlaylistCardTile extends StatelessWidget {
left: 8.0,
child: PlayItemButton(
onTap: () async {
final Playlist fullPlaylist =
await deezerAPI.fullPlaylist(playlist!.id);
final Playlist fullPlaylist = await DeezerAPI
.instance
.fullPlaylist(playlist!.id);
await playerHelper.playFromPlaylist(fullPlaylist);
},
))
@ -639,7 +641,8 @@ class AlbumCard extends StatelessWidget {
left: 8.0,
child: PlayItemButton(
onTap: () async {
final fullAlbum = await deezerAPI.album(album.id);
final fullAlbum =
await DeezerAPI.instance.album(album.id);
await playerHelper.playFromAlbum(fullAlbum);
},
),

View File

@ -27,4 +27,21 @@ class Utils {
}
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;
}
}

View File

@ -567,6 +567,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -575,6 +591,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -789,7 +813,7 @@ packages:
path: "../just_audio_media_kit"
relative: true
source: path
version: "2.0.1"
version: "2.0.0"
just_audio_platform_interface:
dependency: transitive
description:

View File

@ -107,6 +107,8 @@ dependencies:
tray_manager: ^0.2.1
window_manager:
^0.3.8
get_it: ^7.6.7
freezed_annotation: ^2.4.1
#deezcryptor:
#path: deezcryptor/
@ -119,6 +121,7 @@ dev_dependencies:
hive_generator: ^2.0.0
flutter_lints: ^3.0.1
isar_generator: ^3.1.0+1
freezed: ^2.4.7
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec