Initial commit: SpotiFLAC Android/iOS app

This commit is contained in:
zarzet committed 2026-01-01 19:28:15 +07:00
commit 3fdcf6d5b7
126 files changed
+14079

No files matched your search

@@ -0,0 +1,5 @@
package com.example.temp_project
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -0,0 +1,87 @@
package com.zarz.spotiflac
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
class DownloadService : Service() {
companion object {
const val CHANNEL_ID = "spotiflac_download_channel"
const val NOTIFICATION_ID = 1
const val ACTION_START = "com.zarz.spotiflac.START_DOWNLOAD"
const val ACTION_STOP = "com.zarz.spotiflac.STOP_DOWNLOAD"
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_START -> startForegroundService()
ACTION_STOP -> stopSelf()
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Download Progress",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Shows download progress for SpotiFLAC"
setShowBadge(false)
}
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
private fun startForegroundService() {
val notification = createNotification("Downloading...", 0)
startForeground(NOTIFICATION_ID, notification)
}
fun updateProgress(trackName: String, progress: Int) {
val notification = createNotification(trackName, progress)
val manager = getSystemService(NotificationManager::class.java)
manager.notify(NOTIFICATION_ID, notification)
}
private fun createNotification(title: String, progress: Int): Notification {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val stopIntent = Intent(this, DownloadService::class.java).apply {
action = ACTION_STOP
}
val stopPendingIntent = PendingIntent.getService(
this, 0, stopIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("SpotiFLAC")
.setContentText(title)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setProgress(100, progress, progress == 0)
.setOngoing(true)
.setContentIntent(pendingIntent)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Cancel", stopPendingIntent)
.build()
}
}
@@ -0,0 +1,138 @@
package com.zarz.spotiflac
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import gobackend.Gobackend
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.zarz.spotiflac/backend"
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
scope.launch {
try {
when (call.method) {
"parseSpotifyUrl" -> {
val url = call.argument<String>("url") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.parseSpotifyURL(url)
}
result.success(response)
}
"getSpotifyMetadata" -> {
val url = call.argument<String>("url") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.getSpotifyMetadata(url)
}
result.success(response)
}
"searchSpotify" -> {
val query = call.argument<String>("query") ?: ""
val limit = call.argument<Int>("limit") ?: 10
val response = withContext(Dispatchers.IO) {
Gobackend.searchSpotify(query, limit.toLong())
}
result.success(response)
}
"checkAvailability" -> {
val spotifyId = call.argument<String>("spotify_id") ?: ""
val isrc = call.argument<String>("isrc") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.checkAvailability(spotifyId, isrc)
}
result.success(response)
}
"downloadTrack" -> {
val requestJson = call.arguments as String
val response = withContext(Dispatchers.IO) {
Gobackend.downloadTrack(requestJson)
}
result.success(response)
}
"downloadWithFallback" -> {
val requestJson = call.arguments as String
val response = withContext(Dispatchers.IO) {
Gobackend.downloadWithFallback(requestJson)
}
result.success(response)
}
"getDownloadProgress" -> {
val response = withContext(Dispatchers.IO) {
Gobackend.getDownloadProgress()
}
result.success(response)
}
"setDownloadDirectory" -> {
val path = call.argument<String>("path") ?: ""
withContext(Dispatchers.IO) {
Gobackend.setDownloadDirectory(path)
}
result.success(null)
}
"checkDuplicate" -> {
val outputDir = call.argument<String>("output_dir") ?: ""
val isrc = call.argument<String>("isrc") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.checkDuplicate(outputDir, isrc)
}
result.success(response)
}
"buildFilename" -> {
val template = call.argument<String>("template") ?: ""
val metadata = call.argument<String>("metadata") ?: "{}"
val response = withContext(Dispatchers.IO) {
Gobackend.buildFilename(template, metadata)
}
result.success(response)
}
"sanitizeFilename" -> {
val filename = call.argument<String>("filename") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.sanitizeFilename(filename)
}
result.success(response)
}
"fetchLyrics" -> {
val spotifyId = call.argument<String>("spotify_id") ?: ""
val trackName = call.argument<String>("track_name") ?: ""
val artistName = call.argument<String>("artist_name") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.fetchLyrics(spotifyId, trackName, artistName)
}
result.success(response)
}
"getLyricsLRC" -> {
val spotifyId = call.argument<String>("spotify_id") ?: ""
val trackName = call.argument<String>("track_name") ?: ""
val artistName = call.argument<String>("artist_name") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.getLyricsLRC(spotifyId, trackName, artistName)
}
result.success(response)
}
"embedLyricsToFile" -> {
val filePath = call.argument<String>("file_path") ?: ""
val lyrics = call.argument<String>("lyrics") ?: ""
val response = withContext(Dispatchers.IO) {
Gobackend.embedLyricsToFile(filePath, lyrics)
}
result.success(response)
}
else -> result.notImplemented()
}
} catch (e: Exception) {
result.error("ERROR", e.message, null)
}
}
}
}
}