feat: auth screen with auto-redirect, sync favorites/history with backend
This commit is contained in:
12
app/src/main/java/com/radiola/domain/model/Recording.kt
Normal file
12
app/src/main/java/com/radiola/domain/model/Recording.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.radiola.domain.model
|
||||
|
||||
data class Recording(
|
||||
val id: Long,
|
||||
val stationName: String,
|
||||
val stationId: Int,
|
||||
val filePath: String,
|
||||
val startTime: Long,
|
||||
val endTime: Long?,
|
||||
val trackName: String?,
|
||||
val duration: Long?
|
||||
)
|
||||
@@ -8,5 +8,6 @@ data class Station(
|
||||
val coverUrl: String,
|
||||
val genre: String,
|
||||
val tags: List<String>,
|
||||
val sortOrder: Int
|
||||
val sortOrder: Int,
|
||||
val source: String = "record"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.radiola.domain.model
|
||||
|
||||
enum class StationTestStatus {
|
||||
OK, // Stream OK + has metadata (Icy or NowPlaying)
|
||||
OK_NO_META, // Stream OK, no metadata
|
||||
OFFLINE, // HTTP error / timeout / no response
|
||||
ERROR // Other exception
|
||||
}
|
||||
|
||||
data class StationTestResult(
|
||||
val stationId: Int,
|
||||
val stationName: String,
|
||||
val streamUrl: String,
|
||||
val status: StationTestStatus,
|
||||
val httpCode: Int? = null,
|
||||
val contentType: String? = null,
|
||||
val hasIcyMetadata: Boolean = false,
|
||||
val icyTitle: String? = null,
|
||||
val hasNowPlaying: Boolean = false,
|
||||
val nowPlayingTrack: String? = null,
|
||||
val errorMessage: String? = null
|
||||
)
|
||||
7
app/src/main/java/com/radiola/domain/model/User.kt
Normal file
7
app/src/main/java/com/radiola/domain/model/User.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.radiola.domain.model
|
||||
|
||||
data class User(
|
||||
val id: String,
|
||||
val email: String,
|
||||
val name: String? = null
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.radiola.domain.repository
|
||||
|
||||
import com.radiola.domain.model.User
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AuthRepository {
|
||||
suspend fun requestMagicLink(email: String): Result<Unit>
|
||||
suspend fun verifyMagicLink(email: String, code: String): Result<User>
|
||||
fun isLoggedIn(): Flow<Boolean>
|
||||
fun currentUser(): Flow<User?>
|
||||
suspend fun logout()
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FavoritesRepository {
|
||||
fun getFavorites(): Flow<List<Station>>
|
||||
fun getFavoriteIds(): Flow<Set<Int>>
|
||||
suspend fun addFavorite(station: Station)
|
||||
suspend fun addFavorite(stationId: Int)
|
||||
suspend fun removeFavorite(stationId: Int)
|
||||
fun isFavorite(stationId: Int): Flow<Boolean>
|
||||
suspend fun reorderFavorites(orderedIds: List<Int>)
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.radiola.domain.model.Track
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NowPlayingRepository {
|
||||
fun getNowPlaying(stationPrefix: String): Flow<Track?>
|
||||
fun getAllNowPlaying(): Flow<Map<String, Track>>
|
||||
fun getNowPlaying(stationId: Int): Flow<Track?>
|
||||
fun getAllNowPlaying(): Flow<Map<Int, Track>>
|
||||
suspend fun refreshNowPlaying(): Result<Unit>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.radiola.domain.repository
|
||||
|
||||
import com.radiola.domain.model.Recording
|
||||
import com.radiola.domain.model.Station
|
||||
import com.radiola.domain.model.Track
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface RecordingRepository {
|
||||
val isRecording: StateFlow<Boolean>
|
||||
fun getRecordings(): Flow<List<Recording>>
|
||||
suspend fun startRecording(station: Station, track: Track?)
|
||||
suspend fun stopRecording()
|
||||
suspend fun deleteRecording(id: Long)
|
||||
}
|
||||
@@ -7,4 +7,5 @@ interface StationRepository {
|
||||
fun getStations(): Flow<List<Station>>
|
||||
suspend fun refreshStations(): Result<Unit>
|
||||
fun getStationById(id: Int): Flow<Station?>
|
||||
fun getTags(): Flow<List<String>>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.radiola.domain.repository
|
||||
|
||||
import com.radiola.domain.model.Station
|
||||
|
||||
interface SyncRepository {
|
||||
suspend fun pushFavorite(stationId: Int): Result<Unit>
|
||||
suspend fun removeFavorite(stationId: Int): Result<Unit>
|
||||
suspend fun pushHistory(stationId: Int): Result<Unit>
|
||||
suspend fun fetchRemoteFavorites(): Result<List<Int>>
|
||||
suspend fun fetchRemoteHistory(): Result<List<Int>>
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import javax.inject.Inject
|
||||
class GetNowPlayingUseCase @Inject constructor(
|
||||
private val nowPlayingRepository: NowPlayingRepository
|
||||
) {
|
||||
operator fun invoke(stationPrefix: String): Flow<Track?> {
|
||||
return nowPlayingRepository.getNowPlaying(stationPrefix)
|
||||
operator fun invoke(stationId: Int): Flow<Track?> {
|
||||
return nowPlayingRepository.getNowPlaying(stationId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.radiola.domain.usecase
|
||||
|
||||
import com.radiola.domain.repository.StationRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class RefreshStationsUseCase @Inject constructor(
|
||||
private val stationRepository: StationRepository
|
||||
) {
|
||||
suspend operator fun invoke(): Result<Unit> = stationRepository.refreshStations()
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.radiola.domain.usecase
|
||||
|
||||
import com.radiola.data.repository.StationTester
|
||||
import com.radiola.domain.model.StationTestResult
|
||||
import com.radiola.domain.repository.NowPlayingRepository
|
||||
import com.radiola.domain.repository.StationRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class TestStationsUseCase @Inject constructor(
|
||||
private val stationRepository: StationRepository,
|
||||
private val nowPlayingRepository: NowPlayingRepository,
|
||||
private val stationTester: StationTester
|
||||
) {
|
||||
|
||||
data class Progress(
|
||||
val current: Int,
|
||||
val total: Int,
|
||||
val result: StationTestResult? = null
|
||||
)
|
||||
|
||||
operator fun invoke(): Flow<Progress> = flow {
|
||||
val stations = stationRepository.getStations().first()
|
||||
|
||||
nowPlayingRepository.refreshNowPlaying()
|
||||
val nowPlayingMap = nowPlayingRepository.getAllNowPlaying().first()
|
||||
|
||||
stations.forEachIndexed { index, station ->
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
stationTester.test(station, nowPlayingMap[station.id])
|
||||
}
|
||||
emit(Progress(current = index + 1, total = stations.size, result = result))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.repository.AuthRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetAuthStateUseCase @Inject constructor(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
operator fun invoke(): Flow<Boolean> = repository.isLoggedIn()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.model.User
|
||||
import com.radiola.domain.repository.AuthRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetCurrentUserUseCase @Inject constructor(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
operator fun invoke(): Flow<User?> = repository.currentUser()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.repository.AuthRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class LogoutUseCase @Inject constructor(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke() = repository.logout()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.repository.SyncRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushFavoriteUseCase @Inject constructor(
|
||||
private val syncRepository: SyncRepository
|
||||
) {
|
||||
suspend operator fun invoke(stationId: Int, isAdding: Boolean): Result<Unit> =
|
||||
if (isAdding) syncRepository.pushFavorite(stationId)
|
||||
else syncRepository.removeFavorite(stationId)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.repository.SyncRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushHistoryUseCase @Inject constructor(
|
||||
private val syncRepository: SyncRepository
|
||||
) {
|
||||
suspend operator fun invoke(stationId: Int): Result<Unit> =
|
||||
syncRepository.pushHistory(stationId)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.repository.AuthRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class RequestMagicLinkUseCase @Inject constructor(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(email: String): Result<Unit> =
|
||||
repository.requestMagicLink(email)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.repository.FavoritesRepository
|
||||
import com.radiola.domain.repository.SyncRepository
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
class SyncFavoritesUseCase @Inject constructor(
|
||||
private val favoritesRepository: FavoritesRepository,
|
||||
private val syncRepository: SyncRepository
|
||||
) {
|
||||
suspend operator fun invoke() {
|
||||
syncRepository.fetchRemoteFavorites()
|
||||
.onSuccess { remoteIds ->
|
||||
val localIds = favoritesRepository.getFavoriteIds().first()
|
||||
// Add remote favorites that are missing locally
|
||||
remoteIds.forEach { id ->
|
||||
if (id !in localIds) {
|
||||
favoritesRepository.addFavorite(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.radiola.domain.usecase.auth
|
||||
|
||||
import com.radiola.domain.repository.AuthRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerifyMagicLinkUseCase @Inject constructor(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(email: String, code: String) =
|
||||
repository.verifyMagicLink(email, code)
|
||||
}
|
||||
Reference in New Issue
Block a user