refactor: code cleanup and improvements

This commit is contained in:
zarzet committed 2026-01-17 09:07:29 +07:00
1 parent be9444c76b
commit b96233f90b
32 files changed
+13 -453

No files matched your search

-3
View File
@@ -14,7 +14,6 @@ class ApkDownloader {
required String version,
ProgressCallback? onProgress,
}) async {
// Validate URL for security
final uri = Uri.tryParse(url);
if (uri == null || uri.scheme != 'https') {
_log.e('Refusing to download from invalid or non-HTTPS URL');
@@ -35,7 +34,6 @@ class ApkDownloader {
final contentLength = response.contentLength ?? 0;
// Get download directory
final dir = await getExternalStorageDirectory();
if (dir == null) {
_log.e('Could not get storage directory');
@@ -45,7 +43,6 @@ class ApkDownloader {
final filePath = '${dir.path}/SpotiFLAC-$version.apk';
final file = File(filePath);
// Delete if exists
if (await file.exists()) {
await file.delete();
}
+1 -14
View File
@@ -23,7 +23,6 @@ class CsvImportService {
final content = await file.readAsString();
final tracks = _parseCsv(content);
// Enrich tracks with metadata from Deezer (cover URL, duration, etc.)
if (tracks.isNotEmpty) {
return await _enrichTracksMetadata(tracks, onProgress: onProgress);
}
@@ -48,7 +47,6 @@ class CsvImportService {
final track = tracks[i];
onProgress?.call(i + 1, tracks.length);
// Only enrich if missing cover/duration
if (track.coverUrl == null || track.duration == 0) {
Map<String, dynamic>? trackData;
@@ -62,7 +60,6 @@ class CsvImportService {
}
}
// Fallback to text search if ISRC failed or not available
if (trackData == null) {
try {
final query = '${track.artistName} ${track.name}';
@@ -71,13 +68,11 @@ class CsvImportService {
if (searchResult.containsKey('tracks')) {
final tracksList = searchResult['tracks'] as List<dynamic>?;
if (tracksList != null && tracksList.isNotEmpty) {
// Find best match by comparing names
for (final result in tracksList) {
final resultMap = result as Map<String, dynamic>;
final resultName = (resultMap['name'] as String?)?.toLowerCase() ?? '';
final trackNameLower = track.name.toLowerCase();
// Check if track name matches (contains or equals)
if (resultName.contains(trackNameLower) || trackNameLower.contains(resultName)) {
trackData = resultMap;
_log.d('Text search match for ${track.name}: $resultName');
@@ -85,7 +80,6 @@ class CsvImportService {
}
}
// If no exact match, use first result
if (trackData == null && tracksList.isNotEmpty) {
trackData = tracksList.first as Map<String, dynamic>;
_log.d('Using first search result for ${track.name}');
@@ -97,7 +91,6 @@ class CsvImportService {
}
}
// Apply enriched data if found
if (trackData != null) {
final coverUrl = trackData['images'] as String?;
final durationMs = trackData['duration_ms'] as int? ?? 0;
@@ -127,7 +120,6 @@ class CsvImportService {
}
}
// Keep original track if enrichment failed or not needed
enrichedTracks.add(track);
}
@@ -137,10 +129,9 @@ class CsvImportService {
static List<Track> _parseCsv(String content) {
final List<Track> tracks = [];
final lines = content.split(RegExp(r'\r\n|\r|\n')); // Handle various newline formats
final lines = content.split(RegExp(r'\r\n|\r|\n'));
if (lines.isEmpty) return tracks;
// Detect headers line (assume first non-empty line)
int startIdx = 0;
while (startIdx < lines.length && lines[startIdx].trim().isEmpty) {
startIdx++;
@@ -150,7 +141,6 @@ class CsvImportService {
final headers = _parseLine(lines[startIdx]);
final colMap = <String, int>{};
for (int i = 0; i < headers.length; i++) {
// Normalize header: lowercase, trim, remove quotes
String h = _cleanValue(headers[i]).toLowerCase();
colMap[h] = i;
}
@@ -164,7 +154,6 @@ class CsvImportService {
final values = _parseLine(line);
// Helper to get value securely
String? getVal(List<String> keys) {
return _getValue(values, colMap, keys);
}
@@ -180,7 +169,6 @@ class CsvImportService {
spotifyId = spotifyId.replaceAll('spotify:track:', '');
}
// Basic validation: Need at least name and artist, OR a spotify ID
if ((trackName != null && trackName.isNotEmpty && artistName != null) || (spotifyId != null && spotifyId.isNotEmpty)) {
tracks.add(Track(
id: spotifyId ?? 'csv_${DateTime.now().millisecondsSinceEpoch}_$i',
@@ -215,7 +203,6 @@ class CsvImportService {
if (val.startsWith('"') && val.endsWith('"') && val.length >= 2) {
val = val.substring(1, val.length - 1);
}
// Handle double quotes escape in CSV ("" -> ")
val = val.replaceAll('""', '"');
return val;
}
-15
View File
@@ -31,14 +31,12 @@ class FFmpegService {
static Future<String?> convertM4aToFlac(String inputPath) async {
final outputPath = inputPath.replaceAll('.m4a', '.flac');
// FFmpeg command to remux M4A to FLAC
final command =
'-i "$inputPath" -c:a flac -compression_level 8 "$outputPath" -y';
final result = await _execute(command);
if (result.success) {
// Delete original M4A file
try {
await File(inputPath).delete();
} catch (_) {}
@@ -88,18 +86,15 @@ class FFmpegService {
inputPath.split(Platform.pathSeparator).last.replaceAll('.flac', '');
final outputDir = '$dir${Platform.pathSeparator}M4A';
// Create output directory
await Directory(outputDir).create(recursive: true);
final outputPath = '$outputDir${Platform.pathSeparator}$baseName.m4a';
String command;
if (codec == 'alac') {
// ALAC - lossless
command =
'-i "$inputPath" -codec:a alac -map 0:a -map_metadata 0 "$outputPath" -y';
} else {
// AAC - lossy
command =
'-i "$inputPath" -codec:a aac -b:a $bitrate -map 0:a -map_metadata 0 "$outputPath" -y';
}
@@ -141,25 +136,19 @@ class FFmpegService {
String? coverPath,
Map<String, String>? metadata,
}) async {
// Android Scoped Storage: Cannot write directly to Music folder with FFmpeg
// Use app-internal cache directory for temp output
final tempDir = await getTemporaryDirectory();
final uniqueId = DateTime.now().millisecondsSinceEpoch;
final tempOutput = '${tempDir.path}/temp_embed_$uniqueId.flac';
// Construct command
final StringBuffer cmdBuffer = StringBuffer();
cmdBuffer.write('-i "$flacPath" ');
// Add cover input if available
if (coverPath != null) {
cmdBuffer.write('-i "$coverPath" ');
}
// Map audio stream
cmdBuffer.write('-map 0:a ');
// Map cover stream if available
if (coverPath != null) {
cmdBuffer.write('-map 1:0 ');
cmdBuffer.write('-c:v copy ');
@@ -168,13 +157,10 @@ class FFmpegService {
cmdBuffer.write('-metadata:s:v comment="Cover (front)" ');
}
// Copy audio codec (don't re-encode)
cmdBuffer.write('-c:a copy ');
// Add text metadata
if (metadata != null) {
metadata.forEach((key, value) {
// Sanitize value: escape double quotes
final sanitizedValue = value.replaceAll('"', '\\"');
cmdBuffer.write('-metadata $key="$sanitizedValue" ');
});
@@ -215,7 +201,6 @@ class FFmpegService {
}
}
// Clean up temp file if exists
try {
final tempFile = File(tempOutput);
if (await tempFile.exists()) {
-2
View File
@@ -32,7 +32,6 @@ class NotificationService {
await _notifications.initialize(initSettings);
// Create notification channel for Android
if (Platform.isAndroid) {
await _notifications
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
@@ -227,7 +226,6 @@ class NotificationService {
await _notifications.cancel(downloadProgressId);
}
// Update APK download notifications
Future<void> showUpdateDownloadProgress({
required String version,
required int received,
-1
View File
@@ -770,7 +770,6 @@ class PlatformBridge {
if (result == null || result == '') return null;
return jsonDecode(result as String) as Map<String, dynamic>;
} catch (e) {
// No extension found or error handling URL
return null;
}
}
-7
View File
@@ -30,13 +30,11 @@ class ShareIntentService {
if (_initialized) return;
_initialized = true;
// Listen to media sharing coming from outside the app while the app is in memory
_mediaSubscription = ReceiveSharingIntent.instance.getMediaStream().listen(
_handleSharedMedia,
onError: (err) => _log.e('Error: $err'),
);
// Get the media sharing coming from outside the app while the app is closed
final initialMedia = await ReceiveSharingIntent.instance.getInitialMedia();
if (initialMedia.isNotEmpty) {
_handleSharedMedia(initialMedia, isInitial: true);
@@ -47,14 +45,12 @@ class ShareIntentService {
void _handleSharedMedia(List<SharedMediaFile> files, {bool isInitial = false}) {
for (final file in files) {
// Check the path - for text shares, the path contains the shared text
final textToCheck = file.path;
final url = _extractSpotifyUrl(textToCheck);
if (url != null) {
_log.i('Received Spotify URL: $url (initial: $isInitial)');
if (isInitial) {
// Store for later - listener might not be ready yet
_pendingUrl = url;
}
_sharedUrlController.add(url);
@@ -71,18 +67,15 @@ class ShareIntentService {
String? _extractSpotifyUrl(String text) {
if (text.isEmpty) return null;
// Check for spotify: URI format
final uriMatch = RegExp(r'spotify:(track|album|playlist|artist):[a-zA-Z0-9]+').firstMatch(text);
if (uriMatch != null) {
return uriMatch.group(0);
}
// Check for open.spotify.com URL
final urlMatch = RegExp(
r'https?://open\.spotify\.com/(track|album|playlist|artist)/[a-zA-Z0-9]+(\?[^\s]*)?',
).firstMatch(text);
if (urlMatch != null) {
// Return URL without query params for cleaner handling
final fullUrl = urlMatch.group(0)!;
final queryIndex = fullUrl.indexOf('?');
return queryIndex > 0 ? fullUrl.substring(0, queryIndex) : fullUrl;
-4
View File
@@ -65,7 +65,6 @@ class UpdateChecker {
Map<String, dynamic>? releaseData;
if (channel == 'preview') {
// For preview channel, get all releases and find the latest (including prereleases)
final response = await http.get(
Uri.parse('$_allReleasesApiUrl?per_page=10'),
headers: {'Accept': 'application/vnd.github.v3+json'},
@@ -82,10 +81,8 @@ class UpdateChecker {
return null;
}
// First release is the latest (including prereleases)
releaseData = releases.first as Map<String, dynamic>;
} else {
// For stable channel, use /latest endpoint (excludes prereleases)
final response = await http.get(
Uri.parse(_latestApiUrl),
headers: {'Accept': 'application/vnd.github.v3+json'},
@@ -124,7 +121,6 @@ class UpdateChecker {
final name = (asset['name'] as String? ?? '').toLowerCase();
if (name.endsWith('.apk')) {
final downloadUrl = asset['browser_download_url'] as String?;
// Only accept HTTPS URLs for security
final uri = downloadUrl != null ? Uri.tryParse(downloadUrl) : null;
if (uri == null || uri.scheme != 'https') {
_log.w('Skipping non-HTTPS APK URL: $downloadUrl');