mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-01 22:18:43 +00:00
multiplatform: product specification (#6655)
This commit is contained in:
+1
@@ -108,6 +108,7 @@ class ActiveCallState: Closeable {
|
||||
}
|
||||
|
||||
|
||||
// Spec: spec/services/calls.md#ActiveCallView
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Composable
|
||||
actual fun ActiveCallView() {
|
||||
|
||||
@@ -42,6 +42,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
// Spec: spec/client/navigation.md#AppScreen
|
||||
@Composable
|
||||
fun AppScreen() {
|
||||
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
|
||||
@@ -78,6 +79,7 @@ fun AppScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/client/navigation.md#MainScreen
|
||||
@Composable
|
||||
fun MainScreen() {
|
||||
val chatModel = ChatModel
|
||||
@@ -289,6 +291,7 @@ fun AndroidWrapInCallLayout(content: @Composable () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/client/navigation.md#AndroidScreen
|
||||
@Composable
|
||||
fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||
BoxWithConstraints {
|
||||
@@ -402,6 +405,7 @@ fun EndPartOfScreen() {
|
||||
ModalManager.end.showInView()
|
||||
}
|
||||
|
||||
// Spec: spec/client/navigation.md#DesktopScreen
|
||||
@Composable
|
||||
fun DesktopScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||
Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
// Spec: spec/client/navigation.md#AppLock
|
||||
object AppLock {
|
||||
/**
|
||||
* We don't want these values to be bound to Activity lifecycle since activities are changed often, for example, when a user
|
||||
|
||||
@@ -81,6 +81,7 @@ val connectProgressManager = ConnectProgressManager
|
||||
/*
|
||||
* Without this annotation an animation from ChatList to ChatView has 1 frame per the whole animation. Don't delete it
|
||||
* */
|
||||
// Spec: spec/state.md#ChatModel
|
||||
@Stable
|
||||
object ChatModel {
|
||||
val controller: ChatController = ChatController
|
||||
@@ -334,6 +335,7 @@ object ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/state.md#ChatsContext
|
||||
class ChatsContext(val secondaryContextFilter: SecondaryContextFilter?) {
|
||||
val chats = mutableStateOf(SnapshotStateList<Chat>())
|
||||
/** if you modify the items by adding/removing them, use helpers methods like [addToChatItems], [removeLastChatItems], [removeAllAndNotify], [clearAndNotify] and so on.
|
||||
@@ -1321,6 +1323,7 @@ interface SomeChat {
|
||||
val updatedAt: Instant
|
||||
}
|
||||
|
||||
// Spec: spec/state.md#Chat
|
||||
@Serializable @Stable
|
||||
data class Chat(
|
||||
val remoteHostId: Long?,
|
||||
@@ -1362,6 +1365,7 @@ data class Chat(
|
||||
true
|
||||
}
|
||||
|
||||
// Spec: spec/state.md#ChatStats
|
||||
@Serializable
|
||||
data class ChatStats(
|
||||
val unreadCount: Int = 0,
|
||||
@@ -1382,6 +1386,7 @@ data class Chat(
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/state.md#ChatInfo
|
||||
@Serializable
|
||||
sealed class ChatInfo: SomeChat, NamedChat {
|
||||
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ sealed class WriteFileResult {
|
||||
}
|
||||
* */
|
||||
|
||||
// Spec: spec/services/files.md#writeCryptoFile
|
||||
fun writeCryptoFile(path: String, data: ByteArray): CryptoFileArgs {
|
||||
val ctrl = ChatController.getChatCtrl() ?: throw Exception("Controller is not initialized")
|
||||
val buffer = ByteBuffer.allocateDirect(data.size)
|
||||
|
||||
+14
@@ -90,6 +90,7 @@ enum class SimplexLinkMode {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/state.md#AppPreferences
|
||||
class AppPreferences {
|
||||
// deprecated, remove in 2024
|
||||
private val runServiceInBackground = mkBoolPreference(SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND, true)
|
||||
@@ -491,6 +492,7 @@ private const val MESSAGE_TIMEOUT: Int = 300_000_000
|
||||
|
||||
object ChatController {
|
||||
private var chatCtrl: ChatCtrl? = -1
|
||||
// Spec: spec/state.md#appPrefs
|
||||
val appPrefs: AppPreferences by lazy { AppPreferences() }
|
||||
|
||||
val messagesChannel: Channel<API> = Channel()
|
||||
@@ -654,6 +656,7 @@ object ChatController {
|
||||
chatModel.updateChatTags(rhId)
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#startReceiver
|
||||
private fun startReceiver() {
|
||||
Log.d(TAG, "ChatController startReceiver")
|
||||
if (receiverJob != null || chatCtrl == null) return
|
||||
@@ -797,6 +800,7 @@ object ChatController {
|
||||
return null
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#sendCmd
|
||||
suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, retryNum: Int = 0, log: Boolean = true): API {
|
||||
val ctrl = otherCtrl ?: chatCtrl ?: throw Exception("Controller is not initialized")
|
||||
|
||||
@@ -821,6 +825,7 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#recvMsg
|
||||
fun recvMsg(ctrl: ChatCtrl): API? {
|
||||
val rStr = chatRecvMsgWait(ctrl, MESSAGE_TIMEOUT)
|
||||
return if (rStr == "") {
|
||||
@@ -2559,6 +2564,7 @@ object ChatController {
|
||||
AlertManager.shared.showAlertMsg(title, errMsg)
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#processReceivedMsg
|
||||
private suspend fun processReceivedMsg(msg: API) {
|
||||
lastMsgReceivedTimestamp = System.currentTimeMillis()
|
||||
val rhId = msg.rhId
|
||||
@@ -3519,6 +3525,7 @@ class SharedPreference<T>(val get: () -> T, set: (T) -> Unit) {
|
||||
}
|
||||
|
||||
// ChatCommand
|
||||
// Spec: spec/api.md#CC
|
||||
sealed class CC {
|
||||
class Console(val cmd: String): CC()
|
||||
class ShowActiveUser: CC()
|
||||
@@ -4150,9 +4157,11 @@ class UpdatedMessage(val msgContent: MsgContent, val mentions: Map<String, Long>
|
||||
@Serializable
|
||||
class ChatTagData(val emoji: String?, val text: String)
|
||||
|
||||
// Spec: spec/api.md#ArchiveConfig
|
||||
@Serializable
|
||||
class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? = null, val parentTempDirectory: String? = null)
|
||||
|
||||
// Spec: spec/database.md#DBEncryptionConfig
|
||||
@Serializable
|
||||
class DBEncryptionConfig(val currentKey: String, val newKey: String)
|
||||
|
||||
@@ -5960,6 +5969,7 @@ val yaml = Yaml(configuration = YamlConfiguration(
|
||||
codePointLimit = 5500000,
|
||||
))
|
||||
|
||||
// Spec: spec/api.md#API
|
||||
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
|
||||
@Serializable(with = APISerializer::class)
|
||||
sealed class API {
|
||||
@@ -6099,6 +6109,7 @@ private fun <T> decodeObject(deserializer: DeserializationStrategy<T>, obj: Json
|
||||
runCatching { json.decodeFromJsonElement(deserializer, obj!!) }.getOrNull()
|
||||
|
||||
// ChatResponse
|
||||
// Spec: spec/api.md#CR
|
||||
@Serializable
|
||||
sealed class CR {
|
||||
@Serializable @SerialName("activeUser") class ActiveUser(val user: User): CR()
|
||||
@@ -6958,6 +6969,7 @@ data class RemoteFile(
|
||||
val fileSource: CryptoFile
|
||||
)
|
||||
|
||||
// Spec: spec/api.md#ChatError
|
||||
@Serializable
|
||||
sealed class ChatError {
|
||||
val string: String get() = when (this) {
|
||||
@@ -7641,6 +7653,7 @@ sealed class RCErrorType {
|
||||
@Serializable @SerialName("syntax") data class SYNTAX(val syntaxErr: String): RCErrorType()
|
||||
}
|
||||
|
||||
// Spec: spec/database.md#ArchiveError
|
||||
@Serializable
|
||||
sealed class ArchiveError {
|
||||
val string: String get() = when (this) {
|
||||
@@ -7722,6 +7735,7 @@ sealed class RemoteCtrlError {
|
||||
@Serializable @SerialName("protocolError") object ProtocolError: RemoteCtrlError()
|
||||
}
|
||||
|
||||
// Spec: spec/services/notifications.md#NotificationsMode
|
||||
enum class NotificationsMode() {
|
||||
OFF, PERIODIC, SERVICE, /*INSTANT - for Firebase notifications */;
|
||||
|
||||
|
||||
@@ -14,12 +14,14 @@ import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
// ghc's rts
|
||||
// Spec: spec/architecture.md#initHS
|
||||
external fun initHS()
|
||||
// android-support
|
||||
external fun pipeStdOutToSocket(socketName: String) : Int
|
||||
|
||||
// SimpleX API
|
||||
typealias ChatCtrl = Long
|
||||
// Spec: spec/architecture.md#chatMigrateInit
|
||||
external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array<Any>
|
||||
external fun chatCloseStore(ctrl: ChatCtrl): String
|
||||
external fun chatSendCmdRetry(ctrl: ChatCtrl, msg: String, retryNum: Int): String
|
||||
@@ -45,6 +47,7 @@ val appPreferences: AppPreferences
|
||||
|
||||
val chatController: ChatController = ChatController
|
||||
|
||||
// Spec: spec/architecture.md#initChatControllerOnStart
|
||||
fun initChatControllerOnStart() {
|
||||
withLongRunningApi {
|
||||
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
|
||||
@@ -55,6 +58,7 @@ fun initChatControllerOnStart() {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/architecture.md#initChatController
|
||||
suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: () -> CompletableDeferred<Boolean> = { CompletableDeferred(true) }) {
|
||||
Log.d(TAG, "initChatController")
|
||||
try {
|
||||
@@ -182,6 +186,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/architecture.md#chatInitTemporaryDatabase
|
||||
fun chatInitTemporaryDatabase(dbPath: String, key: String? = null, confirmation: MigrationConfirmation = MigrationConfirmation.Error): Pair<DBMigrationResult, ChatCtrl?> {
|
||||
val dbKey = key ?: randomDatabasePassword()
|
||||
Log.d(TAG, "chatInitTemporaryDatabase path: $dbPath")
|
||||
@@ -193,6 +198,7 @@ fun chatInitTemporaryDatabase(dbPath: String, key: String? = null, confirmation:
|
||||
return res to migrated[1] as ChatCtrl
|
||||
}
|
||||
|
||||
// Spec: spec/architecture.md#chatInitControllerRemovingDatabases
|
||||
fun chatInitControllerRemovingDatabases() {
|
||||
val dbPath = dbAbsolutePrefixPath
|
||||
// Remove previous databases, otherwise, can be .errorNotADatabase with null controller
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.net.URLEncoder
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
// Spec: spec/services/files.md#dataDir
|
||||
expect val dataDir: File
|
||||
expect val tmpDir: File
|
||||
expect val filesDir: File
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ enum class NotificationAction {
|
||||
ACCEPT_CONTACT_REQUEST
|
||||
}
|
||||
|
||||
// Spec: spec/services/notifications.md#ntfManager
|
||||
lateinit var ntfManager: NtfManager
|
||||
|
||||
abstract class NtfManager {
|
||||
|
||||
@@ -22,6 +22,7 @@ import chat.simplex.res.MR
|
||||
import kotlinx.serialization.Transient
|
||||
import java.util.UUID
|
||||
|
||||
// Spec: spec/services/theme.md#DefaultTheme
|
||||
enum class DefaultTheme {
|
||||
LIGHT, DARK, SIMPLEX, BLACK;
|
||||
|
||||
@@ -47,6 +48,7 @@ enum class DefaultThemeMode {
|
||||
@SerialName("dark") DARK
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#AppColors
|
||||
@Stable
|
||||
class AppColors(
|
||||
title: Color,
|
||||
@@ -99,6 +101,7 @@ class AppColors(
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#AppWallpaper
|
||||
@Stable
|
||||
class AppWallpaper(
|
||||
background: Color? = null,
|
||||
@@ -133,6 +136,7 @@ class AppWallpaper(
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#ThemeColor
|
||||
enum class ThemeColor {
|
||||
PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, SENT_QUOTE, RECEIVED_MESSAGE, RECEIVED_QUOTE, PRIMARY_VARIANT2, WALLPAPER_BACKGROUND, WALLPAPER_TINT;
|
||||
|
||||
@@ -174,6 +178,7 @@ enum class ThemeColor {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#ThemeColors
|
||||
@Serializable
|
||||
data class ThemeColors(
|
||||
@SerialName("accent")
|
||||
@@ -214,6 +219,7 @@ data class ThemeColors(
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#ThemeWallpaper
|
||||
@Serializable
|
||||
data class ThemeWallpaper (
|
||||
val preset: String? = null,
|
||||
@@ -293,6 +299,7 @@ data class ThemesFile(
|
||||
val themes: List<ThemeOverrides> = emptyList()
|
||||
)
|
||||
|
||||
// Spec: spec/services/theme.md#ThemeOverrides
|
||||
@Serializable
|
||||
data class ThemeOverrides (
|
||||
val themeId: String = UUID.randomUUID().toString(),
|
||||
@@ -463,6 +470,7 @@ fun List<ThemeOverrides>.skipDuplicates(): List<ThemeOverrides> {
|
||||
return res
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#ThemeModeOverrides
|
||||
@Serializable
|
||||
data class ThemeModeOverrides (
|
||||
val light: ThemeModeOverride? = null,
|
||||
@@ -474,6 +482,7 @@ data class ThemeModeOverrides (
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#ThemeModeOverride
|
||||
@Serializable
|
||||
data class ThemeModeOverride (
|
||||
val mode: DefaultThemeMode = CurrentColors.value.base.mode,
|
||||
@@ -714,6 +723,7 @@ val BlackColorPaletteApp = AppColors(
|
||||
|
||||
var systemInDarkThemeCurrently: Boolean = isInNightMode()
|
||||
|
||||
// Spec: spec/services/theme.md#CurrentColors
|
||||
val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow(ThemeManager.currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()))
|
||||
|
||||
@Composable
|
||||
@@ -758,6 +768,7 @@ fun reactOnDarkThemeChanges(isDark: Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#SimpleXTheme
|
||||
@Composable
|
||||
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
|
||||
// TODO: Fix preview working with dark/light theme
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ object ThemeManager {
|
||||
?: ThemeWallpaper.from(PresetWallpaper.SCHOOL.toType(CurrentColors.value.base), null, null))
|
||||
}
|
||||
|
||||
// Spec: spec/services/theme.md#currentColors
|
||||
fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List<ThemeOverrides>): ActiveTheme {
|
||||
val themeName = appPrefs.currentTheme.get()!!
|
||||
val nonSystemThemeName = nonSystemThemeName()
|
||||
|
||||
+1
@@ -6,6 +6,7 @@ import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.platform.*
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
// Spec: spec/services/calls.md#ActiveCallView
|
||||
@Composable
|
||||
expect fun ActiveCallView()
|
||||
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ import chat.simplex.common.views.usersettings.ProfilePreview
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
// Spec: spec/services/calls.md#IncomingCallAlertView
|
||||
@Composable
|
||||
fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
|
||||
val cm = chatModel.callManager
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ data class Call(
|
||||
get() = localMediaSources.hasVideo || peerMediaSources.hasVideo
|
||||
}
|
||||
|
||||
// Spec: spec/services/calls.md#CallState
|
||||
enum class CallState {
|
||||
WaitCapabilities,
|
||||
InvitationSent,
|
||||
|
||||
+1
@@ -93,6 +93,7 @@ fun ConnectInProgressView(s: String) {
|
||||
@Composable
|
||||
// staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat
|
||||
// to chat list smooth. Otherwise, chat view will become blank right before the transition starts
|
||||
// Spec: spec/client/chat-view.md#ChatView
|
||||
fun ChatView(
|
||||
chatsCtx: ChatModel.ChatsContext,
|
||||
staleChatId: State<String?>,
|
||||
|
||||
+4
@@ -47,6 +47,7 @@ import kotlin.math.min
|
||||
|
||||
const val MAX_NUMBER_OF_MENTIONS = 3
|
||||
|
||||
// Spec: spec/client/compose.md#ComposePreview
|
||||
@Serializable
|
||||
sealed class ComposePreview {
|
||||
@Serializable object NoPreview: ComposePreview()
|
||||
@@ -92,6 +93,7 @@ object ComposeMessageSerializer : KSerializer<TextRange> {
|
||||
decoder.decodeLong().let { value -> TextRange(unpackInt1(value), unpackInt2(value)) }
|
||||
}
|
||||
|
||||
// Spec: spec/client/compose.md#ComposeState
|
||||
@Serializable
|
||||
data class ComposeState(
|
||||
val message: ComposeMessage = ComposeMessage(),
|
||||
@@ -259,6 +261,7 @@ fun chatItemPreview(chatItem: ChatItem): ComposePreview {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/client/compose.md#AttachmentSelection
|
||||
@Composable
|
||||
expect fun AttachmentSelection(
|
||||
composeState: MutableState<ComposeState>,
|
||||
@@ -341,6 +344,7 @@ suspend fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text:
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/client/compose.md#ComposeView
|
||||
@Composable
|
||||
fun ComposeView(
|
||||
rhId: Long?,
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ import dev.icerock.moko.resources.compose.painterResource
|
||||
import kotlinx.coroutines.*
|
||||
import java.net.URI
|
||||
|
||||
// Spec: spec/client/compose.md#SendMsgView
|
||||
@Composable
|
||||
fun SendMsgView(
|
||||
composeState: MutableState<ComposeState>,
|
||||
|
||||
+1
@@ -61,6 +61,7 @@ data class ChatItemReactionMenuItem (
|
||||
val onClick: (() -> Unit)?
|
||||
)
|
||||
|
||||
// Spec: spec/client/chat-view.md#ChatItemView
|
||||
@Composable
|
||||
fun ChatItemView(
|
||||
chatsCtx: ChatModel.ChatsContext,
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
// Spec: spec/client/chat-list.md#ChatListNavLinkView
|
||||
@Composable
|
||||
fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
|
||||
val showMenu = remember { mutableStateOf(false) }
|
||||
|
||||
+1
@@ -122,6 +122,7 @@ fun ToggleChatListCard() {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/client/chat-list.md#ChatListView
|
||||
@Composable
|
||||
fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow<AnimatedViewState>, setPerformLA: (Boolean) -> Unit, stopped: Boolean) {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ import chat.simplex.common.views.chat.item.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.ImageResource
|
||||
|
||||
// Spec: spec/client/chat-list.md#ChatPreviewView
|
||||
@Composable
|
||||
fun ChatPreviewView(
|
||||
chat: Chat,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
// Spec: spec/client/chat-list.md#TagListView
|
||||
@Composable
|
||||
fun TagListView(rhId: Long?, chat: Chat? = null, close: () -> Unit, reorderMode: Boolean) {
|
||||
val userTags = remember { chatModel.userTags }
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.*
|
||||
|
||||
private val USER_PICKER_SECTION_SPACING = 32.dp
|
||||
|
||||
// Spec: spec/client/chat-list.md#UserPicker
|
||||
@Composable
|
||||
fun UserPicker(
|
||||
chatModel: ChatModel,
|
||||
|
||||
+1
@@ -74,6 +74,7 @@ object DatabaseUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/database.md#DBMigrationResult
|
||||
@Serializable
|
||||
sealed class DBMigrationResult {
|
||||
@Serializable @SerialName("ok") object OK: DBMigrationResult()
|
||||
|
||||
+1
@@ -114,6 +114,7 @@ fun annotatedStringResource(id: StringResource, vararg args: Any?): AnnotatedStr
|
||||
expect fun SetupClipboardListener()
|
||||
|
||||
// maximum image file size to be auto-accepted
|
||||
// Spec: spec/services/files.md#MAX_IMAGE_SIZE
|
||||
const val MAX_IMAGE_SIZE: Long = 261_120 // 255KB
|
||||
const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2
|
||||
const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ private const val SERVER_HOST = "localhost"
|
||||
private const val SERVER_PORT = 50395
|
||||
val connections = ArrayList<WebSocket>()
|
||||
|
||||
// Spec: spec/services/calls.md#ActiveCallView
|
||||
@Composable
|
||||
actual fun ActiveCallView() {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Reference in New Issue
Block a user