impl chat user interface
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M668.5,428.5Q680,417 680,400Q680,383 668.5,371.5Q657,360 640,360Q623,360 611.5,371.5Q600,383 600,400Q600,417 611.5,428.5Q623,440 640,440Q657,440 668.5,428.5ZM320,360L520,360L520,280L320,280L320,360ZM180,840Q146,726 113,612.5Q80,499 80,380Q80,288 144,224Q208,160 300,160L500,160Q529,122 570.5,101Q612,80 660,80Q685,80 702.5,97.5Q720,115 720,140Q720,146 718.5,152Q717,158 715,163Q711,174 707.5,185.5Q704,197 702,209L793,300L880,300L880,579L767,616L700,840L480,840L480,760L400,760L400,840L180,840ZM240,760L320,760L320,680L560,680L560,760L640,760L702,554L800,521L800,380L760,380L620,240Q620,220 622.5,201.5Q625,183 630,164Q601,172 579,191.5Q557,211 547,240L300,240Q242,240 201,281Q160,322 160,380Q160,478 187,571.5Q214,665 240,760ZM480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462L480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462Q480,462 480,462L480,462L480,462L480,462L480,462L480,462L480,462L480,462L480,462L480,462Z"/>
|
||||
</vector>
|
||||
@@ -68,4 +68,6 @@
|
||||
<string name="movie_tags_empty">Настроить теги</string>
|
||||
<string name="user_tags_filter_disable">Не фильтровать</string>
|
||||
<string name="check_for_updates">Поиск обновлений</string>
|
||||
<string name="chat_title_app_bar">Братан СвинGPT</string>
|
||||
<string name="chat_title">СвинGPT</string>
|
||||
</resources>
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package ru.shadowsparky.vbox.shared.data.chat
|
||||
|
||||
import org.koin.core.annotation.Factory
|
||||
import ru.shadowsparky.chat.domain.ChatTextParser
|
||||
import ru.shadowsparky.chat.domain.TextToken
|
||||
|
||||
@Factory
|
||||
class MovieTextParser : ChatTextParser {
|
||||
private val regex = Regex("\\[MOVIE\\](.*?)\\[/MOVIE\\]")
|
||||
|
||||
override fun parse(content: String): List<TextToken> {
|
||||
val tokens = mutableListOf<TextToken>()
|
||||
var lastIndex = 0
|
||||
regex.findAll(content).forEach { matchResult ->
|
||||
if (matchResult.range.first > lastIndex) {
|
||||
tokens.add(TextToken.Plain(content.substring(lastIndex, matchResult.range.first)))
|
||||
}
|
||||
val movieTitle = matchResult.groupValues[1]
|
||||
tokens.add(TextToken.Link(text = movieTitle))
|
||||
lastIndex = matchResult.range.last + 1
|
||||
}
|
||||
if (lastIndex < content.length) {
|
||||
tokens.add(TextToken.Plain(content.substring(lastIndex)))
|
||||
}
|
||||
return tokens.ifEmpty { listOf(TextToken.Plain(content)) }
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
package ru.shadowsparky.vbox.shared.data
|
||||
package ru.shadowsparky.vbox.shared.data.chat
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
@@ -8,6 +8,8 @@ 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.data.apply
|
||||
import ru.shadowsparky.vbox.shared.data.get
|
||||
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
|
||||
import ru.shadowsparky.vbox.shared.domain.ServerConfigurationRepository
|
||||
import ru.shadowsparky.vbox.shared.domain.getServerConfiguration
|
||||
@@ -25,7 +27,7 @@ class RemoteChatRepository(
|
||||
mapOf(ChatRepository.ARG_AFTER_ID to afterId.toString())
|
||||
}
|
||||
return httpClient.get<MessagesResponse>(
|
||||
"$DYNAMIC_PREFIX/chat/query",
|
||||
"${DYNAMIC_PREFIX}/chat/query",
|
||||
args,
|
||||
serverConfigurationRepository.getServerConfiguration()
|
||||
).content
|
||||
@@ -36,7 +38,7 @@ class RemoteChatRepository(
|
||||
setBody(message)
|
||||
serverConfigurationRepository.getServerConfiguration().apply(
|
||||
this,
|
||||
"$DYNAMIC_PREFIX/chat/send"
|
||||
"${DYNAMIC_PREFIX}/chat/send"
|
||||
)
|
||||
}.body<Message>()
|
||||
}
|
||||
+1
-2
@@ -12,7 +12,6 @@ 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
|
||||
@@ -64,10 +63,10 @@ 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,
|
||||
is Child.ChatChild,
|
||||
is Child.SavedMovieChild -> {
|
||||
if (child is Child.VideosChild && child.component.search != null) {
|
||||
VideoScreen(child.component)
|
||||
|
||||
+7
@@ -1,12 +1,14 @@
|
||||
package ru.shadowsparky.vbox.shared.presentation
|
||||
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.Res
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.chat_title
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.home
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.recommendations
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.saved_movies
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.search
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.settings
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.star_unchecked
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.svingpt
|
||||
import androidx.compose.runtime.Composable
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
@@ -31,6 +33,11 @@ fun createNavigationList(): List<NavigationEntry<Route>> {
|
||||
stringResource(Res.string.search),
|
||||
painterResource(Res.drawable.search)
|
||||
),
|
||||
NavigationEntry(
|
||||
Route.Chat(),
|
||||
stringResource(Res.string.chat_title),
|
||||
painterResource(Res.drawable.svingpt)
|
||||
),
|
||||
NavigationEntry(
|
||||
Route.Settings(),
|
||||
stringResource(Res.string.settings),
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ import ru.shadowsparky.ui.LocalBlurState
|
||||
import ru.shadowsparky.ui.applyBlurEffect
|
||||
import ru.shadowsparky.ui.rememberBlurState
|
||||
import ru.shadowsparky.ui.theme.ComponentDefaults
|
||||
import ru.shadowsparky.vbox.shared.presentation.chat.ChatScreen
|
||||
import ru.shadowsparky.vbox.shared.presentation.list.VideoContent
|
||||
import ru.shadowsparky.vbox.shared.presentation.nav.Child
|
||||
import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
||||
@@ -29,7 +30,7 @@ fun MainScreen(route: Route, child: Child, root: VBoxRootComponent) {
|
||||
child.component,
|
||||
route
|
||||
) { root.navReplace(it) }
|
||||
|
||||
is Child.ChatChild -> ChatScreen(child.component, route) { root.navReplace(it) }
|
||||
else -> MainScreenInternal(route, child) { root.navReplace(it) }
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -4,6 +4,7 @@ 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.domain.ChatTextParser
|
||||
import ru.shadowsparky.chat.presentation.ChatComponent
|
||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
||||
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||
@@ -14,7 +15,8 @@ import ru.shadowsparky.vbox.shared.presentation.nav.RouteIds
|
||||
@Factory
|
||||
@Named(RouteIds.CHAT)
|
||||
class ChatComponentFactory(
|
||||
private val chatRepository: ChatRepository
|
||||
private val chatRepository: ChatRepository,
|
||||
private val textParser: ChatTextParser
|
||||
) : ComponentFactory<Child.ChatChild, Route.Chat, Route> {
|
||||
override fun create(
|
||||
route: Route.Chat,
|
||||
@@ -23,7 +25,9 @@ class ChatComponentFactory(
|
||||
): Child.ChatChild {
|
||||
val comp = ChatComponent(
|
||||
child,
|
||||
chatRepository
|
||||
chatRepository,
|
||||
textParser,
|
||||
onLinkClicked = { root.nav(Route.Videos(it)) }
|
||||
)
|
||||
return Child.ChatChild(comp)
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package ru.shadowsparky.vbox.shared.presentation.chat
|
||||
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.Res
|
||||
import androiddev.apps.vbox.client.shared.generated.resources.chat_title_app_bar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import ru.shadowsparky.chat.presentation.ChatComponent
|
||||
import ru.shadowsparky.chat.presentation.ChatContent
|
||||
import ru.shadowsparky.ui.LocalBlurState
|
||||
import ru.shadowsparky.ui.applyBlurEffect
|
||||
import ru.shadowsparky.ui.theme.ComponentDefaults
|
||||
import ru.shadowsparky.vbox.shared.presentation.BaseMainScreen
|
||||
import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ChatScreen(component: ChatComponent, route: Route, onNavigate: (Route) -> Unit) {
|
||||
val state by component.state.subscribeAsState()
|
||||
BaseMainScreen(
|
||||
appBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(Res.string.chat_title_app_bar)) },
|
||||
colors = ComponentDefaults.topAppBarColors(),
|
||||
modifier = Modifier.applyBlurEffect(LocalBlurState.current)
|
||||
)
|
||||
},
|
||||
route,
|
||||
onNavigate = onNavigate,
|
||||
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
|
||||
content = { ChatContent(component, it, state) }
|
||||
)
|
||||
}
|
||||
+2
-4
@@ -37,8 +37,7 @@ class SettingsComponent(
|
||||
val doRegister: () -> Unit,
|
||||
val editPassword: () -> Unit,
|
||||
val showOffline: () -> Unit,
|
||||
val showTags: () -> Unit,
|
||||
val showChat: () -> Unit
|
||||
val showTags: () -> Unit
|
||||
) : ComponentContext by context {
|
||||
val updateComponent = updateComponentFactory.create(childContext("UpdateComponent"))
|
||||
|
||||
@@ -100,8 +99,7 @@ class SettingsComponentFactory(
|
||||
tokenStorage = tokenStorage,
|
||||
showOffline = { root.nav(Route.OfflineVideo()) },
|
||||
offlineSupport = offlineStorage != null,
|
||||
showTags = { root.nav(Route.Settings.TagOverview()) },
|
||||
showChat = { root.nav(Route.Chat())}
|
||||
showTags = { root.nav(Route.Settings.TagOverview()) }
|
||||
)
|
||||
return Child.SettingsChild(comp)
|
||||
}
|
||||
|
||||
-6
@@ -82,12 +82,6 @@ fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
TextPreference(
|
||||
text = "chat",
|
||||
modifier = Modifier.clickable { comp.showChat() }
|
||||
)
|
||||
}
|
||||
}
|
||||
section {
|
||||
val needAuth = tokenInfo == null
|
||||
|
||||
Reference in New Issue
Block a user