0.5.1 - Download fixes
This commit is contained in:
parent
8db1223805
commit
22ceca2d9c
|
@ -1,5 +1,6 @@
|
||||||
package f.f.freezer;
|
package f.f.freezer;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.jaudiotagger.audio.AudioFile;
|
import org.jaudiotagger.audio.AudioFile;
|
||||||
|
@ -36,6 +37,13 @@ import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
public class Deezer {
|
public class Deezer {
|
||||||
|
|
||||||
|
DownloadLog logger;
|
||||||
|
|
||||||
|
//Initialize for logging
|
||||||
|
void init(DownloadLog logger) {
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
//Get guest SID cookie from deezer.com
|
//Get guest SID cookie from deezer.com
|
||||||
public static String getSidCookie() throws Exception {
|
public static String getSidCookie() throws Exception {
|
||||||
URL url = new URL("https://deezer.com/");
|
URL url = new URL("https://deezer.com/");
|
||||||
|
@ -102,7 +110,7 @@ public class Deezer {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int qualityFallback(String trackId, String md5origin, String mediaVersion, int originalQuality) throws Exception {
|
public int qualityFallback(String trackId, String md5origin, String mediaVersion, int originalQuality) throws Exception {
|
||||||
//Create HEAD requests to check if exists
|
//Create HEAD requests to check if exists
|
||||||
URL url = new URL(getTrackUrl(trackId, md5origin, mediaVersion, originalQuality));
|
URL url = new URL(getTrackUrl(trackId, md5origin, mediaVersion, originalQuality));
|
||||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
@ -110,6 +118,7 @@ public class Deezer {
|
||||||
int rc = connection.getResponseCode();
|
int rc = connection.getResponseCode();
|
||||||
//Track not available
|
//Track not available
|
||||||
if (rc > 400) {
|
if (rc > 400) {
|
||||||
|
logger.warn("Quality fallback, response code: " + Integer.toString(rc) + ", current: " + Integer.toString(originalQuality));
|
||||||
//Returns -1 if no quality available
|
//Returns -1 if no quality available
|
||||||
if (originalQuality == 1) return -1;
|
if (originalQuality == 1) return -1;
|
||||||
if (originalQuality == 3) return qualityFallback(trackId, md5origin, mediaVersion, 1);
|
if (originalQuality == 3) return qualityFallback(trackId, md5origin, mediaVersion, 1);
|
||||||
|
@ -251,6 +260,7 @@ public class Deezer {
|
||||||
original = original.replaceAll("%0trackNumber%", String.format("%02d", trackNumber));
|
original = original.replaceAll("%0trackNumber%", String.format("%02d", trackNumber));
|
||||||
//Year
|
//Year
|
||||||
original = original.replaceAll("%year%", publicTrack.getString("release_date").substring(0, 4));
|
original = original.replaceAll("%year%", publicTrack.getString("release_date").substring(0, 4));
|
||||||
|
original = original.replaceAll("%date%", publicTrack.getString("release_date"));
|
||||||
|
|
||||||
if (newQuality == 9) return original + ".flac";
|
if (newQuality == 9) return original + ".flac";
|
||||||
return original + ".mp3";
|
return original + ".mp3";
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
package f.f.freezer;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class DownloadLog {
|
||||||
|
|
||||||
|
BufferedWriter writer;
|
||||||
|
|
||||||
|
//Open/Create file
|
||||||
|
public void open(Context context) {
|
||||||
|
File file = new File(context.getExternalFilesDir(""), "download.log");
|
||||||
|
try {
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.createNewFile();
|
||||||
|
}
|
||||||
|
writer = new BufferedWriter(new FileWriter(file, true));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
Log.e("DOWN", "Error opening download log!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close log
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
writer.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
Log.w("DOWN", "Error closing download log!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String time() {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
|
||||||
|
return format.format(Calendar.getInstance().getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write error to log
|
||||||
|
public void error(String info) {
|
||||||
|
if (writer == null) return;
|
||||||
|
String data = "E:" + time() + ": " + info;
|
||||||
|
try {
|
||||||
|
writer.write(data);
|
||||||
|
writer.newLine();
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
Log.w("DOWN", "Error writing into log.");
|
||||||
|
}
|
||||||
|
Log.e("DOWN", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write error to log with download info
|
||||||
|
public void error(String info, Download download) {
|
||||||
|
if (writer == null) return;
|
||||||
|
String data = "E:" + time() + " (TrackID: " + download.trackId + ", ID: " + Integer.toString(download.id) + "): " +info;
|
||||||
|
try {
|
||||||
|
writer.write(data);
|
||||||
|
writer.newLine();
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
Log.w("DOWN", "Error writing into log.");
|
||||||
|
}
|
||||||
|
Log.e("DOWN", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write warning to log
|
||||||
|
public void warn(String info) {
|
||||||
|
if (writer == null) return;
|
||||||
|
String data = "W:" + time() + ": " + info;
|
||||||
|
try {
|
||||||
|
writer.write(data);
|
||||||
|
writer.newLine();
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
Log.w("DOWN", "Error writing into log.");
|
||||||
|
}
|
||||||
|
Log.w("DOWN", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write warning to log with download info
|
||||||
|
public void warn(String info, Download download) {
|
||||||
|
if (writer == null) return;
|
||||||
|
String data = "W:" + time() + " (TrackID: " + download.trackId + ", ID: " + Integer.toString(download.id) + "): " +info;
|
||||||
|
try {
|
||||||
|
writer.write(data);
|
||||||
|
writer.newLine();
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
Log.w("DOWN", "Error writing into log.");
|
||||||
|
}
|
||||||
|
Log.w("DOWN", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,6 +30,8 @@ import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
@ -54,6 +56,7 @@ public class DownloadService extends Service {
|
||||||
DownloadSettings settings;
|
DownloadSettings settings;
|
||||||
Context context;
|
Context context;
|
||||||
SQLiteDatabase db;
|
SQLiteDatabase db;
|
||||||
|
Deezer deezer = new Deezer();
|
||||||
|
|
||||||
Messenger serviceMessenger;
|
Messenger serviceMessenger;
|
||||||
Messenger activityMessenger;
|
Messenger activityMessenger;
|
||||||
|
@ -62,12 +65,11 @@ public class DownloadService extends Service {
|
||||||
ArrayList<Download> downloads = new ArrayList<>();
|
ArrayList<Download> downloads = new ArrayList<>();
|
||||||
ArrayList<DownloadThread> threads = new ArrayList<>();
|
ArrayList<DownloadThread> threads = new ArrayList<>();
|
||||||
ArrayList<Boolean> updateRequests = new ArrayList<>();
|
ArrayList<Boolean> updateRequests = new ArrayList<>();
|
||||||
ArrayList<String> pendingCovers = new ArrayList<>();
|
|
||||||
boolean updating = false;
|
boolean updating = false;
|
||||||
Handler progressUpdateHandler = new Handler();
|
Handler progressUpdateHandler = new Handler();
|
||||||
|
DownloadLog logger = new DownloadLog();
|
||||||
|
|
||||||
public DownloadService() {
|
public DownloadService() { }
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
@ -79,6 +81,10 @@ public class DownloadService extends Service {
|
||||||
createNotificationChannel();
|
createNotificationChannel();
|
||||||
createProgressUpdateHandler();
|
createProgressUpdateHandler();
|
||||||
|
|
||||||
|
//Setup logger, deezer api
|
||||||
|
logger.open(context);
|
||||||
|
deezer.init(logger);
|
||||||
|
|
||||||
//Get DB
|
//Get DB
|
||||||
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
|
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
|
||||||
db = dbHelper.getWritableDatabase();
|
db = dbHelper.getWritableDatabase();
|
||||||
|
@ -89,6 +95,9 @@ public class DownloadService extends Service {
|
||||||
//Cancel notifications
|
//Cancel notifications
|
||||||
notificationManager.cancelAll();
|
notificationManager.cancelAll();
|
||||||
|
|
||||||
|
//Logger
|
||||||
|
logger.close();
|
||||||
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,10 +187,11 @@ public class DownloadService extends Service {
|
||||||
//Check if last download
|
//Check if last download
|
||||||
if (threads.size() == 0) {
|
if (threads.size() == 0) {
|
||||||
running = false;
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Send updates to UI
|
||||||
|
updateProgress();
|
||||||
updateState();
|
updateState();
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Send state change to UI
|
//Send state change to UI
|
||||||
|
@ -273,15 +283,16 @@ public class DownloadService extends Service {
|
||||||
//Quality fallback
|
//Quality fallback
|
||||||
int newQuality;
|
int newQuality;
|
||||||
try {
|
try {
|
||||||
newQuality = Deezer.qualityFallback(download.trackId, download.md5origin, download.mediaVersion, download.quality);
|
newQuality = deezer.qualityFallback(download.trackId, download.md5origin, download.mediaVersion, download.quality);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("QF", "Quality fallback failed: " + e.toString());
|
logger.error("Quality fallback failed: " + e.toString(), download);
|
||||||
download.state = Download.DownloadState.ERROR;
|
download.state = Download.DownloadState.ERROR;
|
||||||
exit();
|
exit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//No quality available
|
//No quality available
|
||||||
if (newQuality == -1) {
|
if (newQuality == -1) {
|
||||||
|
logger.error("No available quality!", download);
|
||||||
download.state = Download.DownloadState.DEEZER_ERROR;
|
download.state = Download.DownloadState.DEEZER_ERROR;
|
||||||
exit();
|
exit();
|
||||||
return;
|
return;
|
||||||
|
@ -294,7 +305,7 @@ public class DownloadService extends Service {
|
||||||
trackJson = Deezer.callPublicAPI("track", download.trackId);
|
trackJson = Deezer.callPublicAPI("track", download.trackId);
|
||||||
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("ERR", "Unable to fetch track metadata.");
|
logger.error("Unable to fetch track and album metadata! " + e.toString(), download);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
download.state = Download.DownloadState.ERROR;
|
download.state = Download.DownloadState.ERROR;
|
||||||
exit();
|
exit();
|
||||||
|
@ -305,9 +316,8 @@ public class DownloadService extends Service {
|
||||||
try {
|
try {
|
||||||
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, newQuality));
|
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, newQuality));
|
||||||
parentDir = new File(outFile.getParent());
|
parentDir = new File(outFile.getParent());
|
||||||
parentDir.mkdirs();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("ERR", "Error creating directories! TrackID: " + download.trackId);
|
logger.error("Error generating track filename (" + download.path + "): " + e.toString(), download);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
download.state = Download.DownloadState.ERROR;
|
download.state = Download.DownloadState.ERROR;
|
||||||
exit();
|
exit();
|
||||||
|
@ -351,7 +361,7 @@ public class DownloadService extends Service {
|
||||||
|
|
||||||
//Open streams
|
//Open streams
|
||||||
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||||
OutputStream outputStream = new FileOutputStream(tmpFile.getPath());
|
OutputStream outputStream = new FileOutputStream(tmpFile.getPath(), true);
|
||||||
//Save total
|
//Save total
|
||||||
download.filesize = start + connection.getContentLength();
|
download.filesize = start + connection.getContentLength();
|
||||||
//Download
|
//Download
|
||||||
|
@ -384,7 +394,7 @@ public class DownloadService extends Service {
|
||||||
updateProgress();
|
updateProgress();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//Download error
|
//Download error
|
||||||
Log.e("DOWNLOAD", "Download error!");
|
logger.error("Download error: " + e.toString(), download);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
download.state = Download.DownloadState.ERROR;
|
download.state = Download.DownloadState.ERROR;
|
||||||
exit();
|
exit();
|
||||||
|
@ -397,7 +407,7 @@ public class DownloadService extends Service {
|
||||||
try {
|
try {
|
||||||
Deezer.decryptTrack(tmpFile.getPath(), download.trackId);
|
Deezer.decryptTrack(tmpFile.getPath(), download.trackId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("DEC", "Decryption failed!");
|
logger.error("Decryption error: " + e.toString(), download);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
//Shouldn't ever fail
|
//Shouldn't ever fail
|
||||||
}
|
}
|
||||||
|
@ -408,27 +418,37 @@ public class DownloadService extends Service {
|
||||||
exit();
|
exit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//Copy to destination directory
|
|
||||||
|
//Create dirs and copy
|
||||||
|
parentDir.mkdirs();
|
||||||
if (!tmpFile.renameTo(outFile)) {
|
if (!tmpFile.renameTo(outFile)) {
|
||||||
|
boolean error = true;
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
Files.move(tmpFile.toPath(), outFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
tmpFile.delete();
|
||||||
|
error = false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error moving file! " + outFile.getPath() + ", " + e.toString(), download);
|
||||||
|
download.state = Download.DownloadState.ERROR;
|
||||||
|
exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
logger.error("Error moving file! " + outFile.getPath(), download);
|
||||||
download.state = Download.DownloadState.ERROR;
|
download.state = Download.DownloadState.ERROR;
|
||||||
exit();
|
exit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!download.priv) {
|
|
||||||
//Download cover
|
|
||||||
File coverFile = new File(parentDir, "cover.jpg");
|
|
||||||
//Wait for another thread to download it
|
|
||||||
while (pendingCovers.contains(coverFile.getPath())) {
|
|
||||||
try { Thread.sleep(100); } catch (Exception ignored) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!coverFile.exists()) {
|
if (!download.priv) {
|
||||||
try {
|
|
||||||
//Create fake file so other threads don't start downloading covers
|
|
||||||
coverFile.createNewFile();
|
|
||||||
pendingCovers.add(coverFile.getPath());
|
|
||||||
|
|
||||||
|
//Download cover for each track
|
||||||
|
File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg");
|
||||||
|
|
||||||
|
try {
|
||||||
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + trackJson.getString("md5_image") + "/1400x1400-000000-80-0-0.jpg");
|
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + trackJson.getString("md5_image") + "/1400x1400-000000-80-0-0.jpg");
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
//Set headers
|
//Set headers
|
||||||
|
@ -444,17 +464,18 @@ public class DownloadService extends Service {
|
||||||
outputStream.write(buffer, 0, read);
|
outputStream.write(buffer, 0, read);
|
||||||
}
|
}
|
||||||
//On done
|
//On done
|
||||||
|
try {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("ERR", "Error downloading cover!");
|
logger.error("Error downloading cover! " + e.toString(), download);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
coverFile.delete();
|
coverFile.delete();
|
||||||
}
|
}
|
||||||
//Remove lock
|
|
||||||
pendingCovers.remove(coverFile.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
//Tag
|
//Tag
|
||||||
try {
|
try {
|
||||||
|
@ -464,6 +485,13 @@ public class DownloadService extends Service {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Delete cover if disabled
|
||||||
|
if (!settings.trackCover)
|
||||||
|
coverFile.delete();
|
||||||
|
|
||||||
|
//Album cover
|
||||||
|
downloadAlbumCover(albumJson);
|
||||||
|
|
||||||
//Lyrics
|
//Lyrics
|
||||||
if (settings.downloadLyrics) {
|
if (settings.downloadLyrics) {
|
||||||
try {
|
try {
|
||||||
|
@ -475,7 +503,7 @@ public class DownloadService extends Service {
|
||||||
fileOutputStream.write(lrcData.getBytes());
|
fileOutputStream.write(lrcData.getBytes());
|
||||||
fileOutputStream.close();
|
fileOutputStream.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w("WAR", "Missing lyrics! " + e.toString());
|
logger.warn("Error downloading lyrics! " + e.toString(), download);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -486,6 +514,46 @@ public class DownloadService extends Service {
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Each track has own album art, this is to download cover.jpg
|
||||||
|
void downloadAlbumCover(JSONObject albumJson) {
|
||||||
|
//Checks
|
||||||
|
if (albumJson == null || !albumJson.has("md5_image")) return;
|
||||||
|
File coverFile = new File(parentDir, "cover.jpg");
|
||||||
|
if (coverFile.exists()) return;
|
||||||
|
//Don't download if doesn't have album
|
||||||
|
if (!download.path.contains("/%album%/")) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Create to lock
|
||||||
|
coverFile.createNewFile();
|
||||||
|
|
||||||
|
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + albumJson.getString("md5_image") + "/1400x1400-000000-80-0-0.jpg");
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
//Set headers
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.connect();
|
||||||
|
//Open streams
|
||||||
|
InputStream inputStream = connection.getInputStream();
|
||||||
|
OutputStream outputStream = new FileOutputStream(coverFile.getPath());
|
||||||
|
//Download
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read = 0;
|
||||||
|
while ((read = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
//On done
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
outputStream.close();
|
||||||
|
connection.disconnect();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Error downloading album cover! " + e.toString(), download);
|
||||||
|
coverFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void stopDownload() {
|
void stopDownload() {
|
||||||
stopDownload = true;
|
stopDownload = true;
|
||||||
}
|
}
|
||||||
|
@ -691,16 +759,18 @@ public class DownloadService extends Service {
|
||||||
int downloadThreads;
|
int downloadThreads;
|
||||||
boolean overwriteDownload;
|
boolean overwriteDownload;
|
||||||
boolean downloadLyrics;
|
boolean downloadLyrics;
|
||||||
|
boolean trackCover;
|
||||||
|
|
||||||
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics) {
|
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics, boolean trackCover) {
|
||||||
this.downloadThreads = downloadThreads;
|
this.downloadThreads = downloadThreads;
|
||||||
this.overwriteDownload = overwriteDownload;
|
this.overwriteDownload = overwriteDownload;
|
||||||
this.downloadLyrics = downloadLyrics;
|
this.downloadLyrics = downloadLyrics;
|
||||||
|
this.trackCover = trackCover;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Parse settings from bundle sent from UI
|
//Parse settings from bundle sent from UI
|
||||||
static DownloadSettings fromBundle(Bundle b) {
|
static DownloadSettings fromBundle(Bundle b) {
|
||||||
return new DownloadSettings(b.getInt("downloadThreads"), b.getBoolean("overwriteDownload"), b.getBoolean("downloadLyrics"));
|
return new DownloadSettings(b.getInt("downloadThreads"), b.getBoolean("overwriteDownload"), b.getBoolean("downloadLyrics"), b.getBoolean("trackCover"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class MainActivity extends FlutterActivity {
|
||||||
ArrayList<HashMap> downloads = call.arguments();
|
ArrayList<HashMap> downloads = call.arguments();
|
||||||
for (int i=0; i<downloads.size(); i++) {
|
for (int i=0; i<downloads.size(); i++) {
|
||||||
//Check if exists
|
//Check if exists
|
||||||
Cursor cursor = db.rawQuery("SELECT id, state FROM Downloads WHERE trackId == ? AND path == ?",
|
Cursor cursor = db.rawQuery("SELECT id, state, quality FROM Downloads WHERE trackId == ? AND path == ?",
|
||||||
new String[]{(String)downloads.get(i).get("trackId"), (String)downloads.get(i).get("path")});
|
new String[]{(String)downloads.get(i).get("trackId"), (String)downloads.get(i).get("path")});
|
||||||
if (cursor.getCount() > 0) {
|
if (cursor.getCount() > 0) {
|
||||||
//If done or error, set state to NONE - they should be skipped because file exists
|
//If done or error, set state to NONE - they should be skipped because file exists
|
||||||
|
@ -74,6 +74,7 @@ public class MainActivity extends FlutterActivity {
|
||||||
if (cursor.getInt(1) >= 3) {
|
if (cursor.getInt(1) >= 3) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put("state", 0);
|
values.put("state", 0);
|
||||||
|
values.put("quality", cursor.getInt(2));
|
||||||
db.update("Downloads", values, "id == ?", new String[]{Integer.toString(cursor.getInt(0))});
|
db.update("Downloads", values, "id == ?", new String[]{Integer.toString(cursor.getInt(0))});
|
||||||
Log.d("INFO", "Already exists in DB, updating to none state!");
|
Log.d("INFO", "Already exists in DB, updating to none state!");
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,6 +117,7 @@ public class MainActivity extends FlutterActivity {
|
||||||
bundle.putInt("downloadThreads", (int)call.argument("downloadThreads"));
|
bundle.putInt("downloadThreads", (int)call.argument("downloadThreads"));
|
||||||
bundle.putBoolean("overwriteDownload", (boolean)call.argument("overwriteDownload"));
|
bundle.putBoolean("overwriteDownload", (boolean)call.argument("overwriteDownload"));
|
||||||
bundle.putBoolean("downloadLyrics", (boolean)call.argument("downloadLyrics"));
|
bundle.putBoolean("downloadLyrics", (boolean)call.argument("downloadLyrics"));
|
||||||
|
bundle.putBoolean("trackCover", (boolean)call.argument("trackCover"));
|
||||||
sendMessage(DownloadService.SERVICE_SETTINGS_UPDATE, bundle);
|
sendMessage(DownloadService.SERVICE_SETTINGS_UPDATE, bundle);
|
||||||
|
|
||||||
result.success(null);
|
result.success(null);
|
||||||
|
|
|
@ -103,7 +103,7 @@ class Track {
|
||||||
}
|
}
|
||||||
List<String> playbackDetails;
|
List<String> playbackDetails;
|
||||||
if (mi.extras['playbackDetails'] != null)
|
if (mi.extras['playbackDetails'] != null)
|
||||||
playbackDetails = jsonDecode(mi.extras['playbackDetails']).map<String>((e) => e.toString()).toList();
|
playbackDetails = (jsonDecode(mi.extras['playbackDetails'])??[]).map<String>((e) => e.toString()).toList();
|
||||||
|
|
||||||
return Track(
|
return Track(
|
||||||
title: mi.title??mi.displayTitle,
|
title: mi.title??mi.displayTitle,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:disk_space/disk_space.dart';
|
import 'package:disk_space/disk_space.dart';
|
||||||
import 'package:filesize/filesize.dart';
|
import 'package:filesize/filesize.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.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';
|
||||||
|
@ -117,6 +119,10 @@ class DownloadManager {
|
||||||
Batch b = db.batch();
|
Batch b = db.batch();
|
||||||
b = await _addTrackToDB(b, track, true);
|
b = await _addTrackToDB(b, track, true);
|
||||||
await b.commit();
|
await b.commit();
|
||||||
|
|
||||||
|
//Cache art
|
||||||
|
DefaultCacheManager().getSingleFile(track.albumArt.thumb);
|
||||||
|
DefaultCacheManager().getSingleFile(track.albumArt.full);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get path
|
//Get path
|
||||||
|
@ -136,6 +142,10 @@ class DownloadManager {
|
||||||
|
|
||||||
//Add to DB
|
//Add to DB
|
||||||
if (private) {
|
if (private) {
|
||||||
|
//Cache art
|
||||||
|
DefaultCacheManager().getSingleFile(album.art.thumb);
|
||||||
|
DefaultCacheManager().getSingleFile(album.art.full);
|
||||||
|
|
||||||
Batch b = db.batch();
|
Batch b = db.batch();
|
||||||
b.insert('Albums', album.toSQL(off: true), conflictAlgorithm: ConflictAlgorithm.replace);
|
b.insert('Albums', album.toSQL(off: true), conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
for (Track t in album.tracks) {
|
for (Track t in album.tracks) {
|
||||||
|
@ -168,6 +178,9 @@ class DownloadManager {
|
||||||
b.insert('Playlists', playlist.toSQL(), conflictAlgorithm: ConflictAlgorithm.replace);
|
b.insert('Playlists', playlist.toSQL(), conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
for (Track t in playlist.tracks) {
|
for (Track t in playlist.tracks) {
|
||||||
b = await _addTrackToDB(b, t, false);
|
b = await _addTrackToDB(b, t, false);
|
||||||
|
//Cache art
|
||||||
|
DefaultCacheManager().getSingleFile(t.albumArt.thumb);
|
||||||
|
DefaultCacheManager().getSingleFile(t.albumArt.full);
|
||||||
}
|
}
|
||||||
await b.commit();
|
await b.commit();
|
||||||
}
|
}
|
||||||
|
@ -410,14 +423,14 @@ class DownloadManager {
|
||||||
path = p.join(path, sanitize(playlistName));
|
path = p.join(path, sanitize(playlistName));
|
||||||
|
|
||||||
if (settings.artistFolder)
|
if (settings.artistFolder)
|
||||||
path = p.join(path, sanitize(track.artistString));
|
path = p.join(path, '%artist%');
|
||||||
|
|
||||||
//Album folder / with disk number
|
//Album folder / with disk number
|
||||||
if (settings.albumFolder) {
|
if (settings.albumFolder) {
|
||||||
if (settings.albumDiscFolder) {
|
if (settings.albumDiscFolder) {
|
||||||
path = p.join(path, sanitize(track.album.title) + ' - Disk ' + track.diskNumber.toString());
|
path = p.join(path, '%album%' + ' - Disk ' + track.diskNumber.toString());
|
||||||
} else {
|
} else {
|
||||||
path = p.join(path, sanitize(track.album.title));
|
path = p.join(path, '%album%');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Final path
|
//Final path
|
||||||
|
|
|
@ -166,7 +166,7 @@ const language_ar_ar = {
|
||||||
"Language": "اللغة",
|
"Language": "اللغة",
|
||||||
"Language changed, please restart Freezer to apply!": "تم تغيير اللغة، الرجاء إعادة تشغيل فريزر لتطبيق!",
|
"Language changed, please restart Freezer to apply!": "تم تغيير اللغة، الرجاء إعادة تشغيل فريزر لتطبيق!",
|
||||||
"Importing...": "جار الاستيراد...",
|
"Importing...": "جار الاستيراد...",
|
||||||
"Radio": "راديو"
|
"Radio": "راديو",
|
||||||
|
|
||||||
//0.5.0 Strings:
|
//0.5.0 Strings:
|
||||||
"Storage permission denied!": "رفض إذن التخزين!",
|
"Storage permission denied!": "رفض إذن التخزين!",
|
||||||
|
@ -189,7 +189,7 @@ const language_ar_ar = {
|
||||||
"To get latest releases": "لتنزيل اخر اصدارات البرنامج",
|
"To get latest releases": "لتنزيل اخر اصدارات البرنامج",
|
||||||
"Official chat": "الدردشة الرسمية",
|
"Official chat": "الدردشة الرسمية",
|
||||||
"Telegram Group": "مجموعة التلكرام",
|
"Telegram Group": "مجموعة التلكرام",
|
||||||
"Huge thanks to all the contributors! <3": "شكرا جزيلا لجميع المساهمين! <3",
|
"Huge thanks to all the contributors! <3": "<3 !شكرا جزيلا لجميع المساهمين",
|
||||||
"Edit playlist": "تعديل قائمة التشغيل",
|
"Edit playlist": "تعديل قائمة التشغيل",
|
||||||
"Update": "تحديث",
|
"Update": "تحديث",
|
||||||
"Playlist updated!": "تم تحديث قائمة التشغيل!",
|
"Playlist updated!": "تم تحديث قائمة التشغيل!",
|
||||||
|
|
|
@ -188,7 +188,9 @@ const language_en_us = {
|
||||||
"Storage permission denied!": "Storage permission denied!",
|
"Storage permission denied!": "Storage permission denied!",
|
||||||
"Failed": "Failed",
|
"Failed": "Failed",
|
||||||
"Queued": "Queued",
|
"Queued": "Queued",
|
||||||
"External": "External",
|
//Updated in 0.5.1 - used in context of download:
|
||||||
|
"External": "Storage",
|
||||||
|
//0.5.0
|
||||||
"Restart failed downloads": "Restart failed downloads",
|
"Restart failed downloads": "Restart failed downloads",
|
||||||
"Clear failed": "Clear failed",
|
"Clear failed": "Clear failed",
|
||||||
"Download Settings": "Download Settings",
|
"Download Settings": "Download Settings",
|
||||||
|
@ -198,7 +200,9 @@ const language_en_us = {
|
||||||
"Not set": "Not set",
|
"Not set": "Not set",
|
||||||
"Search or paste URL": "Search or paste URL",
|
"Search or paste URL": "Search or paste URL",
|
||||||
"History": "History",
|
"History": "History",
|
||||||
"Download threads": "Download threads",
|
//Updated 0.5.1
|
||||||
|
"Download threads": "Concurrent downloads",
|
||||||
|
//0.5.0
|
||||||
"Lyrics unavailable, empty or failed to load!": "Lyrics unavailable, empty or failed to load!",
|
"Lyrics unavailable, empty or failed to load!": "Lyrics unavailable, empty or failed to load!",
|
||||||
"About": "About",
|
"About": "About",
|
||||||
"Telegram Channel": "Telegram Channel",
|
"Telegram Channel": "Telegram Channel",
|
||||||
|
@ -209,6 +213,12 @@ const language_en_us = {
|
||||||
"Edit playlist": "Edit playlist",
|
"Edit playlist": "Edit playlist",
|
||||||
"Update": "Update",
|
"Update": "Update",
|
||||||
"Playlist updated!": "Playlist updated!",
|
"Playlist updated!": "Playlist updated!",
|
||||||
"Downloads added!": "Downloads added!"
|
"Downloads added!": "Downloads added!",
|
||||||
|
|
||||||
|
//0.5.1 Strings:
|
||||||
|
"Save cover file for every track": "Save cover file for every track",
|
||||||
|
"Download Log": "Download Log",
|
||||||
|
"Repository": "Repository",
|
||||||
|
"Source code, report issues there.": "Source code, report issues there."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -187,6 +187,33 @@ const language_it_it = {
|
||||||
"Language changed, please restart Freezer to apply!":
|
"Language changed, please restart Freezer to apply!":
|
||||||
"Lingua cambiata, riavvia Freezer per applicare la modifica!",
|
"Lingua cambiata, riavvia Freezer per applicare la modifica!",
|
||||||
"Importing...": "Importando...",
|
"Importing...": "Importando...",
|
||||||
"Radio": "Radio"
|
"Radio": "Radio",
|
||||||
|
|
||||||
|
//0.5.0 Strings:
|
||||||
|
"Storage permission denied!": "Autorizzazione di archiviazione negata!",
|
||||||
|
"Failed": "Fallito",
|
||||||
|
"Queued": "In coda",
|
||||||
|
"External": "Esterno",
|
||||||
|
"Restart failed downloads": "Riavvia download non riusciti",
|
||||||
|
"Clear failed": "Pulisci fallito",
|
||||||
|
"Download Settings": "Scarica le impostazioni",
|
||||||
|
"Create folder for playlist": "Crea cartella per playlist",
|
||||||
|
"Download .LRC lyrics": "Scarica testi .LRC",
|
||||||
|
"Proxy": "Proxy",
|
||||||
|
"Not set": "Non impostato",
|
||||||
|
"Search or paste URL": "Cerca o incolla l'URL",
|
||||||
|
"History": "Storia",
|
||||||
|
"Download threads": "Scarica threads",
|
||||||
|
"Lyrics unavailable, empty or failed to load!": "Testi non disponibili, vuoti o caricamento non riuscito!",
|
||||||
|
"About": "Info",
|
||||||
|
"Telegram Channel": "Canale Telegram",
|
||||||
|
"To get latest releases": "Per ottenere le ultime versioni",
|
||||||
|
"Official chat": "Chat ufficiale",
|
||||||
|
"Telegram Group": "Gruppo Telegram",
|
||||||
|
"Huge thanks to all the contributors! <3": "Un enorme grazie a tutti i collaboratori! <3",
|
||||||
|
"Edit playlist": "Modifica playlist",
|
||||||
|
"Update": "Aggiorna",
|
||||||
|
"Playlist updated!": "Playlist aggiornata!",
|
||||||
|
"Downloads added!": "Download aggiunti!"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,7 +58,8 @@ class Settings {
|
||||||
bool playlistFolder;
|
bool playlistFolder;
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool downloadLyrics;
|
bool downloadLyrics;
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool trackCover;
|
||||||
|
|
||||||
//Appearance
|
//Appearance
|
||||||
@JsonKey(defaultValue: Themes.Light)
|
@JsonKey(defaultValue: Themes.Light)
|
||||||
|
@ -152,7 +153,8 @@ class Settings {
|
||||||
return {
|
return {
|
||||||
"downloadThreads": downloadThreads,
|
"downloadThreads": downloadThreads,
|
||||||
"overwriteDownload": overwriteDownload,
|
"overwriteDownload": overwriteDownload,
|
||||||
"downloadLyrics": downloadLyrics
|
"downloadLyrics": downloadLyrics,
|
||||||
|
"trackCover": trackCover
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
||||||
..downloadThreads = json['downloadThreads'] as int ?? 2
|
..downloadThreads = json['downloadThreads'] as int ?? 2
|
||||||
..playlistFolder = json['playlistFolder'] as bool ?? false
|
..playlistFolder = json['playlistFolder'] as bool ?? false
|
||||||
..downloadLyrics = json['downloadLyrics'] as bool ?? true
|
..downloadLyrics = json['downloadLyrics'] as bool ?? true
|
||||||
|
..trackCover = json['trackCover'] as bool ?? false
|
||||||
..theme =
|
..theme =
|
||||||
_$enumDecodeNullable(_$ThemesEnumMap, json['theme']) ?? Themes.Light
|
_$enumDecodeNullable(_$ThemesEnumMap, json['theme']) ?? Themes.Light
|
||||||
..primaryColor = Settings._colorFromJson(json['primaryColor'] as int)
|
..primaryColor = Settings._colorFromJson(json['primaryColor'] as int)
|
||||||
|
@ -59,6 +60,7 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||||
'downloadThreads': instance.downloadThreads,
|
'downloadThreads': instance.downloadThreads,
|
||||||
'playlistFolder': instance.playlistFolder,
|
'playlistFolder': instance.playlistFolder,
|
||||||
'downloadLyrics': instance.downloadLyrics,
|
'downloadLyrics': instance.downloadLyrics,
|
||||||
|
'trackCover': instance.trackCover,
|
||||||
'theme': _$ThemesEnumMap[instance.theme],
|
'theme': _$ThemesEnumMap[instance.theme],
|
||||||
'primaryColor': Settings._colorToJson(instance.primaryColor),
|
'primaryColor': Settings._colorToJson(instance.primaryColor),
|
||||||
'useArtColor': instance.useArtColor,
|
'useArtColor': instance.useArtColor,
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import 'dart:async';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:filesize/filesize.dart';
|
import 'package:filesize/filesize.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:freezer/api/download.dart';
|
import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'cached_image.dart';
|
import 'cached_image.dart';
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
|
||||||
class DownloadsScreen extends StatefulWidget {
|
class DownloadsScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_DownloadsScreenState createState() => _DownloadsScreenState();
|
_DownloadsScreenState createState() => _DownloadsScreenState();
|
||||||
|
@ -189,15 +193,30 @@ class DownloadTile extends StatelessWidget {
|
||||||
|
|
||||||
String subtitle() {
|
String subtitle() {
|
||||||
String out = '';
|
String out = '';
|
||||||
|
|
||||||
|
if (download.state != DownloadState.DOWNLOADING && download.state != DownloadState.POST) {
|
||||||
//Download type
|
//Download type
|
||||||
if (download.private) out += 'Offline'.i18n;
|
if (download.private) out += 'Offline'.i18n;
|
||||||
else out += 'External'.i18n;
|
else out += 'External'.i18n;
|
||||||
out += ' | ';
|
out += ' | ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (download.state == DownloadState.POST) {
|
||||||
|
return 'Post processing...'.i18n;
|
||||||
|
}
|
||||||
|
|
||||||
//Quality
|
//Quality
|
||||||
if (download.quality == 9) out += 'FLAC';
|
if (download.quality == 9) out += 'FLAC';
|
||||||
if (download.quality == 3) out += 'MP3 320kbps';
|
if (download.quality == 3) out += 'MP3 320kbps';
|
||||||
if (download.quality == 1) out += 'MP3 128kbps';
|
if (download.quality == 1) out += 'MP3 128kbps';
|
||||||
|
|
||||||
|
//Downloading show progress
|
||||||
|
if (download.state == DownloadState.DOWNLOADING) {
|
||||||
|
out += ' | ${filesize(download.received, 2)} / ${filesize(download.filesize, 2)}';
|
||||||
|
double progress = download.received.toDouble() / download.filesize.toDouble();
|
||||||
|
out += ' ${(progress*100.0).toStringAsFixed(2)}%';
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,3 +301,62 @@ class DownloadTile extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DownloadLogViewer extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_DownloadLogViewerState createState() => _DownloadLogViewerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DownloadLogViewerState extends State<DownloadLogViewer> {
|
||||||
|
|
||||||
|
List<String> data = [];
|
||||||
|
|
||||||
|
//Load log from file
|
||||||
|
Future _load() async {
|
||||||
|
String path = p.join((await getExternalStorageDirectory()).path, 'download.log');
|
||||||
|
File file = File(path);
|
||||||
|
if (await file.exists()) {
|
||||||
|
String _d = await file.readAsString();
|
||||||
|
setState(() {
|
||||||
|
data = _d.replaceAll("\r", "").split("\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get color by log type
|
||||||
|
Color color(String line) {
|
||||||
|
if (line.startsWith('E:')) return Colors.red;
|
||||||
|
if (line.startsWith('W:')) return Colors.orange[600];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_load();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Download Log'.i18n),
|
||||||
|
),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemCount: data.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
data[i],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: color(data[i])
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -203,6 +203,7 @@ class LibraryTracks extends StatefulWidget {
|
||||||
class _LibraryTracksState extends State<LibraryTracks> {
|
class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
|
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
|
bool _loadingTracks = false;
|
||||||
ScrollController _scrollController = ScrollController();
|
ScrollController _scrollController = ScrollController();
|
||||||
List<Track> tracks = [];
|
List<Track> tracks = [];
|
||||||
List<Track> allTracks = [];
|
List<Track> allTracks = [];
|
||||||
|
@ -250,6 +251,9 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load another page of tracks from deezer
|
//Load another page of tracks from deezer
|
||||||
|
if (_loadingTracks) return;
|
||||||
|
_loadingTracks = true;
|
||||||
|
|
||||||
List<Track> _t;
|
List<Track> _t;
|
||||||
try {
|
try {
|
||||||
_t = await deezerAPI.playlistTracksPage(deezerAPI.favoritesPlaylistId, pos);
|
_t = await deezerAPI.playlistTracksPage(deezerAPI.favoritesPlaylistId, pos);
|
||||||
|
@ -263,6 +267,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
tracks.addAll(_t);
|
tracks.addAll(_t);
|
||||||
_makeFavorite();
|
_makeFavorite();
|
||||||
_loading = false;
|
_loading = false;
|
||||||
|
_loadingTracks = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,12 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||||
await Future.delayed(Duration(milliseconds: 300));
|
await Future.delayed(Duration(milliseconds: 300));
|
||||||
if (q != _query) return null;
|
if (q != _query) return null;
|
||||||
//Load
|
//Load
|
||||||
List sugg = await deezerAPI.searchSuggestions(_query);
|
List sugg;
|
||||||
|
try {
|
||||||
|
sugg = await deezerAPI.searchSuggestions(_query);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (sugg != null)
|
||||||
setState(() => _suggestions = sugg);
|
setState(() => _suggestions = sugg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:fluttericon/font_awesome5_icons.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.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/ui/downloads_screen.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
import 'package:freezer/ui/home_screen.dart';
|
import 'package:freezer/ui/home_screen.dart';
|
||||||
import 'package:i18n_extension/i18n_widget.dart';
|
import 'package:i18n_extension/i18n_widget.dart';
|
||||||
|
@ -352,7 +353,7 @@ class _QualityPickerState extends State<QualityPicker> {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update quality in settings
|
//Update quality in settings
|
||||||
void _updateQuality(AudioQuality q) {
|
void _updateQuality(AudioQuality q) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_quality = q;
|
_quality = q;
|
||||||
});
|
});
|
||||||
|
@ -370,15 +371,8 @@ class _QualityPickerState extends State<QualityPicker> {
|
||||||
case 'offline':
|
case 'offline':
|
||||||
settings.offlineQuality = _quality; break;
|
settings.offlineQuality = _quality; break;
|
||||||
}
|
}
|
||||||
settings.updateAudioServiceQuality();
|
await settings.save();
|
||||||
}
|
await settings.updateAudioServiceQuality();
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
//Save
|
|
||||||
settings.updateAudioServiceQuality();
|
|
||||||
settings.save();
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -558,8 +552,9 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
||||||
if (!(await Permission.storage.request().isGranted)) return;
|
if (!(await Permission.storage.request().isGranted)) return;
|
||||||
//Navigate
|
//Navigate
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) => DirectoryPicker(settings.downloadPath, onSelect: (String p) {
|
builder: (context) => DirectoryPicker(settings.downloadPath, onSelect: (String p) async {
|
||||||
setState(() => settings.downloadPath = p);
|
setState(() => settings.downloadPath = p);
|
||||||
|
await settings.save();
|
||||||
},)
|
},)
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
@ -590,7 +585,7 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
||||||
),
|
),
|
||||||
Container(height: 8.0),
|
Container(height: 8.0),
|
||||||
Text(
|
Text(
|
||||||
'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%, %playlistTrackNumber%, %0playlistTrackNumber%, %year%',
|
'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%, %playlistTrackNumber%, %0playlistTrackNumber%, %year%, %date%',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
),
|
),
|
||||||
|
@ -734,6 +729,26 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('Save cover file for every track'.i18n),
|
||||||
|
leading: Container(
|
||||||
|
width: 30.0,
|
||||||
|
child: Checkbox(
|
||||||
|
value: settings.trackCover,
|
||||||
|
onChanged: (v) {
|
||||||
|
setState(() => settings.trackCover = v);
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('Download Log'.i18n),
|
||||||
|
leading: Icon(Icons.sticky_note_2),
|
||||||
|
onTap: () => Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(builder: (context) => DownloadLogViewer())
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1071,6 +1086,14 @@ class _CreditsScreenState extends State<CreditsScreen> {
|
||||||
launch('https://t.me/freezerandroid');
|
launch('https://t.me/freezerandroid');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('Repository'.i18n),
|
||||||
|
subtitle: Text('Source code, report issues there.'),
|
||||||
|
leading: Icon(Icons.code, color: Colors.green, size: 36.0),
|
||||||
|
onTap: () {
|
||||||
|
launch('https://notabug.org/exttex/freezer');
|
||||||
|
},
|
||||||
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
...List.generate(credits.length, (i) => ListTile(
|
...List.generate(credits.length, (i) => ListTile(
|
||||||
title: Text(credits[i][0]),
|
title: Text(credits[i][0]),
|
||||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 0.5.0+1
|
version: 0.5.1+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.8.0 <3.0.0"
|
sdk: ">=2.8.0 <3.0.0"
|
||||||
|
|
Loading…
Reference in New Issue