backend implementation
This commit is contained in:
@@ -20,6 +20,7 @@ dependencies {
|
|||||||
implementation(project(":libs:backend-base"))
|
implementation(project(":libs:backend-base"))
|
||||||
implementation(project(":libs:base"))
|
implementation(project(":libs:base"))
|
||||||
implementation(project(":libs:http-client"))
|
implementation(project(":libs:http-client"))
|
||||||
|
implementation(project(":libs:updater:updater-common"))
|
||||||
|
|
||||||
implementation(libs.ktor.server.core.jvm)
|
implementation(libs.ktor.server.core.jvm)
|
||||||
implementation(libs.ktor.server.host.common.jvm)
|
implementation(libs.ktor.server.host.common.jvm)
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package ru.shadowsparky.vbox.backend.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.koin.core.annotation.Factory
|
||||||
|
import ru.shadowsparky.backend.data.EnvFetcher
|
||||||
|
import ru.shadowsparky.domain.DispatcherProvider
|
||||||
|
import ru.shadowsparky.updater.domain.UpdateFetcher
|
||||||
|
import ru.shadowsparky.updater.domain.UpdateInfo
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Factory
|
||||||
|
class BackendUpdateFetcher(
|
||||||
|
private val envFetcher: EnvFetcher,
|
||||||
|
private val json: Json,
|
||||||
|
private val dispatcherProvider: DispatcherProvider
|
||||||
|
) : UpdateFetcher {
|
||||||
|
private var config: UpdateInfo? = null
|
||||||
|
|
||||||
|
override suspend fun fetch(versionCode: Long): UpdateInfo = withContext(dispatcherProvider.io) {
|
||||||
|
config?.let { return@withContext it }
|
||||||
|
val configPath = envFetcher.get("UPDATE_CONFIG")
|
||||||
|
if (configPath.isBlank()) {
|
||||||
|
UpdateInfo()
|
||||||
|
} else {
|
||||||
|
json.decodeFromString<UpdateInfo>(File(configPath).readText()).apply {
|
||||||
|
config = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package ru.shadowsparky.vbox.backend.di
|
package ru.shadowsparky.vbox.backend.di
|
||||||
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
import ru.shadowsparky.backend.data.TokenVerifier
|
import ru.shadowsparky.backend.data.TokenVerifier
|
||||||
import ru.shadowsparky.backend.domain.JwtInfo
|
import ru.shadowsparky.backend.domain.JwtInfo
|
||||||
import ru.shadowsparky.backend.domain.LoginVerifier
|
import ru.shadowsparky.backend.domain.LoginVerifier
|
||||||
|
import ru.shadowsparky.updater.domain.UpdateFetcher
|
||||||
import ru.shadowsparky.vbox.backend.data.BackendRemoteEventHandler
|
import ru.shadowsparky.vbox.backend.data.BackendRemoteEventHandler
|
||||||
|
import ru.shadowsparky.vbox.backend.data.BackendUpdateFetcher
|
||||||
import ru.shadowsparky.vbox.backend.data.auth.AuthTokenRepositoryFactory
|
import ru.shadowsparky.vbox.backend.data.auth.AuthTokenRepositoryFactory
|
||||||
import ru.shadowsparky.vbox.backend.data.tags.MovieTagRepositoryFactory
|
import ru.shadowsparky.vbox.backend.data.tags.MovieTagRepositoryFactory
|
||||||
import ru.shadowsparky.vbox.backend.data.tags.UserTagRepositoryFactory
|
import ru.shadowsparky.vbox.backend.data.tags.UserTagRepositoryFactory
|
||||||
@@ -22,9 +23,9 @@ class RoutingEntryPoint(
|
|||||||
val searchFactory: SearchRepositoryFactory,
|
val searchFactory: SearchRepositoryFactory,
|
||||||
val recentlyFactory: RecentlyWatchedRepositoryFactory,
|
val recentlyFactory: RecentlyWatchedRepositoryFactory,
|
||||||
val savedMovieFactory: SavedMovieRepositoryFactory,
|
val savedMovieFactory: SavedMovieRepositoryFactory,
|
||||||
val json: Json,
|
|
||||||
val movieTagFactory: MovieTagRepositoryFactory,
|
val movieTagFactory: MovieTagRepositoryFactory,
|
||||||
val userTagFactory: UserTagRepositoryFactory
|
val userTagFactory: UserTagRepositoryFactory,
|
||||||
|
val updateFetcher: UpdateFetcher
|
||||||
)
|
)
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
|
|||||||
+1
@@ -46,5 +46,6 @@ fun Routing.setupAuthMethods(
|
|||||||
setupRecentlyWatched(recentlyFactory)
|
setupRecentlyWatched(recentlyFactory)
|
||||||
setupSavedMovie(savedMovieFactory)
|
setupSavedMovie(savedMovieFactory)
|
||||||
setupTagsRouting(userTagFactory, movieTagFactory)
|
setupTagsRouting(userTagFactory, movieTagFactory)
|
||||||
|
setupUpdates(updateFetcher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.shadowsparky.vbox.backend.presentation.routing
|
||||||
|
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.util.getOrFail
|
||||||
|
import ru.shadowsparky.updater.domain.UpdateFetcher
|
||||||
|
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
|
||||||
|
|
||||||
|
fun Routing.setupUpdates(updateFetcher: UpdateFetcher) {
|
||||||
|
get("$DYNAMIC_PREFIX/checkUpdates") {
|
||||||
|
val versionCode = call.parameters.getOrFail(UpdateFetcher.ARG_VERSION_CODE).toLong()
|
||||||
|
call.respond(updateFetcher.fetch(versionCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ dependencies {
|
|||||||
implementation(project(":libs:ui"))
|
implementation(project(":libs:ui"))
|
||||||
implementation(project(":libs:http-client"))
|
implementation(project(":libs:http-client"))
|
||||||
implementation(project(":libs:video-player"))
|
implementation(project(":libs:video-player"))
|
||||||
implementation(project(":libs:updater"))
|
implementation(project(":libs:updater:updater-client"))
|
||||||
debugImplementation(libs.ui.tooling)
|
debugImplementation(libs.ui.tooling)
|
||||||
implementation(libs.ui.tooling.preview)
|
implementation(libs.ui.tooling.preview)
|
||||||
testImplementation(libs.koin.test)
|
testImplementation(libs.koin.test)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ kotlin {
|
|||||||
implementation(project(":libs:ui"))
|
implementation(project(":libs:ui"))
|
||||||
implementation(project(":libs:http-client"))
|
implementation(project(":libs:http-client"))
|
||||||
implementation(project(":libs:video-player"))
|
implementation(project(":libs:video-player"))
|
||||||
implementation(project(":libs:updater"))
|
implementation(project(":libs:updater:updater-client"))
|
||||||
}
|
}
|
||||||
androidMain.dependencies {
|
androidMain.dependencies {
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
|
|||||||
+3
-5
@@ -2,7 +2,6 @@ package ru.shadowsparky.vbox.shared.data
|
|||||||
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import ru.shadowsparky.domain.AppConfig
|
|
||||||
import ru.shadowsparky.updater.domain.UpdateFetcher
|
import ru.shadowsparky.updater.domain.UpdateFetcher
|
||||||
import ru.shadowsparky.updater.domain.UpdateInfo
|
import ru.shadowsparky.updater.domain.UpdateInfo
|
||||||
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
|
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
|
||||||
@@ -12,14 +11,13 @@ import ru.shadowsparky.vbox.shared.domain.getServerConfiguration
|
|||||||
@Factory
|
@Factory
|
||||||
class VBoxUpdateFetcher(
|
class VBoxUpdateFetcher(
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
private val serverConfigurationProvider: ServerConfigurationRepository,
|
private val serverConfigurationProvider: ServerConfigurationRepository
|
||||||
private val appConfig: AppConfig
|
|
||||||
) : UpdateFetcher {
|
) : UpdateFetcher {
|
||||||
override suspend fun fetch(): UpdateInfo {
|
override suspend fun fetch(versionCode: Long): UpdateInfo {
|
||||||
return httpClient.get<UpdateInfo>(
|
return httpClient.get<UpdateInfo>(
|
||||||
"$DYNAMIC_PREFIX/checkUpdates",
|
"$DYNAMIC_PREFIX/checkUpdates",
|
||||||
mapOf(
|
mapOf(
|
||||||
"versionCode" to appConfig.versionCode.toString()
|
UpdateFetcher.ARG_VERSION_CODE to versionCode.toString()
|
||||||
),
|
),
|
||||||
serverConfigurationProvider.getServerConfiguration()
|
serverConfigurationProvider.getServerConfiguration()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package ru.shadowsparky.updater.domain
|
|
||||||
|
|
||||||
interface UpdateFetcher {
|
|
||||||
suspend fun fetch(): UpdateInfo
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":libs:ui"))
|
implementation(project(":libs:ui"))
|
||||||
implementation(project(":libs:base"))
|
implementation(project(":libs:base"))
|
||||||
|
api(project(":libs:updater:updater-common"))
|
||||||
implementation(libs.ui.tooling.preview)
|
implementation(libs.ui.tooling.preview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
@@ -1,5 +1,8 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="updater_title_available">Доступно обновление</string>
|
<string name="updater_title_available">Доступно обновление</string>
|
||||||
|
<string name="updater_title_not_found">Обновления не найдены</string>
|
||||||
|
<string name="updater_btn_close">Закрыть</string>
|
||||||
|
|
||||||
<string name="updater_btn_download">Скачать</string>
|
<string name="updater_btn_download">Скачать</string>
|
||||||
<string name="updater_changelog_title">Что нового:</string>
|
<string name="updater_changelog_title">Что нового:</string>
|
||||||
<string name="updater_changelog_empty">Улучшения производительности и исправления ошибок.</string>
|
<string name="updater_changelog_empty">Улучшения производительности и исправления ошибок.</string>
|
||||||
+2
-2
@@ -22,9 +22,9 @@ class UpdateInteractor(
|
|||||||
}
|
}
|
||||||
emit(UpdateState.Checking)
|
emit(UpdateState.Checking)
|
||||||
try {
|
try {
|
||||||
val updateInfo = fetcher.fetch()
|
|
||||||
val serverVersion = updateInfo.versionCode ?: 0L
|
|
||||||
val currentVersion = appConfig.versionCode ?: 0L
|
val currentVersion = appConfig.versionCode ?: 0L
|
||||||
|
val updateInfo = fetcher.fetch(currentVersion)
|
||||||
|
val serverVersion = updateInfo.versionCode ?: 0L
|
||||||
|
|
||||||
if (serverVersion <= currentVersion) {
|
if (serverVersion <= currentVersion) {
|
||||||
emit(UpdateState.UpToDate)
|
emit(UpdateState.UpToDate)
|
||||||
+38
-17
@@ -1,24 +1,25 @@
|
|||||||
package ru.shadowsparky.updater.presentation
|
package ru.shadowsparky.updater.presentation
|
||||||
|
|
||||||
import androiddev.libs.updater.generated.resources.Res
|
import androiddev.libs.updater.updater_client.generated.resources.Res
|
||||||
import androiddev.libs.updater.generated.resources.updater_btn_download
|
import androiddev.libs.updater.updater_client.generated.resources.updater_btn_close
|
||||||
import androiddev.libs.updater.generated.resources.updater_btn_grant
|
import androiddev.libs.updater.updater_client.generated.resources.updater_btn_download
|
||||||
import androiddev.libs.updater.generated.resources.updater_btn_install
|
import androiddev.libs.updater.updater_client.generated.resources.updater_btn_grant
|
||||||
import androiddev.libs.updater.generated.resources.updater_btn_retry
|
import androiddev.libs.updater.updater_client.generated.resources.updater_btn_install
|
||||||
import androiddev.libs.updater.generated.resources.updater_changelog_empty
|
import androiddev.libs.updater.updater_client.generated.resources.updater_btn_retry
|
||||||
import androiddev.libs.updater.generated.resources.updater_changelog_title
|
import androiddev.libs.updater.updater_client.generated.resources.updater_changelog_empty
|
||||||
import androiddev.libs.updater.generated.resources.updater_desc_permission
|
import androiddev.libs.updater.updater_client.generated.resources.updater_changelog_title
|
||||||
import androiddev.libs.updater.generated.resources.updater_download_progress
|
import androiddev.libs.updater.updater_client.generated.resources.updater_desc_permission
|
||||||
import androiddev.libs.updater.generated.resources.updater_title_available
|
import androiddev.libs.updater.updater_client.generated.resources.updater_download_progress
|
||||||
import androiddev.libs.updater.generated.resources.updater_title_downloading
|
import androiddev.libs.updater.updater_client.generated.resources.updater_title_available
|
||||||
import androiddev.libs.updater.generated.resources.updater_title_error
|
import androiddev.libs.updater.updater_client.generated.resources.updater_title_downloading
|
||||||
import androiddev.libs.updater.generated.resources.updater_title_permission
|
import androiddev.libs.updater.updater_client.generated.resources.updater_title_error
|
||||||
import androiddev.libs.updater.generated.resources.updater_title_ready
|
import androiddev.libs.updater.updater_client.generated.resources.updater_title_not_found
|
||||||
|
import androiddev.libs.updater.updater_client.generated.resources.updater_title_permission
|
||||||
|
import androiddev.libs.updater.updater_client.generated.resources.updater_title_ready
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -52,7 +53,7 @@ fun UpdateBottomSheet(
|
|||||||
confirmValueChange = { state !is UpdateState.UpdateAvailable || !(state as UpdateState.UpdateAvailable).isForceUpdate }
|
confirmValueChange = { state !is UpdateState.UpdateAvailable || !(state as UpdateState.UpdateAvailable).isForceUpdate }
|
||||||
)
|
)
|
||||||
|
|
||||||
val isVisible = remember(state) { state !is UpdateState.Idle && state !is UpdateState.UpToDate }
|
val isVisible = remember(state) { state !is UpdateState.Idle }
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
@@ -87,6 +88,9 @@ fun UpdateBottomSheet(
|
|||||||
message = currentState.message,
|
message = currentState.message,
|
||||||
onRetryClick = { component.checkUpdates() }
|
onRetryClick = { component.checkUpdates() }
|
||||||
)
|
)
|
||||||
|
is UpdateState.UpToDate -> {
|
||||||
|
UpdateNotFoundContent(onSkip = { component.reset() })
|
||||||
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,7 @@ fun UpdateBottomSheet(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UpdateCheckingContent() {
|
private fun UpdateCheckingContent() {
|
||||||
Loading(Modifier.fillMaxSize())
|
Loading(Modifier.fillMaxWidth())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -192,6 +196,23 @@ private fun UpdateReadyContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun UpdateNotFoundContent(
|
||||||
|
onSkip: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(Res.string.updater_title_not_found), style = MaterialTheme.typography.titleLarge)
|
||||||
|
Button(onClick = onSkip, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Text(stringResource(Res.string.updater_btn_close), style = MaterialTheme.typography.labelLarge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UpdateErrorContent(
|
private fun UpdateErrorContent(
|
||||||
message: String,
|
message: String,
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/build
|
||||||
|
/bin
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.convention.kmp)
|
||||||
|
alias(libs.plugins.convention.android.lib)
|
||||||
|
alias(libs.plugins.convention.serialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":libs:base"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
android { namespace = "ru.shadowsparky.updater.common" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ru.shadowsparky.updater.domain
|
||||||
|
|
||||||
|
interface UpdateFetcher {
|
||||||
|
suspend fun fetch(versionCode: Long): UpdateInfo
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ARG_VERSION_CODE = "versionCode"
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -43,4 +43,5 @@ include(":libs:base")
|
|||||||
include(":libs:ui")
|
include(":libs:ui")
|
||||||
include(":libs:http-client")
|
include(":libs:http-client")
|
||||||
include(":libs:video-player")
|
include(":libs:video-player")
|
||||||
include(":libs:updater")
|
include(":libs:updater:updater-client")
|
||||||
|
include(":libs:updater:updater-common")
|
||||||
|
|||||||
Reference in New Issue
Block a user