This commit is contained in:
2026-07-29 16:21:59 +03:00
parent b83a40f4bd
commit 6485f4ec26
41 changed files with 797 additions and 224 deletions
+2
View File
@@ -27,6 +27,8 @@ kotlin {
implementation(project(":libs:http-client"))
implementation(project(":feature:video-player"))
implementation(project(":feature:updater:updater-client"))
implementation(project(":feature:chat:chat-client"))
implementation(project(":feature:chat:chat-common"))
}
androidMain.dependencies {
implementation(libs.androidx.appcompat)
@@ -0,0 +1,43 @@
package ru.shadowsparky.vbox.shared.data
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import org.koin.core.annotation.Factory
import ru.shadowsparky.chat.domain.ChatRepository
import ru.shadowsparky.chat.domain.Message
import ru.shadowsparky.chat.domain.MessagesResponse
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
import ru.shadowsparky.vbox.shared.domain.ServerConfigurationRepository
import ru.shadowsparky.vbox.shared.domain.getServerConfiguration
@Factory
class RemoteChatRepository(
private val httpClient: HttpClient,
private val serverConfigurationRepository: ServerConfigurationRepository
) : ChatRepository {
override suspend fun query(afterId: Long?): List<Message> {
val args: Map<String, String> = if (afterId == null) {
emptyMap()
} else {
mapOf(ChatRepository.ARG_AFTER_ID to afterId.toString())
}
return httpClient.get<MessagesResponse>(
"$DYNAMIC_PREFIX/chat/query",
args,
serverConfigurationRepository.getServerConfiguration()
).content
}
override suspend fun send(message: Message) {
httpClient.post {
setBody(message)
serverConfigurationRepository.getServerConfiguration().apply(
this,
"$DYNAMIC_PREFIX/chat/send"
)
}.body<Message>()
}
}
@@ -12,6 +12,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.seiko.imageloader.LocalImageLoader
import ru.shadowsparky.chat.presentation.ChatScreen
import ru.shadowsparky.koin
import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.nav.Nav
@@ -63,6 +64,7 @@ private fun NavInternal(root: VBoxRootComponent) {
is Child.EditPasswordChild -> EditPasswordScreen(child.component)
is Child.LoadingChild -> LoadingScreen(child.component)
is Child.OfflineVideoChild -> OfflineVideoScreen(child.component)
is Child.ChatChild -> ChatScreen(child.component)
is Child.VideosChild,
is Child.SearchChild,
is Child.SettingsChild,
@@ -0,0 +1,30 @@
package ru.shadowsparky.vbox.shared.presentation.chat
import com.arkivanov.decompose.ComponentContext
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Named
import ru.shadowsparky.chat.domain.ChatRepository
import ru.shadowsparky.chat.presentation.ChatComponent
import ru.shadowsparky.ui.nav.ComponentFactory
import ru.shadowsparky.ui.nav.RouteNavigator
import ru.shadowsparky.vbox.shared.presentation.nav.Child
import ru.shadowsparky.vbox.shared.presentation.nav.Route
import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds
@Factory
@Named(RouteIds.CHAT)
class ChatComponentFactory(
private val chatRepository: ChatRepository
) : ComponentFactory<Child.ChatChild, Route.Chat, Route> {
override fun create(
route: Route.Chat,
child: ComponentContext,
root: RouteNavigator<Route>
): Child.ChatChild {
val comp = ChatComponent(
child,
chatRepository
)
return Child.ChatChild(comp)
}
}
@@ -1,5 +1,6 @@
package ru.shadowsparky.vbox.shared.presentation.nav
import ru.shadowsparky.chat.presentation.ChatComponent
import ru.shadowsparky.vbox.shared.presentation.list.VideoScreenComponent
import ru.shadowsparky.vbox.shared.presentation.loading.LoadingComponent
import ru.shadowsparky.vbox.shared.presentation.offline.OfflineVideoComponent
@@ -32,4 +33,5 @@ sealed class Child : ru.shadowsparky.ui.nav.Child {
class LoadingChild(val component: LoadingComponent) : Child()
class PlayerChild(val component: PlayerComponent) : Child()
class TagChild(val component: TagComponent) : Child()
class ChatChild(val component: ChatComponent) : Child()
}
@@ -21,6 +21,9 @@ sealed class Route : ru.shadowsparky.ui.nav.Route() {
@Serializable
data class OfflineVideo(override val routeId: String = RouteIds.OFFLINE_VIDEO) : Route()
@Serializable
data class Chat(override val routeId: String = RouteIds.CHAT) : Route()
@Serializable
data class Loading(
val authRequired: Boolean = true,
@@ -21,4 +21,5 @@ object RouteIds {
const val REGISTER = "register"
const val EDIT_PASSWORD = "editPassword"
const val TAG_OVERVIEW = "tags"
const val CHAT = "chat"
}
@@ -37,12 +37,11 @@ class SettingsComponent(
val doRegister: () -> Unit,
val editPassword: () -> Unit,
val showOffline: () -> Unit,
val showTags: () -> Unit
val showTags: () -> Unit,
val showChat: () -> Unit
) : ComponentContext by context {
val updateComponent = updateComponentFactory.create(childContext("UpdateComponent"))
val hasUpdateSupport = updateComponent.hasSupport
val serverInfo = serverConfigurationRepository.serverConfiguration.map { it.asHttpStr() }
private val componentScope = createComponentScope()
@@ -101,7 +100,8 @@ class SettingsComponentFactory(
tokenStorage = tokenStorage,
showOffline = { root.nav(Route.OfflineVideo()) },
offlineSupport = offlineStorage != null,
showTags = { root.nav(Route.Settings.TagOverview()) }
showTags = { root.nav(Route.Settings.TagOverview()) },
showChat = { root.nav(Route.Chat())}
)
return Child.SettingsChild(comp)
}
@@ -82,6 +82,12 @@ fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
)
}
}
item {
TextPreference(
text = "chat",
modifier = Modifier.clickable { comp.showChat() }
)
}
}
section {
val needAuth = tokenInfo == null