feat: auth screen with auto-redirect, sync favorites/history with backend
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package com.radiola.data.repository
|
||||
|
||||
import com.radiola.data.local.TokenDataStore
|
||||
import com.radiola.data.remote.RadiolaApi
|
||||
import com.radiola.data.remote.dto.MagicLinkRequestDto
|
||||
import com.radiola.data.remote.dto.MagicLinkVerifyDto
|
||||
import com.radiola.domain.model.User
|
||||
import com.radiola.domain.repository.AuthRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthRepositoryImpl @Inject constructor(
|
||||
private val api: RadiolaApi,
|
||||
private val tokenDataStore: TokenDataStore
|
||||
) : AuthRepository {
|
||||
|
||||
override suspend fun requestMagicLink(email: String): Result<Unit> = try {
|
||||
api.requestMagicLink(MagicLinkRequestDto(email))
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
|
||||
override suspend fun verifyMagicLink(email: String, code: String): Result<User> = try {
|
||||
val response = api.verifyMagicLink(MagicLinkVerifyDto(email, code))
|
||||
tokenDataStore.saveToken(
|
||||
token = response.accessToken,
|
||||
userId = response.user.id,
|
||||
email = response.user.email,
|
||||
name = response.user.name
|
||||
)
|
||||
Result.success(
|
||||
User(
|
||||
id = response.user.id,
|
||||
email = response.user.email,
|
||||
name = response.user.name
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
|
||||
override fun isLoggedIn(): Flow<Boolean> = tokenDataStore.isLoggedIn
|
||||
|
||||
override fun currentUser(): Flow<User?> = combine(
|
||||
tokenDataStore.userId,
|
||||
tokenDataStore.userEmail,
|
||||
tokenDataStore.userName
|
||||
) { id, email, name ->
|
||||
if (id != null && email != null) {
|
||||
User(id = id, email = email, name = name)
|
||||
} else null
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
tokenDataStore.clear()
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,18 @@ class FavoritesRepositoryImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFavoriteIds(): Flow<Set<Int>> {
|
||||
return db.stationDao().getFavoriteIds().map { it.toSet() }
|
||||
}
|
||||
|
||||
override suspend fun addFavorite(station: Station) {
|
||||
db.stationDao().setFavorite(station.id, true)
|
||||
}
|
||||
|
||||
override suspend fun addFavorite(stationId: Int) {
|
||||
db.stationDao().setFavorite(stationId, true)
|
||||
}
|
||||
|
||||
override suspend fun removeFavorite(stationId: Int) {
|
||||
db.stationDao().setFavorite(stationId, false)
|
||||
}
|
||||
@@ -42,6 +50,7 @@ class FavoritesRepositoryImpl @Inject constructor(
|
||||
coverUrl = coverUrl,
|
||||
genre = genre,
|
||||
tags = tags.split(",").filter { it.isNotBlank() },
|
||||
sortOrder = sortOrder
|
||||
sortOrder = sortOrder,
|
||||
source = source
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,18 +13,18 @@ class NowPlayingRepositoryImpl @Inject constructor(
|
||||
private val api: RecordApi
|
||||
) : NowPlayingRepository {
|
||||
|
||||
private val _nowPlaying = MutableStateFlow<Map<String, Track>>(emptyMap())
|
||||
private val _nowPlaying = MutableStateFlow<Map<Int, Track>>(emptyMap())
|
||||
|
||||
override fun getNowPlaying(stationPrefix: String): Flow<Track?> {
|
||||
return _nowPlaying.map { it[stationPrefix] }
|
||||
override fun getNowPlaying(stationId: Int): Flow<Track?> {
|
||||
return _nowPlaying.map { it[stationId] }
|
||||
}
|
||||
|
||||
override fun getAllNowPlaying(): Flow<Map<String, Track>> = _nowPlaying
|
||||
override fun getAllNowPlaying(): Flow<Map<Int, Track>> = _nowPlaying
|
||||
|
||||
override suspend fun refreshNowPlaying(): Result<Unit> {
|
||||
return try {
|
||||
val response = api.getNowPlaying()
|
||||
val map = response.result.associate { it.prefix to it.toDomain() }
|
||||
val map = response.result.associate { it.id to it.toDomain() }
|
||||
_nowPlaying.value = map
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.radiola.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.radiola.data.local.AppDatabase
|
||||
import com.radiola.data.local.entity.RecordingEntity
|
||||
import com.radiola.domain.model.Recording
|
||||
import com.radiola.domain.model.Station
|
||||
import com.radiola.domain.model.Track
|
||||
import com.radiola.domain.repository.RecordingRepository
|
||||
import com.radiola.service.RecordingService
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
class RecordingRepositoryImpl @Inject constructor(
|
||||
private val db: AppDatabase,
|
||||
private val okHttpClient: OkHttpClient,
|
||||
@ApplicationContext private val context: Context
|
||||
) : RecordingRepository {
|
||||
|
||||
private val _isRecording = kotlinx.coroutines.flow.MutableStateFlow(false)
|
||||
override val isRecording: StateFlow<Boolean> = _isRecording.asStateFlow()
|
||||
|
||||
private var recordingJob: Job? = null
|
||||
private var currentCall: okhttp3.Call? = null
|
||||
private var currentRecordingId: Long? = null
|
||||
|
||||
override fun getRecordings(): Flow<List<Recording>> {
|
||||
return db.recordingDao().getAll().map { entities ->
|
||||
entities.map { it.toDomain() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun startRecording(station: Station, track: Track?) {
|
||||
if (_isRecording.value) return
|
||||
|
||||
val id = System.currentTimeMillis()
|
||||
currentRecordingId = id
|
||||
|
||||
val dir = File(context.getExternalFilesDir(Environment.DIRECTORY_MUSIC), "radiola_recordings")
|
||||
dir.mkdirs()
|
||||
|
||||
val ext = when {
|
||||
station.streamUrl.contains(".aac", ignoreCase = true) -> "aac"
|
||||
station.streamUrl.contains(".mp3", ignoreCase = true) -> "mp3"
|
||||
else -> "audio"
|
||||
}
|
||||
val safeName = station.name.replace(Regex("[^a-zA-Z0-9а-яА-ЯёЁ]"), "_").take(30)
|
||||
val file = File(dir, "${safeName}_${id}.$ext")
|
||||
|
||||
val entity = RecordingEntity(
|
||||
id = id,
|
||||
stationName = station.name,
|
||||
stationId = station.id,
|
||||
filePath = file.absolutePath,
|
||||
startTime = id,
|
||||
endTime = null,
|
||||
trackName = track?.let { "${it.artist} - ${it.song}" },
|
||||
duration = null
|
||||
)
|
||||
db.recordingDao().insert(entity)
|
||||
_isRecording.value = true
|
||||
|
||||
// Start foreground service to keep process alive during recording
|
||||
val serviceIntent = Intent(context, RecordingService::class.java).apply {
|
||||
putExtra(RecordingService.EXTRA_STATION_NAME, station.name)
|
||||
}
|
||||
ContextCompat.startForegroundService(context, serviceIntent)
|
||||
|
||||
recordingJob = CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
|
||||
var output: FileOutputStream? = null
|
||||
try {
|
||||
val request = Request.Builder().url(station.streamUrl).build()
|
||||
val call = okHttpClient.newCall(request)
|
||||
currentCall = call
|
||||
val response = call.execute()
|
||||
|
||||
if (!response.isSuccessful) {
|
||||
Log.e("RecordingRepo", "HTTP error: ${response.code}")
|
||||
return@launch
|
||||
}
|
||||
|
||||
output = FileOutputStream(file)
|
||||
val input = response.body?.byteStream()
|
||||
if (input == null) {
|
||||
Log.e("RecordingRepo", "Empty response body")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val buffer = ByteArray(8192)
|
||||
var bytesRead: Int
|
||||
while (isActive) {
|
||||
bytesRead = input.read(buffer)
|
||||
if (bytesRead == -1) break
|
||||
output.write(buffer, 0, bytesRead)
|
||||
}
|
||||
input.close()
|
||||
} catch (e: IOException) {
|
||||
if (e.message?.contains("Canceled") == true) {
|
||||
Log.d("RecordingRepo", "Recording cancelled normally")
|
||||
} else {
|
||||
Log.e("RecordingRepo", "Recording error", e)
|
||||
}
|
||||
} finally {
|
||||
try { output?.close() } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stopRecording() {
|
||||
currentCall?.cancel()
|
||||
currentCall = null
|
||||
recordingJob?.cancelAndJoin()
|
||||
recordingJob = null
|
||||
_isRecording.value = false
|
||||
|
||||
// Stop foreground service
|
||||
context.stopService(Intent(context, RecordingService::class.java))
|
||||
|
||||
currentRecordingId?.let { id ->
|
||||
val endTime = System.currentTimeMillis()
|
||||
val duration = endTime - id
|
||||
try {
|
||||
db.recordingDao().updateEndTime(id, endTime, duration)
|
||||
} catch (e: Exception) {
|
||||
Log.e("RecordingRepo", "Failed to update recording end time", e)
|
||||
}
|
||||
}
|
||||
currentRecordingId = null
|
||||
}
|
||||
|
||||
override suspend fun deleteRecording(id: Long) {
|
||||
val entity = db.recordingDao().getById(id)
|
||||
entity?.let {
|
||||
try { File(it.filePath).delete() } catch (_: Exception) {}
|
||||
}
|
||||
db.recordingDao().deleteById(id)
|
||||
}
|
||||
|
||||
private fun RecordingEntity.toDomain(): Recording = Recording(
|
||||
id = id,
|
||||
stationName = stationName,
|
||||
stationId = stationId,
|
||||
filePath = filePath,
|
||||
startTime = startTime,
|
||||
endTime = endTime,
|
||||
trackName = trackName,
|
||||
duration = duration
|
||||
)
|
||||
}
|
||||
@@ -32,20 +32,21 @@ class SettingsRepositoryImpl @Inject constructor(
|
||||
}
|
||||
|
||||
override fun getLastStationId(): Flow<Int?> = dataStore.data.map { it[LAST_STATION_ID] }
|
||||
override suspend fun setLastStationId(id: Int) = dataStore.edit { it[LAST_STATION_ID] = id }
|
||||
override suspend fun setLastStationId(id: Int) { dataStore.edit { it[LAST_STATION_ID] = id } }
|
||||
|
||||
override fun getSleepTimerMinutes(): Flow<Int> = dataStore.data.map { it[SLEEP_TIMER] ?: 30 }
|
||||
override suspend fun setSleepTimerMinutes(minutes: Int) = dataStore.edit { it[SLEEP_TIMER] = minutes }
|
||||
override suspend fun setSleepTimerMinutes(minutes: Int) { dataStore.edit { it[SLEEP_TIMER] = minutes } }
|
||||
|
||||
override fun getEnabledDeeplinkServices(): Flow<Set<String>> = dataStore.data.map {
|
||||
it[ENABLED_SERVICES] ?: setOf("yandex", "vk", "spotify", "apple", "youtube")
|
||||
}
|
||||
override suspend fun setEnabledDeeplinkServices(serviceIds: Set<String>) =
|
||||
override suspend fun setEnabledDeeplinkServices(serviceIds: Set<String>) {
|
||||
dataStore.edit { it[ENABLED_SERVICES] = serviceIds }
|
||||
}
|
||||
|
||||
override fun getEqualizerPreset(): Flow<String> = dataStore.data.map { it[EQUALIZER_PRESET] ?: "Flat" }
|
||||
override suspend fun setEqualizerPreset(preset: String) = dataStore.edit { it[EQUALIZER_PRESET] = preset }
|
||||
override suspend fun setEqualizerPreset(preset: String) { dataStore.edit { it[EQUALIZER_PRESET] = preset } }
|
||||
|
||||
override fun isRecordingEnabled(): Flow<Boolean> = dataStore.data.map { it[RECORDING_ENABLED] ?: false }
|
||||
override suspend fun setRecordingEnabled(enabled: Boolean) = dataStore.edit { it[RECORDING_ENABLED] = enabled }
|
||||
override suspend fun setRecordingEnabled(enabled: Boolean) { dataStore.edit { it[RECORDING_ENABLED] = enabled } }
|
||||
}
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
package com.radiola.data.repository
|
||||
|
||||
import com.radiola.data.local.LocalStationDataSource
|
||||
import com.radiola.data.local.AppDatabase
|
||||
import com.radiola.data.local.entity.StationEntity
|
||||
import com.radiola.data.local.entity.TagEntity
|
||||
import com.radiola.data.remote.RecordApi
|
||||
import com.radiola.data.remote.ApiMapper.toDomain
|
||||
import com.radiola.domain.model.Station
|
||||
import com.radiola.domain.repository.StationRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
class StationRepositoryImpl @Inject constructor(
|
||||
private val api: RecordApi,
|
||||
private val db: AppDatabase
|
||||
private val db: AppDatabase,
|
||||
private val localDataSource: LocalStationDataSource
|
||||
) : StationRepository {
|
||||
|
||||
private val _tags = MutableStateFlow<List<String>>(emptyList())
|
||||
|
||||
override fun getStations(): Flow<List<Station>> {
|
||||
return db.stationDao().getAll().map { entities ->
|
||||
entities.map { it.toDomain() }
|
||||
@@ -22,29 +29,73 @@ class StationRepositoryImpl @Inject constructor(
|
||||
}
|
||||
|
||||
override suspend fun refreshStations(): Result<Unit> {
|
||||
android.util.Log.d("StationRepo", "refreshStations() called")
|
||||
return try {
|
||||
val response = api.getStations()
|
||||
val entities = response.result.mapIndexed { index, dto ->
|
||||
val domain = dto.toDomain()
|
||||
// 1. Load local stations from assets
|
||||
val localStations = localDataSource.loadStations()
|
||||
android.util.Log.d("StationRepo", "Loaded ${localStations.size} local stations")
|
||||
val localGroups = localDataSource.loadGroups()
|
||||
|
||||
// 2. Try to enrich with Record API data (covers, streams, tags)
|
||||
val apiResponse = try { api.getStations() } catch (e: Exception) { null }
|
||||
val apiStations = apiResponse?.result?.stations ?: emptyList()
|
||||
val apiTags = apiResponse?.result?.tags?.map { it.name } ?: emptyList()
|
||||
|
||||
// 3. Merge: local stations enriched with API data where IDs match
|
||||
val merged = localStations.map { local ->
|
||||
val apiStation = apiStations.find { it.id == local.id }
|
||||
if (apiStation != null) {
|
||||
val domain = apiStation.toDomain()
|
||||
local.copy(
|
||||
coverUrl = domain.coverUrl,
|
||||
streamUrl = domain.streamUrl,
|
||||
genre = local.genre, // keep group name for filtering
|
||||
tags = (local.tags + domain.tags).distinct(), // merge tags
|
||||
prefix = domain.prefix,
|
||||
source = "record"
|
||||
)
|
||||
} else {
|
||||
local
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Save to DB
|
||||
android.util.Log.d("StationRepo", "Saving ${merged.size} merged stations to DB")
|
||||
val entities = merged.mapIndexed { index, station ->
|
||||
StationEntity(
|
||||
id = domain.id,
|
||||
name = domain.name,
|
||||
prefix = domain.prefix,
|
||||
streamUrl = domain.streamUrl,
|
||||
coverUrl = domain.coverUrl,
|
||||
genre = domain.genre,
|
||||
tags = domain.tags.joinToString(","),
|
||||
id = station.id,
|
||||
name = station.name,
|
||||
prefix = station.prefix,
|
||||
streamUrl = station.streamUrl,
|
||||
coverUrl = station.coverUrl,
|
||||
genre = station.genre,
|
||||
tags = station.tags.joinToString(","),
|
||||
sortOrder = index,
|
||||
source = station.source,
|
||||
isFavorite = false
|
||||
)
|
||||
}
|
||||
db.stationDao().insertAll(entities)
|
||||
android.util.Log.d("StationRepo", "Inserted ${entities.size} stations into DB")
|
||||
|
||||
// 5. Update tags: group names + API tags
|
||||
val groupNames = localGroups.map { it.name }.filter { it.isNotBlank() }
|
||||
val allTags = (groupNames + apiTags).distinct().sorted()
|
||||
db.tagDao().clearAll()
|
||||
db.tagDao().insertAll(allTags.map { TagEntity(it) })
|
||||
_tags.value = allTags
|
||||
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("StationRepo", "refreshStations() failed", e)
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTags(): Flow<List<String>> {
|
||||
return _tags.asStateFlow()
|
||||
}
|
||||
|
||||
override fun getStationById(id: Int): Flow<Station?> {
|
||||
return db.stationDao().getById(id).map { it?.toDomain() }
|
||||
}
|
||||
@@ -57,6 +108,7 @@ class StationRepositoryImpl @Inject constructor(
|
||||
coverUrl = coverUrl,
|
||||
genre = genre,
|
||||
tags = tags.split(",").filter { it.isNotBlank() },
|
||||
sortOrder = sortOrder
|
||||
sortOrder = sortOrder,
|
||||
source = source
|
||||
)
|
||||
}
|
||||
|
||||
116
app/src/main/java/com/radiola/data/repository/StationTester.kt
Normal file
116
app/src/main/java/com/radiola/data/repository/StationTester.kt
Normal file
@@ -0,0 +1,116 @@
|
||||
package com.radiola.data.repository
|
||||
|
||||
import com.radiola.domain.model.Station
|
||||
import com.radiola.domain.model.StationTestResult
|
||||
import com.radiola.domain.model.StationTestStatus
|
||||
import com.radiola.domain.model.Track
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.Buffer
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class StationTester @Inject constructor(
|
||||
private val okHttpClient: OkHttpClient
|
||||
) {
|
||||
|
||||
suspend fun test(station: Station, nowPlaying: Track?): StationTestResult {
|
||||
val testClient = okHttpClient.newBuilder()
|
||||
.connectTimeout(8, TimeUnit.SECONDS)
|
||||
.readTimeout(8, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(station.streamUrl)
|
||||
.header("Icy-Metadata", "1")
|
||||
.build()
|
||||
|
||||
return try {
|
||||
testClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return StationTestResult(
|
||||
stationId = station.id,
|
||||
stationName = station.name,
|
||||
streamUrl = station.streamUrl,
|
||||
status = StationTestStatus.OFFLINE,
|
||||
httpCode = response.code,
|
||||
errorMessage = "HTTP ${response.code}"
|
||||
)
|
||||
}
|
||||
|
||||
val contentType = response.header("Content-Type")
|
||||
val hasAudio = contentType?.startsWith("audio") == true
|
||||
val metaint = response.header("icy-metaint")?.toIntOrNull()
|
||||
val hasIcy = metaint != null
|
||||
|
||||
val icyTitle = if (hasIcy && metaint != null) {
|
||||
readIcyTitle(response, metaint)
|
||||
} else null
|
||||
|
||||
StationTestResult(
|
||||
stationId = station.id,
|
||||
stationName = station.name,
|
||||
streamUrl = station.streamUrl,
|
||||
status = when {
|
||||
icyTitle != null || nowPlaying != null -> StationTestStatus.OK
|
||||
hasAudio -> StationTestStatus.OK_NO_META
|
||||
else -> StationTestStatus.OK_NO_META
|
||||
},
|
||||
httpCode = response.code,
|
||||
contentType = contentType,
|
||||
hasIcyMetadata = hasIcy,
|
||||
icyTitle = icyTitle,
|
||||
hasNowPlaying = nowPlaying != null,
|
||||
nowPlayingTrack = nowPlaying?.let { "${it.artist} - ${it.song}" },
|
||||
errorMessage = null
|
||||
)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
StationTestResult(
|
||||
stationId = station.id,
|
||||
stationName = station.name,
|
||||
streamUrl = station.streamUrl,
|
||||
status = StationTestStatus.OFFLINE,
|
||||
errorMessage = e.message ?: "Network error"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
StationTestResult(
|
||||
stationId = station.id,
|
||||
stationName = station.name,
|
||||
streamUrl = station.streamUrl,
|
||||
status = StationTestStatus.ERROR,
|
||||
errorMessage = e.message ?: "Unknown error"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readIcyTitle(response: okhttp3.Response, metaint: Int): String? {
|
||||
return try {
|
||||
val source = response.body?.source() ?: return null
|
||||
val buffer = Buffer()
|
||||
|
||||
var skipped = 0L
|
||||
while (skipped < metaint) {
|
||||
val toSkip = (metaint - skipped).coerceAtMost(8192)
|
||||
val actual = source.read(buffer, toSkip)
|
||||
if (actual == -1L) return null
|
||||
skipped += actual
|
||||
}
|
||||
buffer.clear()
|
||||
|
||||
val metaLengthByte = source.readByte().toInt() and 0xFF
|
||||
if (metaLengthByte == 0) return null
|
||||
|
||||
val metaBytes = source.readByteArray((metaLengthByte * 16).toLong())
|
||||
val metadata = String(metaBytes, Charsets.UTF_8).trim('\u0000')
|
||||
|
||||
val regex = Regex("StreamTitle='([^']*)'")
|
||||
regex.find(metadata)?.groupValues?.get(1)?.trim()?.takeIf { it.isNotBlank() }
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.radiola.data.repository
|
||||
|
||||
import com.radiola.data.local.TokenDataStore
|
||||
import com.radiola.data.remote.RadiolaApi
|
||||
import com.radiola.domain.repository.SyncRepository
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SyncRepositoryImpl @Inject constructor(
|
||||
private val api: RadiolaApi,
|
||||
private val tokenDataStore: TokenDataStore
|
||||
) : SyncRepository {
|
||||
|
||||
private fun isLoggedIn(): Boolean = tokenDataStore.currentToken != null
|
||||
|
||||
override suspend fun pushFavorite(stationId: Int): Result<Unit> {
|
||||
if (!isLoggedIn()) return Result.success(Unit)
|
||||
return try {
|
||||
api.addFavorite(stationId.toString())
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeFavorite(stationId: Int): Result<Unit> {
|
||||
if (!isLoggedIn()) return Result.success(Unit)
|
||||
return try {
|
||||
api.removeFavorite(stationId.toString())
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun pushHistory(stationId: Int): Result<Unit> {
|
||||
if (!isLoggedIn()) return Result.success(Unit)
|
||||
return try {
|
||||
api.addHistory(stationId.toString())
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchRemoteFavorites(): Result<List<Int>> {
|
||||
if (!isLoggedIn()) return Result.success(emptyList())
|
||||
return try {
|
||||
val stations = api.getFavorites()
|
||||
Result.success(stations.map { it.stationId })
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchRemoteHistory(): Result<List<Int>> {
|
||||
if (!isLoggedIn()) return Result.success(emptyList())
|
||||
return try {
|
||||
val response = api.getHistory()
|
||||
Result.success(response.items.map { it.stationId })
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user