diff --git a/androidApp/src/main/java/org/blackcandy/android/compose/player/FullPlayer.kt b/androidApp/src/main/java/org/blackcandy/android/compose/player/FullPlayer.kt index 83ea798..05b3a14 100644 --- a/androidApp/src/main/java/org/blackcandy/android/compose/player/FullPlayer.kt +++ b/androidApp/src/main/java/org/blackcandy/android/compose/player/FullPlayer.kt @@ -101,7 +101,7 @@ fun PlayerHorizontalLayout( horizontalArrangement = Arrangement.Center, ) { PlayerArt( - imageURL = currentSong?.albumImageUrl?.large, + imageURL = currentSong?.albumImageUrls?.large, size = dimensionResource(R.dimen.player_album_cover_large_size), modifier = Modifier @@ -183,7 +183,7 @@ fun PlayerVerticalLayout( horizontalAlignment = Alignment.CenterHorizontally, ) { PlayerArt( - imageURL = currentSong?.albumImageUrl?.large, + imageURL = currentSong?.albumImageUrls?.large, size = dimensionResource(if (isExpandedHeight) R.dimen.player_album_cover_large_size else R.dimen.player_album_cover_size), ) diff --git a/androidApp/src/main/java/org/blackcandy/android/compose/player/MiniPlayer.kt b/androidApp/src/main/java/org/blackcandy/android/compose/player/MiniPlayer.kt index 6cef903..6b4bfa1 100644 --- a/androidApp/src/main/java/org/blackcandy/android/compose/player/MiniPlayer.kt +++ b/androidApp/src/main/java/org/blackcandy/android/compose/player/MiniPlayer.kt @@ -53,7 +53,7 @@ fun MiniPlayer( ) { if (musicState.hasCurrentSong) { AsyncImage( - model = musicState.currentSong!!.albumImageUrl.small, + model = musicState.currentSong!!.albumImageUrls.small, contentDescription = stringResource(R.string.album_cover), modifier = Modifier diff --git a/androidApp/src/main/java/org/blackcandy/android/fragments/web/WebFragment.kt b/androidApp/src/main/java/org/blackcandy/android/fragments/web/WebFragment.kt index 926e2dd..1ac1de9 100644 --- a/androidApp/src/main/java/org/blackcandy/android/fragments/web/WebFragment.kt +++ b/androidApp/src/main/java/org/blackcandy/android/fragments/web/WebFragment.kt @@ -1,15 +1,16 @@ package org.blackcandy.android.fragments.web +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView +import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.accompanist.themeadapter.material3.Mdc3Theme -import dev.hotwire.core.turbo.errors.HttpError import dev.hotwire.core.turbo.errors.VisitError import dev.hotwire.navigation.destinations.HotwireDestinationDeepLink import dev.hotwire.navigation.fragments.HotwireWebFragment @@ -17,6 +18,7 @@ import kotlinx.coroutines.launch import org.blackcandy.android.R import org.blackcandy.android.compose.CustomErrorScreen import org.blackcandy.android.utils.SnackbarUtil.Companion.showSnackbar +import org.blackcandy.shared.utils.NEW_SESSION_PATH import org.blackcandy.shared.viewmodels.WebViewModel import org.koin.androidx.viewmodel.ext.android.viewModel @@ -46,14 +48,11 @@ open class WebFragment : HotwireWebFragment() { savedInstanceState: Bundle?, ): View? = inflater.inflate(R.layout.fragment_web, container, false) - override fun onVisitErrorReceived( - location: String, - error: VisitError, - ) { - if (error is HttpError.ClientError.Unauthorized) { + override fun onVisitStarted(location: String) { + super.onVisitStarted(location) + + if (location.toUri().path == NEW_SESSION_PATH) { viewModel.logout() - } else { - super.onVisitErrorReceived(location, error) } } diff --git a/iosApp/iosApp/ViewControllers/WebViewController.swift b/iosApp/iosApp/ViewControllers/WebViewController.swift index 8be7b48..b9fda7c 100644 --- a/iosApp/iosApp/ViewControllers/WebViewController.swift +++ b/iosApp/iosApp/ViewControllers/WebViewController.swift @@ -31,4 +31,14 @@ class WebViewController: HotwireWebViewController { deinit { observationTask?.cancel() } + + override func visitableDidRender() { + super.visitableDidRender() + + if currentVisitableURL.path == NEW_SESSION_PATH { + viewModel.logout(onSuccess: { + changeRootViewController(viewController: LoginViewController()) + }) + } + } } diff --git a/iosApp/iosApp/Views/Player/FullPlayer.swift b/iosApp/iosApp/Views/Player/FullPlayer.swift index 4896c6f..d505b97 100644 --- a/iosApp/iosApp/Views/Player/FullPlayer.swift +++ b/iosApp/iosApp/Views/Player/FullPlayer.swift @@ -23,7 +23,7 @@ struct FullPlayer: View { Spacer() } - PlayerArt(imageURL: currentSong?.albumImageUrl.large) + PlayerArt(imageURL: currentSong?.albumImageUrls.large) .padding(.bottom, CustomStyle.spacing(.extraWide)) PlayerInfo(currentSong: currentSong) diff --git a/iosApp/iosApp/Views/PlayerScreen.swift b/iosApp/iosApp/Views/PlayerScreen.swift index 8095b7b..cb8f489 100644 --- a/iosApp/iosApp/Views/PlayerScreen.swift +++ b/iosApp/iosApp/Views/PlayerScreen.swift @@ -132,7 +132,7 @@ struct PlayerScreen: View { } } .task(id: currentSong?.id) { - guard let urlString = currentSong?.albumImageUrl.small, + guard let urlString = currentSong?.albumImageUrls.small, let url = URL(string: urlString) else { albumImage = nil return diff --git a/shared/src/androidMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt b/shared/src/androidMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt index df71b74..f56bdd4 100644 --- a/shared/src/androidMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt +++ b/shared/src/androidMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt @@ -282,7 +282,7 @@ actual class MusicServiceController( .setTitle(song.name) .setArtist(song.artistName) .setAlbumTitle(song.albumName) - .setArtworkUri(Uri.parse(song.albumImageUrl.large)) + .setArtworkUri(Uri.parse(song.albumImageUrls.large)) .build(), ).build() diff --git a/shared/src/commonMain/kotlin/org/blackcandy/shared/api/BlackCandyService.kt b/shared/src/commonMain/kotlin/org/blackcandy/shared/api/BlackCandyService.kt index a345cb1..1cde809 100644 --- a/shared/src/commonMain/kotlin/org/blackcandy/shared/api/BlackCandyService.kt +++ b/shared/src/commonMain/kotlin/org/blackcandy/shared/api/BlackCandyService.kt @@ -3,23 +3,23 @@ package org.blackcandy.shared.api import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.delete -import io.ktor.client.request.forms.submitForm import io.ktor.client.request.get -import io.ktor.client.request.parameter import io.ktor.client.request.post import io.ktor.client.request.put +import io.ktor.client.request.setBody import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.request import io.ktor.http.HttpHeaders import io.ktor.http.URLBuilder -import io.ktor.http.parameters import kotlinx.serialization.json.Json import kotlinx.serialization.json.boolean -import kotlinx.serialization.json.int +import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject import org.blackcandy.shared.models.AuthenticationResponse import org.blackcandy.shared.models.Song import org.blackcandy.shared.models.SystemInfo @@ -86,15 +86,16 @@ class BlackCandyServiceImpl( ): ApiResponse = handleResponse { val response: HttpResponse = - client.submitForm( - url = "authentication", - formParameters = - parameters { - append("with_cookie", "true") - append("session[email]", email) - append("session[password]", password) + client.post("sessions") { + setBody( + buildJsonObject { + putJsonObject("session") { + put("email", email) + put("password", password) + } }, - ) + ) + } val userElement = Json.parseToJsonElement(response.bodyAsText()).jsonObject["user"]!! @@ -102,7 +103,7 @@ class BlackCandyServiceImpl( val id = userElement.jsonObject["id"]?.jsonPrimitive?.long!! val userEmail = userElement.jsonObject["email"]?.jsonPrimitive.toString() val isAdmin = userElement.jsonObject["is_admin"]?.jsonPrimitive?.boolean!! - val cookies = response.headers.getAll(HttpHeaders.SetCookie)!! + val cookies = response.headers.getAll(HttpHeaders.SetCookie) ?: emptyList() AuthenticationResponse( token = token, @@ -118,7 +119,7 @@ class BlackCandyServiceImpl( override suspend fun removeAuthentication(): ApiResponse = handleResponse { - client.delete("authentication").body() + client.delete("my/session").body() } override suspend fun getSongsFromCurrentPlaylist(): ApiResponse> = @@ -130,7 +131,11 @@ class BlackCandyServiceImpl( handleResponse { client .post("favorite_playlist/songs") { - parameter("song_id", songId.toString()) + setBody( + buildJsonObject { + put("song_id", songId) + }, + ) }.body() } @@ -156,7 +161,11 @@ class BlackCandyServiceImpl( handleResponse { client .put("current_playlist/songs/$songId/move") { - parameter("destination_song_id", destinationSongId.toString()) + setBody( + buildJsonObject { + put("destination_song_id", destinationSongId) + }, + ) }.body() } @@ -178,15 +187,19 @@ class BlackCandyServiceImpl( handleResponse { client .post("current_playlist/songs") { - parameter("song_id", songId.toString()) + setBody( + buildJsonObject { + put("song_id", songId) - if (currentSongId != null) { - parameter("current_song_id", currentSongId.toString()) - } + if (currentSongId != null) { + put("current_song_id", currentSongId) + } - if (location != null) { - parameter("location", location) - } + if (location != null) { + put("location", location) + } + }, + ) }.body() } diff --git a/shared/src/commonMain/kotlin/org/blackcandy/shared/data/UserRepository.kt b/shared/src/commonMain/kotlin/org/blackcandy/shared/data/UserRepository.kt index b722de4..44b3d42 100644 --- a/shared/src/commonMain/kotlin/org/blackcandy/shared/data/UserRepository.kt +++ b/shared/src/commonMain/kotlin/org/blackcandy/shared/data/UserRepository.kt @@ -1,6 +1,5 @@ package org.blackcandy.shared.data -import androidx.datastore.core.DataStore import io.ktor.client.HttpClient import io.ktor.client.plugins.auth.Auth import io.ktor.client.plugins.auth.providers.BearerAuthProvider diff --git a/shared/src/commonMain/kotlin/org/blackcandy/shared/di/CommonModule.kt b/shared/src/commonMain/kotlin/org/blackcandy/shared/di/CommonModule.kt index 4ec74e1..8a83eeb 100644 --- a/shared/src/commonMain/kotlin/org/blackcandy/shared/di/CommonModule.kt +++ b/shared/src/commonMain/kotlin/org/blackcandy/shared/di/CommonModule.kt @@ -10,6 +10,8 @@ import io.ktor.client.plugins.auth.providers.bearer import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.contentType import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.runBlocking import kotlinx.serialization.ExperimentalSerializationApi @@ -90,7 +92,8 @@ private fun provideHttpClient( preferencesDataSource.getServerAddress() } - url("$serverAddress/api/v1/") + url("$serverAddress/") + contentType(ContentType.Application.Json) } HttpResponseValidator { diff --git a/shared/src/commonMain/kotlin/org/blackcandy/shared/models/Song.kt b/shared/src/commonMain/kotlin/org/blackcandy/shared/models/Song.kt index eb3ea80..85df57d 100644 --- a/shared/src/commonMain/kotlin/org/blackcandy/shared/models/Song.kt +++ b/shared/src/commonMain/kotlin/org/blackcandy/shared/models/Song.kt @@ -7,15 +7,17 @@ data class Song( val id: Long, val name: String, val duration: Double, + val albumId: Long, + val artistId: Long, val url: String, val albumName: String, val artistName: String, val format: String, - val albumImageUrl: ImageURL, + val albumImageUrls: ImageURLs, var isFavorited: Boolean, ) { @Serializable - data class ImageURL( + data class ImageURLs( val small: String, val medium: String, val large: String, diff --git a/shared/src/commonMain/kotlin/org/blackcandy/shared/models/SystemInfo.kt b/shared/src/commonMain/kotlin/org/blackcandy/shared/models/SystemInfo.kt index a981634..e28d374 100644 --- a/shared/src/commonMain/kotlin/org/blackcandy/shared/models/SystemInfo.kt +++ b/shared/src/commonMain/kotlin/org/blackcandy/shared/models/SystemInfo.kt @@ -21,6 +21,6 @@ data class SystemInfo( val major: Int, val minor: Int, val patch: Int, - val pre: String = "", + val pre: String? = null, ) } diff --git a/shared/src/commonMain/kotlin/org/blackcandy/shared/utils/Constants.kt b/shared/src/commonMain/kotlin/org/blackcandy/shared/utils/Constants.kt index 36f58ca..c110a44 100644 --- a/shared/src/commonMain/kotlin/org/blackcandy/shared/utils/Constants.kt +++ b/shared/src/commonMain/kotlin/org/blackcandy/shared/utils/Constants.kt @@ -2,3 +2,4 @@ package org.blackcandy.shared.utils expect val BLACK_CANDY_USER_AGENT: String const val NONE_DURATION_TEXT = "--:--" +const val NEW_SESSION_PATH = "/sessions/new" diff --git a/shared/src/iosMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt b/shared/src/iosMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt index b648d50..e08c400 100644 --- a/shared/src/iosMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt +++ b/shared/src/iosMain/kotlin/org/blackcandy/shared/media/MusicServiceController.kt @@ -254,7 +254,7 @@ actual class MusicServiceController( MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = nowPlayingInfo - updateAlbumArtwork(song.albumImageUrl.large) + updateAlbumArtwork(song.albumImageUrls.large) } private fun updateAlbumArtwork(urlString: String) {