Implement desktop support

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2026-07-26 15:52:42 +03:00
Unverified
parent 24fae67efc
commit 2b46c88bf3
133 changed files with 5446 additions and 485 deletions
+9
View File
@@ -16,6 +16,8 @@ kotlin {
minSdk = 24
}
jvm()
listOf(
iosArm64(),
iosSimulatorArm64()
@@ -62,6 +64,13 @@ kotlin {
implementation(libs.multiplatform.settings.coroutines)
implementation(libs.multiplatform.settings.serialization)
}
jvmMain.dependencies {
implementation(libs.kotlinx.datetime)
implementation(libs.multiplatform.settings)
implementation(libs.multiplatform.settings.coroutines)
implementation(libs.multiplatform.settings.serialization)
}
}
}
@@ -21,8 +21,8 @@ data class NavigationItem(
@Composable
fun SimpleNavigationBar(
selectedItem: Int,
vararg items: NavigationItem,
modifier: Modifier = Modifier,
vararg items: NavigationItem,
) {
NavigationBar(modifier) {
items.forEachIndexed { index, item ->
@@ -48,8 +48,8 @@ fun SimpleNavigationBar(
@Composable
fun SimpleNavigationRail(
selectedItem: Int,
vararg items: NavigationItem,
modifier: Modifier = Modifier,
vararg items: NavigationItem,
) {
NavigationRail(
modifier,
@@ -73,4 +73,4 @@ fun SimpleNavigationRail(
)
}
}
}
}
@@ -8,7 +8,7 @@ import platform.CoreCrypto.CCHmac
import platform.CoreCrypto.kCCHmacAlgSHA256
@OptIn(ExperimentalForeignApi::class)
internal fun iosHmacSha256(key: ByteArray, data: ByteArray): ByteArray {
fun iosHmacSha256(key: ByteArray, data: ByteArray): ByteArray {
val mac = ByteArray(32)
key.usePinned { keyPinned ->
@@ -0,0 +1,9 @@
package com.pr0gramm3r101.utils
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.LocalWindowInfo
@OptIn(ExperimentalComposeUiApi::class)
@Composable
actual inline fun currentWindowSize() = LocalWindowInfo.current.containerSize
@@ -0,0 +1,27 @@
package com.pr0gramm3r101.utils
import androidx.compose.ui.platform.Clipboard
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection
actual fun Clipboard.toSupport(): SupportClipboardManager {
return object : SupportClipboardManager {
private var listener: ((String) -> Unit)? = null
override suspend fun setText(string: String) {
Toolkit.getDefaultToolkit().systemClipboard.setContents(StringSelection(string), null)
listener?.invoke(string)
}
override suspend fun getText(): String? {
val contents = Toolkit.getDefaultToolkit().systemClipboard.getContents(null) ?: return null
if (!contents.isDataFlavorSupported(DataFlavor.stringFlavor)) return null
return contents.getTransferData(DataFlavor.stringFlavor) as? String
}
override fun setTextListener(listener: (String) -> Unit) {
this.listener = listener
}
}
}
@@ -0,0 +1,5 @@
package com.pr0gramm3r101.utils
import androidx.compose.ui.Modifier
actual fun Modifier.imeScrollWithKeyboard(): Modifier = this
@@ -0,0 +1,11 @@
package com.pr0gramm3r101.utils
import androidx.compose.runtime.Composable
@Composable
actual fun rememberImeMotion(): ImeMotion =
ImeMotion(
currentBottomPx = 0,
sourceBottomPx = 0,
targetBottomPx = 0,
)
@@ -0,0 +1,11 @@
package com.pr0gramm3r101.utils
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@Composable
actual fun rememberSystemBarsController(): ((Boolean) -> Unit)? {
return remember {
{ _ -> }
}
}
@@ -0,0 +1,24 @@
package com.pr0gramm3r101.utils
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
actual fun Modifier.clearFocusOnKeyboardDismiss() = this
@Composable
actual fun ToggleNavScrimEffect(enabled: Boolean) {}
@Composable
actual fun DialogEdgeToEdgeEffect() {}
actual val materialYouAvailable get() = false
actual fun currentDeviceInfo(): CurrentDeviceInfo =
CurrentDeviceInfo(
osName = System.getProperty("os.name")?.trim()?.takeIf { it.isNotEmpty() },
osVersion = System.getProperty("os.version")?.trim()?.takeIf { it.isNotEmpty() },
deviceType = "desktop",
deviceName = System.getProperty("user.name")?.trim()?.takeIf { it.isNotEmpty() },
brand = null,
model = System.getProperty("os.arch")?.trim()?.takeIf { it.isNotEmpty() },
)
@@ -0,0 +1,5 @@
package com.pr0gramm3r101.utils
actual object UtilsLibrary {
actual fun init(vararg args: Any) {}
}
@@ -0,0 +1,9 @@
package com.pr0gramm3r101.utils.crypto
actual object Base64 {
actual fun encode(bytes: ByteArray): String =
java.util.Base64.getEncoder().encodeToString(bytes)
actual fun decode(base64: String): ByteArray =
runCatching { java.util.Base64.getDecoder().decode(base64) }.getOrDefault(ByteArray(0))
}
@@ -0,0 +1,15 @@
package com.pr0gramm3r101.utils.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
actual object Hmac {
actual suspend fun hmacSha256(key: ByteArray, data: ByteArray): ByteArray =
withContext(Dispatchers.Default) {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(key, "HmacSHA256"))
mac.doFinal(data)
}
}
@@ -0,0 +1,63 @@
package com.pr0gramm3r101.utils.files
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardOpenOption
internal actual fun expectExists(path: String): Boolean = File(path).exists()
internal actual fun expectWriteBytes(path: String, bytes: ByteArray) {
val file = File(path)
file.parentFile?.mkdirs()
file.writeBytes(bytes)
}
internal actual fun expectAppendBytes(path: String, bytes: ByteArray) {
val file = File(path)
file.parentFile?.mkdirs()
Files.write(
file.toPath(),
bytes,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND,
StandardOpenOption.WRITE,
)
}
internal actual fun expectFileSize(path: String): Long {
val file = File(path)
return if (file.exists()) file.length() else 0L
}
internal actual fun expectDelete(path: String) {
File(path).delete()
}
internal actual fun expectDeleteFilesWithPrefix(dirPath: String, namePrefix: String) {
val dir = File(dirPath)
if (!dir.isDirectory) return
dir.listFiles()?.forEach { file ->
if (file.name.startsWith(namePrefix)) {
file.delete()
}
}
}
internal actual fun expectListFileNamesInDirectory(dirPath: String): List<String> {
val dir = File(dirPath)
if (!dir.isDirectory) return emptyList()
return dir.list()?.toList().orEmpty()
}
internal actual fun expectGetAppCacheDirectory(): String {
val home = System.getProperty("user.home")
return if (!home.isNullOrBlank()) {
File(home, ".fromchat/cache").absolutePath
} else {
File(System.getProperty("java.io.tmpdir"), "fromchat").absolutePath
}
}
internal actual fun expectEnsureDirectory(path: String) {
File(path).mkdirs()
}
@@ -0,0 +1,48 @@
package com.pr0gramm3r101.utils.settings
import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.PreferencesSettings
import com.russhwolf.settings.coroutines.toSuspendSettings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.prefs.Preferences
@OptIn(ExperimentalSettingsApi::class)
class JvmSettings(
nodeName: String,
) : Settings {
private val suspendSettings =
PreferencesSettings(Preferences.userRoot().node(nodeName)).toSuspendSettings()
override suspend fun putString(key: String, value: String) = suspendSettings.putString(key, value)
override suspend fun getString(key: String, default: String) = suspendSettings.getString(key, default)
override suspend fun putInt(key: String, value: Int) = suspendSettings.putInt(key, value)
override suspend fun getInt(key: String, default: Int) = suspendSettings.getInt(key, default)
override suspend fun putLong(key: String, value: Long) = suspendSettings.putLong(key, value)
override suspend fun getLong(key: String, default: Long) = suspendSettings.getLong(key, default)
override suspend fun putFloat(key: String, value: Float) = suspendSettings.putFloat(key, value)
override suspend fun getFloat(key: String, default: Float) = suspendSettings.getFloat(key, default)
override suspend fun putBoolean(key: String, value: Boolean) = suspendSettings.putBoolean(key, value)
override suspend fun getBoolean(key: String, default: Boolean) = suspendSettings.getBoolean(key, default)
override suspend fun putStringSet(key: String, value: Set<String>) = withContext(Dispatchers.IO) {
putStringList(key, value.toList())
}
override suspend fun getStringSet(key: String, default: Set<String>) = withContext(Dispatchers.IO) {
getStringList(key, default.toList()).toSet()
}
override suspend fun remove(key: String) = suspendSettings.remove(key)
override suspend fun contains(key: String) = suspendSettings.hasKey(key)
}
@@ -0,0 +1,4 @@
package com.pr0gramm3r101.utils.settings
actual val settings: Settings get() = JvmSettings(nodeName = "ru.fromchat.settings")
actual val secureSettings: Settings get() = JvmSettings(nodeName = "ru.fromchat.secure")