add basic updater implementation in gigawear
This commit is contained in:
@@ -21,3 +21,5 @@ cache
|
|||||||
kotlin-js-store
|
kotlin-js-store
|
||||||
release.keystore
|
release.keystore
|
||||||
keystore.properties
|
keystore.properties
|
||||||
|
tmp
|
||||||
|
run-gigawear-backend.sh
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ dependencies {
|
|||||||
implementation(project(":libs:base"))
|
implementation(project(":libs:base"))
|
||||||
implementation(project(":libs:http-client"))
|
implementation(project(":libs:http-client"))
|
||||||
implementation(project(":feature:updater:updater-common"))
|
implementation(project(":feature:updater:updater-common"))
|
||||||
|
implementation(project(":feature:updater:updater-backend"))
|
||||||
implementation(project(":feature:chat:chat-common"))
|
implementation(project(":feature:chat:chat-common"))
|
||||||
implementation(project(":feature:chat:chat-backend"))
|
implementation(project(":feature:chat:chat-backend"))
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import io.ktor.server.routing.get
|
|||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
import org.koin.core.annotation.KoinApplication
|
import org.koin.core.annotation.KoinApplication
|
||||||
import org.koin.plugin.module.dsl.startKoin
|
import org.koin.plugin.module.dsl.startKoin
|
||||||
|
import ru.shadowsparky.gigawear.backend.presentation.MOCK_USER_ID
|
||||||
import ru.shadowsparky.gigawear.backend.presentation.setupChat
|
import ru.shadowsparky.gigawear.backend.presentation.setupChat
|
||||||
import ru.shadowsparky.koin
|
import ru.shadowsparky.koin
|
||||||
|
import ru.shadowsparky.updater.backend.presentation.setupUpdates
|
||||||
|
|
||||||
@KoinApplication
|
@KoinApplication
|
||||||
object GigaWearApp
|
object GigaWearApp
|
||||||
@@ -26,5 +28,6 @@ fun Application.module() {
|
|||||||
routing {
|
routing {
|
||||||
get("health") { this.call.respond(HttpStatusCode.OK) }
|
get("health") { this.call.respond(HttpStatusCode.OK) }
|
||||||
setupChat(koin.get())
|
setupChat(koin.get())
|
||||||
|
setupUpdates(koin.get()) { MOCK_USER_ID }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import io.ktor.server.routing.post
|
|||||||
import ru.shadowsparky.chat.backend.domain.ProcessUserMessageUseCase
|
import ru.shadowsparky.chat.backend.domain.ProcessUserMessageUseCase
|
||||||
import ru.shadowsparky.chat.domain.Message
|
import ru.shadowsparky.chat.domain.Message
|
||||||
|
|
||||||
private const val MOCK_USER_ID = -1L
|
const val MOCK_USER_ID = -1L
|
||||||
|
|
||||||
fun Route.setupChat(useCase: ProcessUserMessageUseCase) {
|
fun Route.setupChat(useCase: ProcessUserMessageUseCase) {
|
||||||
post("chat") {
|
post("chat") {
|
||||||
|
|||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
package ru.shadowsparky.gigawear.backend.presentation
|
|
||||||
|
|
||||||
import io.ktor.server.request.receive
|
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.Routing
|
|
||||||
import io.ktor.server.routing.post
|
|
||||||
import ru.shadowsparky.chat.backend.domain.ProcessUserMessageUseCase
|
|
||||||
import ru.shadowsparky.chat.domain.Message
|
|
||||||
|
|
||||||
fun Route.setupUpdate(useCase: ProcessUserMessageUseCase) {
|
|
||||||
post("chat") {
|
|
||||||
val message = call.receive<Message>().copy(timestamp = System.currentTimeMillis())
|
|
||||||
call.respond(useCase.execute(message, MOCK_USER_ID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ dependencies {
|
|||||||
implementation(project(":libs:http-client"))
|
implementation(project(":libs:http-client"))
|
||||||
implementation(project(":libs:base"))
|
implementation(project(":libs:base"))
|
||||||
implementation(project(":feature:chat:chat-common"))
|
implementation(project(":feature:chat:chat-common"))
|
||||||
|
implementation(project(":feature:updater:updater-common"))
|
||||||
|
implementation(project(":feature:updater:updater-client"))
|
||||||
|
|
||||||
implementation(libs.activity.compose)
|
implementation(libs.activity.compose)
|
||||||
implementation(libs.androidx.compose.foundation)
|
implementation(libs.androidx.compose.foundation)
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||||
|
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||||
|
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||||
|
-dontwarn org.conscrypt.Conscrypt$Version
|
||||||
|
-dontwarn org.conscrypt.Conscrypt
|
||||||
|
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||||
|
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||||
|
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||||
|
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||||
|
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||||
|
-dontwarn java.lang.management.ManagementFactory
|
||||||
|
-dontwarn java.lang.management.RuntimeMXBean
|
||||||
|
-dontwarn kotlinx.atomicfu.**
|
||||||
|
-dontwarn io.netty.**
|
||||||
|
-dontwarn com.typesafe.**
|
||||||
|
-dontwarn org.slf4j.**
|
||||||
|
-dontwarn com.sun.nio.file.SensitivityWatchEventModifier
|
||||||
|
-dontwarn org.xmlpull.v1.**
|
||||||
|
-dontwarn javax.xml.namespace.**
|
||||||
|
-dontwarn androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
-dontwarn androidx.window.extensions.WindowExtensions
|
||||||
|
-dontwarn androidx.window.extensions.WindowExtensionsProvider
|
||||||
|
-dontwarn androidx.window.extensions.area.ExtensionWindowAreaPresentation
|
||||||
|
-dontwarn androidx.window.extensions.core.util.function.Consumer
|
||||||
|
-dontwarn androidx.window.extensions.core.util.function.Function
|
||||||
|
-dontwarn androidx.window.extensions.core.util.function.Predicate
|
||||||
|
-dontwarn androidx.window.extensions.layout.DisplayFeature
|
||||||
|
-dontwarn androidx.window.extensions.layout.FoldingFeature
|
||||||
|
-dontwarn androidx.window.extensions.layout.WindowLayoutComponent
|
||||||
|
-dontwarn androidx.window.extensions.layout.WindowLayoutInfo
|
||||||
|
-dontwarn androidx.window.sidecar.SidecarDeviceState
|
||||||
|
-dontwarn androidx.window.sidecar.SidecarDisplayFeature
|
||||||
|
-dontwarn androidx.window.sidecar.SidecarInterface$SidecarCallback
|
||||||
|
-dontwarn androidx.window.sidecar.SidecarInterface
|
||||||
|
-dontwarn androidx.window.sidecar.SidecarProvider
|
||||||
|
-dontwarn androidx.window.sidecar.SidecarWindowLayoutInfo
|
||||||
|
|
||||||
|
-keep class ru.shadowsparky.vbox.shared.domain.RemoteEvent { *; }
|
||||||
|
-keep class ru.shadowsparky.vbox.shared.domain.RemoteEvent$OnSaved { *; }
|
||||||
|
-keep class ru.shadowsparky.vbox.shared.domain.RemoteEvent$OnRecent { *; }
|
||||||
|
-keep class ru.shadowsparky.vbox.shared.domain.RemoteEvent$OnSearch { *; }
|
||||||
|
|
||||||
|
-keep class ru.shadowsparky.videobox.multiplatform.**$Config { *; }
|
||||||
|
-keep class ru.shadowsparky.videobox.multiplatform.**$Configuration { *; }
|
||||||
|
|
||||||
|
-keepclassmembers class * implements android.os.Parcelable {
|
||||||
|
static ** CREATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers enum * {
|
||||||
|
public static **[] values();
|
||||||
|
public static ** valueOf(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod
|
||||||
|
|
||||||
|
-keep class com.arkivanov.decompose.** { *; }
|
||||||
|
-keep interface com.arkivanov.decompose.** { *; }
|
||||||
|
-dontwarn com.arkivanov.decompose.**
|
||||||
@@ -3,8 +3,21 @@ package ru.shadowsparky.gigawear
|
|||||||
import org.koin.core.annotation.ComponentScan
|
import org.koin.core.annotation.ComponentScan
|
||||||
import org.koin.core.annotation.Configuration
|
import org.koin.core.annotation.Configuration
|
||||||
import org.koin.core.annotation.Module
|
import org.koin.core.annotation.Module
|
||||||
|
import org.koin.core.annotation.Singleton
|
||||||
|
import ru.shadowsparky.domain.AppConfig
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@ComponentScan
|
@ComponentScan
|
||||||
@Configuration
|
@Configuration
|
||||||
class GigaWearModule
|
class GigaWearModule {
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
fun provideAppConfig(): AppConfig {
|
||||||
|
return object : AppConfig {
|
||||||
|
override val debug = BuildConfig.DEBUG
|
||||||
|
override val appId = "GigaWearClient"
|
||||||
|
override val versionCode = BuildConfig.VERSION_CODE.toLong()
|
||||||
|
override val versionName = BuildConfig.VERSION_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-1
@@ -8,13 +8,15 @@ import org.koin.core.annotation.Factory
|
|||||||
import ru.shadowsparky.chat.domain.ChatRepository
|
import ru.shadowsparky.chat.domain.ChatRepository
|
||||||
import ru.shadowsparky.chat.domain.Message
|
import ru.shadowsparky.chat.domain.Message
|
||||||
|
|
||||||
|
const val ENDPOINT = "http://192.168.0.76:8181"
|
||||||
|
|
||||||
@Factory
|
@Factory
|
||||||
class GigaChatRepository(
|
class GigaChatRepository(
|
||||||
private val httpClient: HttpClient
|
private val httpClient: HttpClient
|
||||||
) : ChatRepository {
|
) : ChatRepository {
|
||||||
override suspend fun query(afterId: Long?): List<Message> = emptyList()
|
override suspend fun query(afterId: Long?): List<Message> = emptyList()
|
||||||
override suspend fun send(message: Message): Message {
|
override suspend fun send(message: Message): Message {
|
||||||
return httpClient.post("http://192.168.0.76:8181/chat") {
|
return httpClient.post("$ENDPOINT/chat") {
|
||||||
setBody(message)
|
setBody(message)
|
||||||
}.body<Message>()
|
}.body<Message>()
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.shadowsparky.gigawear.data
|
||||||
|
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
|
import org.koin.core.annotation.Factory
|
||||||
|
import ru.shadowsparky.updater.domain.UpdateFetcher
|
||||||
|
import ru.shadowsparky.updater.domain.UpdateInfo
|
||||||
|
|
||||||
|
@Factory
|
||||||
|
class UpdateFetcherImpl(private val httpClient: HttpClient) : UpdateFetcher {
|
||||||
|
override suspend fun fetch(versionCode: Long): UpdateInfo {
|
||||||
|
return httpClient.get("$ENDPOINT/checkUpdates") {
|
||||||
|
parameter(UpdateFetcher.ARG_VERSION_CODE, versionCode)
|
||||||
|
}.body()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ru.shadowsparky.gigawear.presentation
|
package ru.shadowsparky.gigawear.presentation
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.annotation.KoinApplication
|
import org.koin.core.annotation.KoinApplication
|
||||||
import org.koin.plugin.module.dsl.startKoin
|
import org.koin.plugin.module.dsl.startKoin
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ class App : Application() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
startKoin<GigaWearApp>()
|
startKoin<GigaWearApp> {
|
||||||
|
androidContext(this@App)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-3
@@ -1,6 +1,7 @@
|
|||||||
package ru.shadowsparky.gigawear.presentation.chat
|
package ru.shadowsparky.gigawear.presentation.chat
|
||||||
|
|
||||||
import com.arkivanov.decompose.ComponentContext
|
import com.arkivanov.decompose.ComponentContext
|
||||||
|
import com.arkivanov.decompose.childContext
|
||||||
import com.arkivanov.decompose.value.MutableValue
|
import com.arkivanov.decompose.value.MutableValue
|
||||||
import com.arkivanov.decompose.value.Value
|
import com.arkivanov.decompose.value.Value
|
||||||
import com.arkivanov.decompose.value.update
|
import com.arkivanov.decompose.value.update
|
||||||
@@ -17,12 +18,15 @@ import ru.shadowsparky.gigawear.presentation.nav.Child
|
|||||||
import ru.shadowsparky.gigawear.presentation.nav.Route
|
import ru.shadowsparky.gigawear.presentation.nav.Route
|
||||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
import ru.shadowsparky.ui.nav.ComponentFactory
|
||||||
import ru.shadowsparky.ui.nav.RouteNavigator
|
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||||
|
import ru.shadowsparky.updater.presentation.UpdateComponentFactory
|
||||||
|
|
||||||
class ChatComponent(
|
class ChatComponent(
|
||||||
private val context: ComponentContext,
|
private val context: ComponentContext,
|
||||||
private val repository: ChatRepository
|
private val repository: ChatRepository,
|
||||||
|
private val updateComponentFactory: UpdateComponentFactory
|
||||||
) : ComponentContext by context {
|
) : ComponentContext by context {
|
||||||
private val scope = coroutineScope()
|
private val scope = coroutineScope()
|
||||||
|
val updateComponent = updateComponentFactory.create(childContext("updater"))
|
||||||
|
|
||||||
private val _state = MutableValue(ChatUiState())
|
private val _state = MutableValue(ChatUiState())
|
||||||
val state: Value<ChatUiState> = _state
|
val state: Value<ChatUiState> = _state
|
||||||
@@ -30,6 +34,10 @@ class ChatComponent(
|
|||||||
private val _events = Channel<ComponentEvent>(Channel.BUFFERED)
|
private val _events = Channel<ComponentEvent>(Channel.BUFFERED)
|
||||||
val events = _events.receiveAsFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
updateComponent.checkUpdates(true)
|
||||||
|
}
|
||||||
|
|
||||||
fun onMicClicked() {
|
fun onMicClicked() {
|
||||||
if (_state.value.isLoading) return
|
if (_state.value.isLoading) return
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -75,14 +83,19 @@ class ChatComponent(
|
|||||||
@Factory
|
@Factory
|
||||||
@Named(Route.CHAT)
|
@Named(Route.CHAT)
|
||||||
class ChatComponentFactory(
|
class ChatComponentFactory(
|
||||||
private val chatRepository: ChatRepository
|
private val chatRepository: ChatRepository,
|
||||||
|
private val updateComponentFactory: UpdateComponentFactory
|
||||||
) : ComponentFactory<Child, Route.ChatScreen, Route> {
|
) : ComponentFactory<Child, Route.ChatScreen, Route> {
|
||||||
override fun create(
|
override fun create(
|
||||||
route: Route.ChatScreen,
|
route: Route.ChatScreen,
|
||||||
child: ComponentContext,
|
child: ComponentContext,
|
||||||
root: RouteNavigator<Route>
|
root: RouteNavigator<Route>
|
||||||
): Child {
|
): Child {
|
||||||
val component = ChatComponent(child, chatRepository)
|
val component = ChatComponent(
|
||||||
|
child,
|
||||||
|
chatRepository,
|
||||||
|
updateComponentFactory
|
||||||
|
)
|
||||||
return Child.MainChild(component)
|
return Child.MainChild(component)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -40,6 +40,7 @@ import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
|||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import ru.shadowsparky.gigawear.R
|
import ru.shadowsparky.gigawear.R
|
||||||
import ru.shadowsparky.gigawear.presentation.WearErrorContent
|
import ru.shadowsparky.gigawear.presentation.WearErrorContent
|
||||||
|
import ru.shadowsparky.updater.presentation.UpdateDialog
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -171,6 +172,7 @@ fun ChatScreen(component: ChatComponent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
|
UpdateDialog(component.updateComponent)
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ dependencies {
|
|||||||
implementation(project(":libs:base"))
|
implementation(project(":libs:base"))
|
||||||
implementation(project(":libs:http-client"))
|
implementation(project(":libs:http-client"))
|
||||||
implementation(project(":feature:updater:updater-common"))
|
implementation(project(":feature:updater:updater-common"))
|
||||||
|
implementation(project(":feature:updater:updater-backend"))
|
||||||
implementation(project(":feature:chat:chat-common"))
|
implementation(project(":feature:chat:chat-common"))
|
||||||
implementation(project(":feature:chat:chat-backend"))
|
implementation(project(":feature:chat:chat-backend"))
|
||||||
|
|
||||||
|
|||||||
+3
-16
@@ -1,23 +1,10 @@
|
|||||||
package ru.shadowsparky.vbox.backend.presentation.routing
|
package ru.shadowsparky.vbox.backend.presentation.routing
|
||||||
|
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.response.respondFile
|
|
||||||
import io.ktor.server.routing.Route
|
import io.ktor.server.routing.Route
|
||||||
import io.ktor.server.routing.get
|
import ru.shadowsparky.updater.backend.data.BackendUpdateFetcherFactory
|
||||||
import io.ktor.server.util.getOrFail
|
import ru.shadowsparky.updater.backend.presentation.setupUpdates
|
||||||
import ru.shadowsparky.updater.domain.UpdateFetcher
|
|
||||||
import ru.shadowsparky.vbox.backend.data.BackendUpdateFetcherFactory
|
|
||||||
import ru.shadowsparky.vbox.backend.presentation.obtainUserId
|
|
||||||
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
|
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
|
||||||
|
|
||||||
fun Route.setupUpdates(updateFetcherFactory: BackendUpdateFetcherFactory) {
|
fun Route.setupUpdates(updateFetcherFactory: BackendUpdateFetcherFactory) {
|
||||||
get("$DYNAMIC_PREFIX/checkUpdates") {
|
setupUpdates(updateFetcherFactory, "$DYNAMIC_PREFIX/")
|
||||||
val versionCode = call.parameters.getOrFail(UpdateFetcher.ARG_VERSION_CODE).toLong()
|
|
||||||
val updateFetcher = updateFetcherFactory.create(call.obtainUserId())
|
|
||||||
call.respond(updateFetcher.fetch(versionCode))
|
|
||||||
}
|
|
||||||
get("$DYNAMIC_PREFIX/getApk") {
|
|
||||||
val updateFetcher = updateFetcherFactory.create(call.obtainUserId())
|
|
||||||
call.respondFile(updateFetcher.updateFile())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/build
|
||||||
|
/bin
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.convention.jvm)
|
||||||
|
alias(libs.plugins.convention.ktor.client)
|
||||||
|
alias(libs.plugins.convention.koin)
|
||||||
|
alias(libs.plugins.convention.serialization)
|
||||||
|
alias(libs.plugins.ktor)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.ktor.server.core.jvm)
|
||||||
|
implementation(project(":libs:backend-base"))
|
||||||
|
implementation(project(":libs:http-client"))
|
||||||
|
implementation(project(":feature:updater:updater-common"))
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package ru.shadowsparky.updater.backend
|
||||||
|
|
||||||
|
import org.koin.core.annotation.ComponentScan
|
||||||
|
import org.koin.core.annotation.Configuration
|
||||||
|
import org.koin.core.annotation.Module
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@ComponentScan
|
||||||
|
@Configuration
|
||||||
|
class BackendUpdaterModule
|
||||||
+6
-8
@@ -1,14 +1,14 @@
|
|||||||
package ru.shadowsparky.vbox.backend.data
|
package ru.shadowsparky.updater.backend.data
|
||||||
|
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.util.cio.readChannel
|
import io.ktor.util.cio.readChannel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import ru.shadowsparky.backend.data.EnvFetcher
|
import ru.shadowsparky.backend.data.EnvFetcher
|
||||||
import ru.shadowsparky.domain.DispatcherProvider
|
|
||||||
import ru.shadowsparky.http.domain.HttpException
|
import ru.shadowsparky.http.domain.HttpException
|
||||||
import ru.shadowsparky.updater.domain.UpdateFetcher
|
import ru.shadowsparky.updater.domain.UpdateFetcher
|
||||||
import ru.shadowsparky.updater.domain.UpdateInfo
|
import ru.shadowsparky.updater.domain.UpdateInfo
|
||||||
@@ -17,18 +17,16 @@ import java.io.File
|
|||||||
@Factory
|
@Factory
|
||||||
class BackendUpdateFetcherFactory(
|
class BackendUpdateFetcherFactory(
|
||||||
private val envFetcher: EnvFetcher,
|
private val envFetcher: EnvFetcher,
|
||||||
private val json: Json,
|
private val json: Json
|
||||||
private val dispatcherProvider: DispatcherProvider
|
|
||||||
) {
|
) {
|
||||||
fun create(userId: Long): BackendUpdateFetcher {
|
fun create(userId: Long): BackendUpdateFetcher {
|
||||||
return BackendUpdateFetcher(envFetcher, json, dispatcherProvider, userId)
|
return BackendUpdateFetcher(envFetcher, json, userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackendUpdateFetcher(
|
class BackendUpdateFetcher(
|
||||||
private val envFetcher: EnvFetcher,
|
private val envFetcher: EnvFetcher,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
private val dispatcherProvider: DispatcherProvider,
|
|
||||||
private val userId: Long
|
private val userId: Long
|
||||||
) : UpdateFetcher {
|
) : UpdateFetcher {
|
||||||
private val fetchMutex = Mutex()
|
private val fetchMutex = Mutex()
|
||||||
@@ -42,7 +40,7 @@ class BackendUpdateFetcher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchInternal(): UpdateInfo = withContext(dispatcherProvider.io) {
|
private suspend fun fetchInternal(): UpdateInfo = withContext(Dispatchers.IO) {
|
||||||
val configPath = envFetcher.get("UPDATE_CONFIG")
|
val configPath = envFetcher.get("UPDATE_CONFIG")
|
||||||
if (configPath.isBlank()) {
|
if (configPath.isBlank()) {
|
||||||
UpdateInfo()
|
UpdateInfo()
|
||||||
@@ -59,7 +57,7 @@ class BackendUpdateFetcher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateFileInternal(): File = withContext(dispatcherProvider.io) {
|
private suspend fun updateFileInternal(): File = withContext(Dispatchers.IO) {
|
||||||
val updateFilePath = envFetcher.get("UPDATE_FILE")
|
val updateFilePath = envFetcher.get("UPDATE_FILE")
|
||||||
if (updateFilePath.isBlank()) throw HttpException(HttpStatusCode.NotFound.value, "Update not found")
|
if (updateFilePath.isBlank()) throw HttpException(HttpStatusCode.NotFound.value, "Update not found")
|
||||||
val file = File(updateFilePath)
|
val file = File(updateFilePath)
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package ru.shadowsparky.updater.backend.presentation
|
||||||
|
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondFile
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.util.getOrFail
|
||||||
|
import ru.shadowsparky.backend.presentation.obtainUserId
|
||||||
|
import ru.shadowsparky.updater.backend.data.BackendUpdateFetcherFactory
|
||||||
|
import ru.shadowsparky.updater.domain.UpdateFetcher
|
||||||
|
|
||||||
|
fun Route.setupUpdates(
|
||||||
|
updateFetcherFactory: BackendUpdateFetcherFactory,
|
||||||
|
prefix: String = "",
|
||||||
|
onUserId: suspend ApplicationCall.() -> Long = { obtainUserId() }
|
||||||
|
) {
|
||||||
|
get("${prefix}checkUpdates") {
|
||||||
|
val versionCode = call.parameters.getOrFail(UpdateFetcher.ARG_VERSION_CODE).toLong()
|
||||||
|
val updateFetcher = updateFetcherFactory.create(onUserId(call))
|
||||||
|
call.respond(updateFetcher.fetch(versionCode))
|
||||||
|
}
|
||||||
|
get("${prefix}getApk") {
|
||||||
|
val updateFetcher = updateFetcherFactory.create(onUserId(call))
|
||||||
|
call.respondFile(updateFetcher.updateFile())
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-18
@@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
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.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
@@ -42,7 +44,6 @@ import org.jetbrains.compose.resources.StringResource
|
|||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.shadowsparky.ui.components.AlertContent
|
import ru.shadowsparky.ui.components.AlertContent
|
||||||
import ru.shadowsparky.ui.components.ErrorContent
|
import ru.shadowsparky.ui.components.ErrorContent
|
||||||
import ru.shadowsparky.ui.components.LazyColumn
|
|
||||||
import ru.shadowsparky.ui.components.Loading
|
import ru.shadowsparky.ui.components.Loading
|
||||||
import ru.shadowsparky.updater.domain.UpdateState
|
import ru.shadowsparky.updater.domain.UpdateState
|
||||||
|
|
||||||
@@ -128,8 +129,9 @@ private fun UpdateAvailableContent(
|
|||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -137,26 +139,18 @@ private fun UpdateAvailableContent(
|
|||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
)
|
)
|
||||||
LazyColumn(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
item {
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.updater_changelog_title),
|
text = stringResource(Res.string.updater_changelog_title),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
}
|
|
||||||
item {
|
|
||||||
Text(
|
Text(
|
||||||
text = changeLog ?: stringResource(Res.string.updater_changelog_empty),
|
text = changeLog ?: stringResource(Res.string.updater_changelog_empty),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
|
||||||
item {
|
|
||||||
Button(
|
Button(
|
||||||
onClick = onDownloadClick,
|
onClick = onDownloadClick,
|
||||||
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth()
|
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth()
|
||||||
@@ -168,8 +162,6 @@ private fun UpdateAvailableContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UpdatePermissionContent(
|
private fun UpdatePermissionContent(
|
||||||
@@ -177,8 +169,9 @@ private fun UpdatePermissionContent(
|
|||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -210,8 +203,9 @@ private fun UpdateDownloadingContent(
|
|||||||
totalBytes: Long,
|
totalBytes: Long,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -241,8 +235,9 @@ private fun UpdateReadyContent(
|
|||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -269,8 +264,9 @@ private fun UpdateFinishedContent(
|
|||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -32,12 +34,13 @@ fun ErrorContent(
|
|||||||
retry: Boolean = true,
|
retry: Boolean = true,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
val requester = remember { FocusRequester() }
|
val requester = remember { FocusRequester() }
|
||||||
val buttonTextRes = if (retry) Res.string.retry else Res.string.ok
|
val buttonTextRes = if (retry) Res.string.retry else Res.string.ok
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = modifier.padding(4.dp)
|
modifier = modifier.verticalScroll(scrollState).padding(4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.error_title),
|
text = stringResource(Res.string.error_title),
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ include(":libs:http-client")
|
|||||||
include(":feature:video-player")
|
include(":feature:video-player")
|
||||||
include(":feature:updater:updater-client")
|
include(":feature:updater:updater-client")
|
||||||
include(":feature:updater:updater-common")
|
include(":feature:updater:updater-common")
|
||||||
|
include(":feature:updater:updater-backend")
|
||||||
include(":feature:chat:chat-client")
|
include(":feature:chat:chat-client")
|
||||||
include(":feature:chat:chat-common")
|
include(":feature:chat:chat-common")
|
||||||
include(":feature:chat:chat-backend")
|
include(":feature:chat:chat-backend")
|
||||||
|
|||||||
Reference in New Issue
Block a user