feat: auth screen with auto-redirect, sync favorites/history with backend

This commit is contained in:
nk
2026-06-02 19:12:07 +03:00
parent d4adb1e7be
commit a83672b455
2934 changed files with 97351 additions and 163 deletions

View File

@@ -8,25 +8,26 @@ import com.radiola.domain.model.Track
object ApiMapper {
fun StationDto.toDomain(): Station {
val cover = iconPng ?: iconSvg ?: ""
val cover = iconFillColored ?: bgImageMobile ?: bgImage ?: ""
val stream = stream128 ?: stream320 ?: streamHls ?: "https://air.radiorecord.ru:805/${prefix}_128"
return Station(
id = id,
name = name,
prefix = prefix,
streamUrl = "https://air.radiorecord.ru:805/${prefix}_128",
streamUrl = stream,
coverUrl = cover,
genre = genre ?: "",
tags = emptyList(),
sortOrder = id
genre = tooltip ?: "",
tags = tags.map { it.name },
sortOrder = sort
)
}
fun NowPlayingItemDto.toDomain(): Track {
return Track(
artist = artist,
song = song,
coverUrl = image600 ?: image100,
stationName = prefix
artist = track.artist,
song = track.song,
coverUrl = track.image600 ?: track.image100,
stationName = ""
)
}
}

View File

@@ -0,0 +1,25 @@
package com.radiola.data.remote
import com.radiola.data.local.TokenDataStore
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
class AuthInterceptor @Inject constructor(
private val tokenDataStore: TokenDataStore
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val token = tokenDataStore.currentToken
return if (token != null) {
chain.proceed(
request.newBuilder()
.header("Authorization", "Bearer $token")
.build()
)
} else {
chain.proceed(request)
}
}
}

View File

@@ -0,0 +1,48 @@
package com.radiola.data.remote
import com.radiola.data.remote.dto.AuthResponseDto
import com.radiola.data.remote.dto.BackendStationDto
import com.radiola.data.remote.dto.HistoryResponseDto
import com.radiola.data.remote.dto.MagicLinkRequestDto
import com.radiola.data.remote.dto.MagicLinkVerifyDto
import com.radiola.data.remote.dto.UserSettingsDto
import kotlinx.serialization.json.JsonObject
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PATCH
import retrofit2.http.Path
interface RadiolaApi {
@POST("auth/magic-link")
suspend fun requestMagicLink(@Body dto: MagicLinkRequestDto): JsonObject
@POST("auth/verify")
suspend fun verifyMagicLink(@Body dto: MagicLinkVerifyDto): AuthResponseDto
@GET("users/me")
suspend fun getMe(): JsonObject
@GET("users/me/settings")
suspend fun getSettings(): UserSettingsDto
@PATCH("users/me/settings")
suspend fun updateSettings(@Body dto: UserSettingsDto): UserSettingsDto
@GET("users/me/favorites")
suspend fun getFavorites(): List<BackendStationDto>
@POST("users/me/favorites/{stationId}")
suspend fun addFavorite(@Path("stationId") stationId: String): JsonObject
@DELETE("users/me/favorites/{stationId}")
suspend fun removeFavorite(@Path("stationId") stationId: String): JsonObject
@GET("users/me/history")
suspend fun getHistory(): HistoryResponseDto
@POST("users/me/history/{stationId}")
suspend fun addHistory(@Path("stationId") stationId: String): JsonObject
}

View File

@@ -0,0 +1,9 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class AuthResponseDto(
val accessToken: String,
val user: UserDto
)

View File

@@ -0,0 +1,18 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class BackendStationDto(
val id: String,
val stationId: Int,
val name: String,
val prefix: String,
val streamUrl: String,
val coverUrl: String? = null,
val genre: String? = null,
val tags: List<String> = emptyList(),
val sortOrder: Int = 0,
val source: String = "local",
val isOnline: Boolean = true
)

View File

@@ -0,0 +1,8 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class FavoritesResponseDto(
val favorites: List<BackendStationDto> = emptyList()
)

View File

@@ -0,0 +1,11 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class HistoryResponseDto(
val items: List<BackendStationDto> = emptyList(),
val total: Int = 0,
val limit: Int = 50,
val offset: Int = 0
)

View File

@@ -0,0 +1,8 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class MagicLinkRequestDto(
val email: String
)

View File

@@ -0,0 +1,9 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class MagicLinkVerifyDto(
val email: String,
val code: String
)

View File

@@ -4,15 +4,20 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class NowPlayingItemDto(
data class TrackDto(
@SerialName("id") val id: Int,
@SerialName("prefix") val prefix: String,
@SerialName("artist") val artist: String,
@SerialName("song") val song: String,
@SerialName("image600") val image600: String? = null,
@SerialName("image100") val image100: String? = null
)
@Serializable
data class NowPlayingItemDto(
@SerialName("id") val id: Int,
@SerialName("track") val track: TrackDto
)
@Serializable
data class NowPlayingResponse(
@SerialName("result") val result: List<NowPlayingItemDto>

View File

@@ -8,12 +8,30 @@ data class StationDto(
@SerialName("id") val id: Int,
@SerialName("title") val name: String,
@SerialName("prefix") val prefix: String,
@SerialName("genre") val genre: String? = null,
@SerialName("icon_png") val iconPng: String? = null,
@SerialName("icon_svg") val iconSvg: String? = null
@SerialName("tooltip") val tooltip: String? = null,
@SerialName("sort") val sort: Int = 0,
@SerialName("bg_image") val bgImage: String? = null,
@SerialName("bg_image_mobile") val bgImageMobile: String? = null,
@SerialName("icon_fill_colored") val iconFillColored: String? = null,
@SerialName("stream_128") val stream128: String? = null,
@SerialName("stream_320") val stream320: String? = null,
@SerialName("stream_hls") val streamHls: String? = null,
@SerialName("tags") val tags: List<TagDto> = emptyList()
)
@Serializable
data class TagDto(
@SerialName("id") val id: Int,
@SerialName("name") val name: String
)
@Serializable
data class StationsResult(
@SerialName("stations") val stations: List<StationDto> = emptyList(),
@SerialName("tags") val tags: List<TagDto> = emptyList()
)
@Serializable
data class StationsResponse(
@SerialName("result") val result: List<StationDto>
@SerialName("result") val result: StationsResult
)

View File

@@ -0,0 +1,10 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class UserDto(
val id: String,
val email: String,
val name: String? = null
)

View File

@@ -0,0 +1,12 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
@Serializable
data class UserSettingsDto(
val theme: String? = null,
val language: String? = null,
val autoPlay: Boolean? = null,
val showOffline: Boolean? = null,
val sleepTimerMinutes: Int? = null
)