mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Merge branch 'master' into master-android
This commit is contained in:
@@ -88,6 +88,7 @@ android {
|
||||
"cs",
|
||||
"de",
|
||||
"es",
|
||||
"fa",
|
||||
"fi",
|
||||
"fr",
|
||||
"hu",
|
||||
|
||||
+72
@@ -931,6 +931,20 @@ object ChatController {
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair<RcvMsgInfo?, QueueInfo>? {
|
||||
val r = sendCmd(rh, CC.APIContactQueueInfo(contactId))
|
||||
if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo)
|
||||
apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r)
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair<RcvMsgInfo?, QueueInfo>? {
|
||||
val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId))
|
||||
if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo)
|
||||
apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r)
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiSwitchContact(rh: Long?, contactId: Long): ConnectionStats? {
|
||||
val r = sendCmd(rh, CC.APISwitchContact(contactId))
|
||||
if (r is CR.ContactSwitchStarted) return r.connectionStats
|
||||
@@ -2507,6 +2521,8 @@ sealed class CC {
|
||||
class ApiSetMemberSettings(val groupId: Long, val groupMemberId: Long, val memberSettings: GroupMemberSettings): CC()
|
||||
class APIContactInfo(val contactId: Long): CC()
|
||||
class APIGroupMemberInfo(val groupId: Long, val groupMemberId: Long): CC()
|
||||
class APIContactQueueInfo(val contactId: Long): CC()
|
||||
class APIGroupMemberQueueInfo(val groupId: Long, val groupMemberId: Long): CC()
|
||||
class APISwitchContact(val contactId: Long): CC()
|
||||
class APISwitchGroupMember(val groupId: Long, val groupMemberId: Long): CC()
|
||||
class APIAbortSwitchContact(val contactId: Long): CC()
|
||||
@@ -2652,6 +2668,8 @@ sealed class CC {
|
||||
is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}"
|
||||
is APIContactInfo -> "/_info @$contactId"
|
||||
is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId"
|
||||
is APIContactQueueInfo -> "/_queue info @$contactId"
|
||||
is APIGroupMemberQueueInfo -> "/_queue info #$groupId $groupMemberId"
|
||||
is APISwitchContact -> "/_switch @$contactId"
|
||||
is APISwitchGroupMember -> "/_switch #$groupId $groupMemberId"
|
||||
is APIAbortSwitchContact -> "/_abort switch @$contactId"
|
||||
@@ -2790,6 +2808,8 @@ sealed class CC {
|
||||
is ApiSetMemberSettings -> "apiSetMemberSettings"
|
||||
is APIContactInfo -> "apiContactInfo"
|
||||
is APIGroupMemberInfo -> "apiGroupMemberInfo"
|
||||
is APIContactQueueInfo -> "apiContactQueueInfo"
|
||||
is APIGroupMemberQueueInfo -> "apiGroupMemberQueueInfo"
|
||||
is APISwitchContact -> "apiSwitchContact"
|
||||
is APISwitchGroupMember -> "apiSwitchGroupMember"
|
||||
is APIAbortSwitchContact -> "apiAbortSwitchContact"
|
||||
@@ -4197,6 +4217,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR()
|
||||
@Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR()
|
||||
@Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats? = null): CR()
|
||||
@Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: QueueInfo): CR()
|
||||
@Serializable @SerialName("contactSwitchStarted") class ContactSwitchStarted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR()
|
||||
@Serializable @SerialName("groupMemberSwitchStarted") class GroupMemberSwitchStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR()
|
||||
@Serializable @SerialName("contactSwitchAborted") class ContactSwitchAborted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR()
|
||||
@@ -4368,6 +4389,7 @@ sealed class CR {
|
||||
is NetworkConfig -> "networkConfig"
|
||||
is ContactInfo -> "contactInfo"
|
||||
is GroupMemberInfo -> "groupMemberInfo"
|
||||
is QueueInfoR -> "queueInfo"
|
||||
is ContactSwitchStarted -> "contactSwitchStarted"
|
||||
is GroupMemberSwitchStarted -> "groupMemberSwitchStarted"
|
||||
is ContactSwitchAborted -> "contactSwitchAborted"
|
||||
@@ -4529,6 +4551,7 @@ sealed class CR {
|
||||
is NetworkConfig -> json.encodeToString(networkConfig)
|
||||
is ContactInfo -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats_)}")
|
||||
is GroupMemberInfo -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats_)}")
|
||||
is QueueInfoR -> withUser(user, "rcvMsgInfo: ${json.encodeToString(rcvMsgInfo)}\nqueueInfo: ${json.encodeToString(queueInfo)}\n")
|
||||
is ContactSwitchStarted -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}")
|
||||
is GroupMemberSwitchStarted -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats)}")
|
||||
is ContactSwitchAborted -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}")
|
||||
@@ -5786,3 +5809,52 @@ enum class UserNetworkType {
|
||||
OTHER -> generalGetString(MR.strings.network_type_other)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class RcvMsgInfo (
|
||||
val msgId: Long,
|
||||
val msgDeliveryId: Long,
|
||||
val msgDeliveryStatus: String,
|
||||
val agentMsgId: Long,
|
||||
val agentMsgMeta: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class QueueInfo (
|
||||
val qiSnd: Boolean,
|
||||
val qiNtf: Boolean,
|
||||
val qiSub: QSub? = null,
|
||||
val qiSize: Int,
|
||||
val qiMsg: MsgInfo? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class QSub (
|
||||
val qSubThread: QSubThread,
|
||||
val qDelivered: String? = null
|
||||
)
|
||||
|
||||
enum class QSubThread {
|
||||
@SerialName("noSub")
|
||||
NO_SUB,
|
||||
@SerialName("subPending")
|
||||
SUB_PENDING,
|
||||
@SerialName("subThread")
|
||||
SUB_THREAD,
|
||||
@SerialName("prohibitSub")
|
||||
PROHIBIT_SUB
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MsgInfo (
|
||||
val msgId: String,
|
||||
val msgTs: Instant,
|
||||
val msgType: MsgType,
|
||||
)
|
||||
|
||||
enum class MsgType {
|
||||
@SerialName("message")
|
||||
MESSAGE,
|
||||
@SerialName("quota")
|
||||
QUOTA
|
||||
}
|
||||
|
||||
+20
@@ -42,6 +42,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
@@ -418,6 +419,19 @@ fun ChatInfoLayout(
|
||||
SectionView(title = stringResource(MR.strings.section_title_for_console)) {
|
||||
InfoRow(stringResource(MR.strings.info_row_local_name), chat.chatInfo.localDisplayName)
|
||||
InfoRow(stringResource(MR.strings.info_row_database_id), chat.chatInfo.apiId.toString())
|
||||
SectionItemView({
|
||||
withBGApi {
|
||||
val info = controller.apiContactQueueInfo(chat.remoteHostId, chat.chatInfo.apiId)
|
||||
if (info != null) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.message_queue_info),
|
||||
text = queueInfoText(info)
|
||||
)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(MR.strings.info_row_debug_delivery))
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionBottomSpacer()
|
||||
@@ -798,6 +812,12 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) {
|
||||
)
|
||||
}
|
||||
|
||||
fun queueInfoText(info: Pair<RcvMsgInfo?, QueueInfo>): String {
|
||||
val (rcvMsgInfo, qInfo) = info
|
||||
val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none)
|
||||
return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewChatInfoLayout() {
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ import androidx.compose.runtime.saveable.mapSaver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.*
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.platform.*
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
@@ -620,7 +621,7 @@ fun ChatLayout(
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colors.background)
|
||||
.then(if (wallpaperImage != null)
|
||||
Modifier.drawBehind { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) }
|
||||
Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) }
|
||||
else
|
||||
Modifier)
|
||||
.padding(contentPadding)
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List<Contact> {
|
||||
.map { it.chatInfo }
|
||||
.filterIsInstance<ChatInfo.Direct>()
|
||||
.map { it.contact }
|
||||
.filter { c -> c.ready && c.active && c.contactId !in memberContactIds && c.chatViewName.lowercase().contains(s) }
|
||||
.filter { c -> c.sendMsgEnabled && !c.nextSendGrpInv && c.contactId !in memberContactIds && c.chatViewName.lowercase().contains(s) }
|
||||
.sortedBy { it.displayName.lowercase() }
|
||||
.toList()
|
||||
}
|
||||
|
||||
+18
@@ -3,6 +3,7 @@ package chat.simplex.common.views.chat.group
|
||||
import InfoRow
|
||||
import SectionBottomSpacer
|
||||
import SectionDividerSpaced
|
||||
import SectionItemView
|
||||
import SectionSpacer
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
@@ -27,6 +28,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -58,6 +60,7 @@ fun GroupMemberInfoView(
|
||||
if (chat != null) {
|
||||
val newRole = remember { mutableStateOf(member.memberRole) }
|
||||
GroupMemberInfoLayout(
|
||||
rhId = rhId,
|
||||
groupInfo,
|
||||
member,
|
||||
connStats,
|
||||
@@ -219,6 +222,7 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c
|
||||
|
||||
@Composable
|
||||
fun GroupMemberInfoLayout(
|
||||
rhId: Long?,
|
||||
groupInfo: GroupInfo,
|
||||
member: GroupMember,
|
||||
connStats: MutableState<ConnectionStats?>,
|
||||
@@ -397,6 +401,19 @@ fun GroupMemberInfoLayout(
|
||||
SectionView(title = stringResource(MR.strings.section_title_for_console)) {
|
||||
InfoRow(stringResource(MR.strings.info_row_local_name), member.localDisplayName)
|
||||
InfoRow(stringResource(MR.strings.info_row_database_id), member.groupMemberId.toString())
|
||||
SectionItemView({
|
||||
withBGApi {
|
||||
val info = controller.apiGroupMemberQueueInfo(rhId, groupInfo.apiId, member.groupMemberId)
|
||||
if (info != null) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.message_queue_info),
|
||||
text = queueInfoText(info)
|
||||
)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(MR.strings.info_row_debug_delivery))
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionBottomSpacer()
|
||||
@@ -644,6 +661,7 @@ fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, member: GroupMember, blocke
|
||||
fun PreviewGroupMemberInfoLayout() {
|
||||
SimpleXTheme {
|
||||
GroupMemberInfoLayout(
|
||||
rhId = null,
|
||||
groupInfo = GroupInfo.sampleData,
|
||||
member = GroupMember.sampleData,
|
||||
connStats = remember { mutableStateOf(null) },
|
||||
|
||||
+1
-7
@@ -154,13 +154,7 @@ fun CIFileView(
|
||||
FileProtocol.SMP -> progressIndicator()
|
||||
FileProtocol.LOCAL -> {}
|
||||
}
|
||||
is CIFileStatus.SndComplete -> {
|
||||
if ((file.forwardingAllowed() || (chatModel.connectedToRemote() && CIFile.cachedRemoteFileRequests[file.fileSource] == true))) {
|
||||
fileIcon()
|
||||
} else {
|
||||
fileIcon(innerIcon = painterResource(MR.images.ic_check_filled))
|
||||
}
|
||||
}
|
||||
is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(MR.images.ic_check_filled))
|
||||
is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
|
||||
is CIFileStatus.SndError -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
|
||||
is CIFileStatus.RcvInvitation ->
|
||||
|
||||
+2
@@ -537,6 +537,7 @@ suspend fun exportChatArchive(
|
||||
if (!m.chatDbChanged.value) {
|
||||
controller.apiSaveAppSettings(AppSettings.current.prepareForExport())
|
||||
}
|
||||
wallpapersDir.mkdirs()
|
||||
m.controller.apiExportArchive(config)
|
||||
if (storagePath == null) {
|
||||
deleteOldArchive(m)
|
||||
@@ -592,6 +593,7 @@ private fun importArchive(
|
||||
withLongRunningApi {
|
||||
try {
|
||||
m.controller.apiDeleteStorage()
|
||||
wallpapersDir.mkdirs()
|
||||
try {
|
||||
val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString())
|
||||
val archiveErrors = m.controller.apiImportArchive(config)
|
||||
|
||||
+75
-45
@@ -1,12 +1,13 @@
|
||||
package chat.simplex.common.views.helpers
|
||||
|
||||
import androidx.compose.ui.draw.CacheDrawScope
|
||||
import androidx.compose.ui.draw.DrawResult
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.graphics.drawscope.*
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
@@ -352,9 +353,17 @@ sealed class WallpaperType {
|
||||
}
|
||||
}
|
||||
|
||||
fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color) = clipRect {
|
||||
val quality = FilterQuality.High
|
||||
fun repeat(imageScale: Float) {
|
||||
private fun drawToBitmap(image: ImageBitmap, imageScale: Float, tint: Color, size: Size, density: Float, layoutDirection: LayoutDirection): ImageBitmap {
|
||||
val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low
|
||||
val drawScope = CanvasDrawScope()
|
||||
val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt())
|
||||
val canvas = Canvas(bitmap)
|
||||
drawScope.draw(
|
||||
density = Density(density),
|
||||
layoutDirection = layoutDirection,
|
||||
canvas = canvas,
|
||||
size = size,
|
||||
) {
|
||||
val scale = imageScale * density
|
||||
for (h in 0..(size.height / image.height / scale).roundToInt()) {
|
||||
for (w in 0..(size.width / image.width / scale).roundToInt()) {
|
||||
@@ -368,50 +377,71 @@ fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, b
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
drawRect(background)
|
||||
when (imageType) {
|
||||
is WallpaperType.Preset -> repeat((imageType.scale ?: 1f) * imageType.predefinedImageScale)
|
||||
is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) {
|
||||
WallpaperScaleType.REPEAT -> repeat(imageType.scale ?: 1f)
|
||||
WallpaperScaleType.FILL, WallpaperScaleType.FIT -> {
|
||||
val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height))
|
||||
val scaledWidth = (image.width * scale.scaleX).roundToInt()
|
||||
val scaledHeight = (image.height * scale.scaleY).roundToInt()
|
||||
// Large image will cause freeze
|
||||
if (image.width > 4320 || image.height > 4320) return@clipRect
|
||||
fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color): DrawResult {
|
||||
val imageScale = if (imageType is WallpaperType.Preset) {
|
||||
(imageType.scale ?: 1f) * imageType.predefinedImageScale
|
||||
} else if (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT) {
|
||||
imageType.scale ?: 1f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
val image = if (imageType is WallpaperType.Preset || (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT)) {
|
||||
drawToBitmap(image, imageScale, tint, size, density, layoutDirection)
|
||||
} else {
|
||||
image
|
||||
}
|
||||
|
||||
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
if (scaleType == WallpaperScaleType.FIT) {
|
||||
if (scaledWidth < size.width) {
|
||||
// has black lines at left and right sides
|
||||
var x = (size.width - scaledWidth) / 2
|
||||
while (x > 0) {
|
||||
drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
x -= scaledWidth
|
||||
}
|
||||
x = size.width - (size.width - scaledWidth) / 2
|
||||
while (x < size.width) {
|
||||
drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
x += scaledWidth
|
||||
}
|
||||
} else {
|
||||
// has black lines at top and bottom sides
|
||||
var y = (size.height - scaledHeight) / 2
|
||||
while (y > 0) {
|
||||
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
y -= scaledHeight
|
||||
}
|
||||
y = size.height - (size.height - scaledHeight) / 2
|
||||
while (y < size.height) {
|
||||
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
y += scaledHeight
|
||||
return onDrawBehind {
|
||||
val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low
|
||||
drawRect(background)
|
||||
when (imageType) {
|
||||
is WallpaperType.Preset -> drawImage(image)
|
||||
is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) {
|
||||
WallpaperScaleType.REPEAT -> drawImage(image)
|
||||
WallpaperScaleType.FILL, WallpaperScaleType.FIT -> {
|
||||
clipRect {
|
||||
val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height))
|
||||
val scaledWidth = (image.width * scale.scaleX).roundToInt()
|
||||
val scaledHeight = (image.height * scale.scaleY).roundToInt()
|
||||
// Large image will cause freeze
|
||||
if (image.width > 4320 || image.height > 4320) return@clipRect
|
||||
|
||||
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
if (scaleType == WallpaperScaleType.FIT) {
|
||||
if (scaledWidth < size.width) {
|
||||
// has black lines at left and right sides
|
||||
var x = (size.width - scaledWidth) / 2
|
||||
while (x > 0) {
|
||||
drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
x -= scaledWidth
|
||||
}
|
||||
x = size.width - (size.width - scaledWidth) / 2
|
||||
while (x < size.width) {
|
||||
drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
x += scaledWidth
|
||||
}
|
||||
} else {
|
||||
// has black lines at top and bottom sides
|
||||
var y = (size.height - scaledHeight) / 2
|
||||
while (y > 0) {
|
||||
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
y -= scaledHeight
|
||||
}
|
||||
y = size.height - (size.height - scaledHeight) / 2
|
||||
while (y < size.height) {
|
||||
drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality)
|
||||
y += scaledHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drawRect(tint)
|
||||
}
|
||||
drawRect(tint)
|
||||
}
|
||||
is WallpaperType.Empty -> {}
|
||||
}
|
||||
is WallpaperType.Empty -> {}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-1
@@ -1,6 +1,5 @@
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -13,12 +12,15 @@ import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.NotificationsMode
|
||||
import chat.simplex.common.platform.onRightClick
|
||||
import chat.simplex.common.platform.windowWidth
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.onboarding.SelectableCard
|
||||
import chat.simplex.common.views.usersettings.SettingsActionItemWithContent
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@Composable
|
||||
fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) {
|
||||
@@ -76,6 +78,26 @@ fun <T> SectionViewSelectable(
|
||||
SectionTextFooter(values.first { it.value == currentValue.value }.description)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> SectionViewSelectableCards(
|
||||
title: String?,
|
||||
currentValue: State<T>,
|
||||
values: List<ValueTitleDesc<T>>,
|
||||
onSelected: (T) -> Unit,
|
||||
) {
|
||||
SectionView(title) {
|
||||
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||
if (title != null) {
|
||||
Text(title, Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING * 2f))
|
||||
}
|
||||
values.forEach { item ->
|
||||
SelectableCard(currentValue, item.value, item.title, item.description, onSelected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SectionItemView(
|
||||
click: (() -> Unit)? = null,
|
||||
|
||||
+1
@@ -594,6 +594,7 @@ private fun MutableState<MigrationToState?>.importArchive(archivePath: String, n
|
||||
chatInitControllerRemovingDatabases()
|
||||
}
|
||||
controller.apiDeleteStorage()
|
||||
wallpapersDir.mkdirs()
|
||||
try {
|
||||
val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString())
|
||||
val archiveErrors = controller.apiImportArchive(config)
|
||||
|
||||
+17
-10
@@ -8,6 +8,7 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -35,9 +36,15 @@ fun SetNotificationsMode(m: ChatModel) {
|
||||
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
|
||||
Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING * 2f))
|
||||
NotificationButton(currentMode, NotificationsMode.OFF, MR.strings.onboarding_notifications_mode_off, MR.strings.onboarding_notifications_mode_off_desc)
|
||||
NotificationButton(currentMode, NotificationsMode.PERIODIC, MR.strings.onboarding_notifications_mode_periodic, MR.strings.onboarding_notifications_mode_periodic_desc)
|
||||
NotificationButton(currentMode, NotificationsMode.SERVICE, MR.strings.onboarding_notifications_mode_service, MR.strings.onboarding_notifications_mode_service_desc)
|
||||
SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) {
|
||||
currentMode.value = NotificationsMode.OFF
|
||||
}
|
||||
SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)){
|
||||
currentMode.value = NotificationsMode.PERIODIC
|
||||
}
|
||||
SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)){
|
||||
currentMode.value = NotificationsMode.SERVICE
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), contentAlignment = Alignment.Center) {
|
||||
@@ -54,22 +61,22 @@ fun SetNotificationsMode(m: ChatModel) {
|
||||
expect fun SetNotificationsModeAdditions()
|
||||
|
||||
@Composable
|
||||
private fun NotificationButton(currentMode: MutableState<NotificationsMode>, mode: NotificationsMode, title: StringResource, description: StringResource) {
|
||||
fun <T> SelectableCard(currentValue: State<T>, newValue: T, title: String, description: AnnotatedString, onSelected: (T) -> Unit) {
|
||||
TextButton(
|
||||
onClick = { currentMode.value = mode },
|
||||
border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)),
|
||||
onClick = { onSelected(newValue) },
|
||||
border = BorderStroke(1.dp, color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)),
|
||||
shape = RoundedCornerShape(35.dp),
|
||||
) {
|
||||
Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp)) {
|
||||
Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp).fillMaxWidth()) {
|
||||
Text(
|
||||
stringResource(title),
|
||||
title,
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
||||
color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
||||
modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Text(annotatedStringResource(description),
|
||||
Text(description,
|
||||
Modifier.align(Alignment.CenterHorizontally),
|
||||
fontSize = 15.sp,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
|
||||
+31
-7
@@ -271,7 +271,6 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v4_5_italian_interface,
|
||||
descrId = MR.strings.v4_5_italian_interface_descr,
|
||||
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat"
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -308,7 +307,6 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v4_6_chinese_spanish_interface,
|
||||
descrId = MR.strings.v4_6_chinese_spanish_interface_descr,
|
||||
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat"
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -330,7 +328,6 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v5_0_polish_interface,
|
||||
descrId = MR.strings.v5_0_polish_interface_descr,
|
||||
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat"
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -362,7 +359,6 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v5_1_japanese_portuguese_interface,
|
||||
descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate,
|
||||
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat"
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -427,7 +423,6 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v5_3_new_interface_languages,
|
||||
descrId = MR.strings.v5_3_new_interface_languages_descr,
|
||||
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat"
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -491,7 +486,6 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v5_5_new_interface_languages,
|
||||
descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate,
|
||||
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat"
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -554,7 +548,37 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v5_7_new_interface_languages,
|
||||
descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate,
|
||||
link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat"
|
||||
)
|
||||
)
|
||||
),
|
||||
VersionDescription(
|
||||
version = "v5.8",
|
||||
post = "https://simplex.chat/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html",
|
||||
features = listOf(
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_settings_ethernet,
|
||||
titleId = MR.strings.v5_8_private_routing,
|
||||
descrId = MR.strings.v5_8_private_routing_descr
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_palette,
|
||||
titleId = MR.strings.v5_8_chat_themes,
|
||||
descrId = MR.strings.v5_8_chat_themes_descr
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_security,
|
||||
titleId = MR.strings.v5_8_safe_files,
|
||||
descrId = MR.strings.v5_8_safe_files_descr
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_battery_3_bar,
|
||||
titleId = MR.strings.v5_8_message_delivery,
|
||||
descrId = MR.strings.v5_8_message_delivery_descr
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_translate,
|
||||
titleId = MR.strings.v5_8_persian_ui,
|
||||
descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
+5
-2
@@ -95,11 +95,13 @@ object AppearanceScope {
|
||||
val backgroundColor = backgroundColor ?: wallpaperType?.defaultBackgroundColor(theme, MaterialTheme.colors.background)
|
||||
val tintColor = tintColor ?: wallpaperType?.defaultTintColor(theme)
|
||||
Column(Modifier
|
||||
.drawBehind {
|
||||
.drawWithCache {
|
||||
if (wallpaperImage != null && wallpaperType != null && backgroundColor != null && tintColor != null) {
|
||||
chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor)
|
||||
} else {
|
||||
drawRect(themeBackgroundColor)
|
||||
onDrawBehind {
|
||||
drawRect(themeBackgroundColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(DEFAULT_PADDING_HALF)
|
||||
@@ -887,6 +889,7 @@ object AppearanceScope {
|
||||
"cs" to "Čeština",
|
||||
"de" to "Deutsch",
|
||||
"es" to "Español",
|
||||
"fa" to "فارسی",
|
||||
"fi" to "Suomi",
|
||||
"fr" to "Français",
|
||||
"hu" to "Magyar",
|
||||
|
||||
+3
-2
@@ -7,6 +7,7 @@ import SectionItemView
|
||||
import SectionItemWithValue
|
||||
import SectionView
|
||||
import SectionViewSelectable
|
||||
import SectionViewSelectableCards
|
||||
import TextIconSpaced
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
@@ -555,7 +556,7 @@ private fun SMPProxyModePicker(
|
||||
Modifier.fillMaxWidth(),
|
||||
) {
|
||||
AppBarTitle(stringResource(MR.strings.network_smp_proxy_mode_private_routing))
|
||||
SectionViewSelectable(null, smpProxyMode, values, updateSMPProxyMode)
|
||||
SectionViewSelectableCards(null, smpProxyMode, values, updateSMPProxyMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,7 +593,7 @@ private fun SMPProxyFallbackPicker(
|
||||
Modifier.fillMaxWidth(),
|
||||
) {
|
||||
AppBarTitle(stringResource(MR.strings.network_smp_proxy_fallback_allow_downgrade))
|
||||
SectionViewSelectable(null, smpProxyFallback, values, updateSMPProxyFallback)
|
||||
SectionViewSelectableCards(null, smpProxyFallback, values, updateSMPProxyFallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1401,6 +1401,7 @@
|
||||
<string name="section_title_for_console">FOR CONSOLE</string>
|
||||
<string name="info_row_local_name">Local name</string>
|
||||
<string name="info_row_database_id">Database ID</string>
|
||||
<string name="info_row_debug_delivery">Debug delivery</string>
|
||||
<string name="info_row_updated_at">Record updated at</string>
|
||||
<string name="info_row_sent_at">Sent at</string>
|
||||
<string name="info_row_created_at">Created at</string>
|
||||
@@ -1462,6 +1463,9 @@
|
||||
<string name="info_row_connection">Connection</string>
|
||||
<string name="conn_level_desc_direct">direct</string>
|
||||
<string name="conn_level_desc_indirect">indirect (%1$s)</string>
|
||||
<string name="message_queue_info">Message queue info</string>
|
||||
<string name="message_queue_info_none">none</string>
|
||||
<string name="message_queue_info_server_info">server queue info: %1$s\n\nlast received msg: %2$s</string>
|
||||
|
||||
<!-- GroupWelcomeView.kt -->
|
||||
<string name="group_welcome_title">Welcome message</string>
|
||||
@@ -1850,6 +1854,15 @@
|
||||
<string name="v5_7_network">Network management</string>
|
||||
<string name="v5_7_network_descr">More reliable network connection.</string>
|
||||
<string name="v5_7_new_interface_languages">Lithuanian UI</string>
|
||||
<string name="v5_8_private_routing">Private message routing 🚀</string>
|
||||
<string name="v5_8_private_routing_descr">Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings.</string>
|
||||
<string name="v5_8_chat_themes">New chat themes</string>
|
||||
<string name="v5_8_chat_themes_descr">Make your chats look different!</string>
|
||||
<string name="v5_8_safe_files">Safely receive files</string>
|
||||
<string name="v5_8_safe_files_descr">Confirm files from unknown servers.</string>
|
||||
<string name="v5_8_message_delivery">Improved message delivery</string>
|
||||
<string name="v5_8_message_delivery_descr">With reduced battery usage.</string>
|
||||
<string name="v5_8_persian_ui">Persian UI</string>
|
||||
|
||||
<!-- CustomTimePicker -->
|
||||
<string name="custom_time_unit_seconds">seconds</string>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#5f6368">
|
||||
<path
|
||||
d="M480-85q-81 0-153.05-31.16-72.05-31.16-125.84-84.95-53.79-53.79-84.95-125.84Q85-399 85-480.01 85-564 116.75-636q31.75-72 86.5-125.25t128.25-83.5Q405-875 488.8-875q78.41 0 148.31 26.25Q707-822.5 759.99-776.37q52.99 46.13 84 110Q875-602.5 875-527q0 106.5-61.75 168t-164.66 61.5H574.5q-19 0-32.5 14.25T528.5-251q0 27.45 14.5 47.22 14.5 19.78 14.5 45.28 0 34-19.75 53.75T480-85Zm0-395Zm-231.5 24.5q19.7 0 34.1-14.4Q297-484.3 297-504q0-19.7-14.4-34.1-14.4-14.4-34.1-14.4-19.7 0-34.1 14.4Q200-523.7 200-504q0 19.7 14.4 34.1 14.4 14.4 34.1 14.4Zm125.5-169q19.7 0 34.1-14.4 14.4-14.4 14.4-34.1 0-19.7-14.4-34.1-14.4-14.4-34.1-14.4-19.7 0-34.1 14.4-14.4 14.4-14.4 34.1 0 19.7 14.4 34.1 14.4 14.4 34.1 14.4Zm212.5 0q19.7 0 34.1-14.4Q635-653.3 635-673q0-19.7-14.4-34.1-14.4-14.4-34.1-14.4-19.7 0-34.1 14.4Q538-692.7 538-673q0 19.7 14.4 34.1 14.4 14.4 34.1 14.4Zm130 169q19.7 0 34.1-14.4Q765-484.3 765-504q0-19.7-14.4-34.1-14.4-14.4-34.1-14.4-19.7 0-34.1 14.4Q668-523.7 668-504q0 19.7 14.4 34.1 14.4 14.4 34.1 14.4Zm-236.34 313q10.84 0 15.34-4.5t4.5-14.5q0-14-14.25-26.25T471.5-240.5q0-45.5 29.75-80T576.5-355h72q76 0 122.5-44.25T817.5-527q0-131-99.25-210.75T488.91-817.5q-144.91 0-245.66 98.05Q142.5-621.41 142.5-480q0 140 98.75 238.75t238.91 98.75Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -115,6 +115,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState<Boolean>) {
|
||||
false
|
||||
}
|
||||
}, title = "SimpleX") {
|
||||
// val hardwareAccelerationDisabled = remember { listOf(GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.UNKNOWN).contains(window.renderApi) }
|
||||
simplexWindowState.window = window
|
||||
AppScreen()
|
||||
if (simplexWindowState.openDialog.isAwaiting) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package chat.simplex.desktop
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.AnimationVector1D
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
@@ -17,6 +16,8 @@ import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
|
||||
fun main() {
|
||||
// Disable hardware acceleration
|
||||
//System.setProperty("skiko.renderApi", "SOFTWARE")
|
||||
initHaskell()
|
||||
runMigrations()
|
||||
initApp()
|
||||
|
||||
@@ -26,11 +26,11 @@ android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
kotlin.jvm.target=11
|
||||
|
||||
android.version_name=5.8-beta.3
|
||||
android.version_code=214
|
||||
android.version_name=5.8-beta.4
|
||||
android.version_code=215
|
||||
|
||||
desktop.version_name=5.8-beta.3
|
||||
desktop.version_code=49
|
||||
desktop.version_name=5.8-beta.4
|
||||
desktop.version_code=50
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
|
||||
Reference in New Issue
Block a user