Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2024-06-01 00:23:02 +01:00
64 changed files with 980 additions and 193 deletions
@@ -88,6 +88,7 @@ android {
"cs",
"de",
"es",
"fa",
"fi",
"fr",
"hu",
@@ -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
}
@@ -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() {
@@ -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)
@@ -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()
}
@@ -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) },
@@ -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 ->
@@ -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)
@@ -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 -> {}
}
}
@@ -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,
@@ -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)
@@ -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,
@@ -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
)
)
),
@@ -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",
@@ -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 &amp; 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()
+4 -4
View File
@@ -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