Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2026-08-20 10:58:21 +01:00
211 changed files with 8535 additions and 1101 deletions
@@ -233,7 +233,8 @@ actual fun getFileName(uri: URI): String? {
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
// Can make an exception
cursor.getString(nameIndex)
// the provider controls this value, and callers use it as a bare file name
cursor.getString(nameIndex)?.let { File(it).name }
}
} catch (e: Exception) {
null
@@ -1293,6 +1293,7 @@ data class User(
val sendRcptsContacts: Boolean,
val sendRcptsSmallGroups: Boolean,
val autoAcceptMemberContacts: Boolean,
val autoAcceptGroupInvitations: Boolean,
val viewPwdHash: UserPwdHash?,
val uiThemes: ThemeModeOverrides? = null,
val userChatRelay: Boolean,
@@ -1325,6 +1326,7 @@ data class User(
sendRcptsContacts = true,
sendRcptsSmallGroups = false,
autoAcceptMemberContacts = false,
autoAcceptGroupInvitations = false,
viewPwdHash = null,
uiThemes = null,
userChatRelay = false,
@@ -941,6 +941,12 @@ object ChatController {
throw Exception("failed to set auto-accept ${r.responseType} ${r.details}")
}
suspend fun apiSetUserAutoAcceptGroupInvitations(u: User, enable: Boolean) {
val r = sendCmd(u.remoteHostId, CC.ApiSetUserAutoAcceptGroupInvitations(u.userId, enable))
if (r.result is CR.CmdOk) return
throw Exception("failed to set auto-accept group invitations ${r.responseType} ${r.details}")
}
suspend fun apiHideUser(u: User, viewPwd: String): User =
setUserPrivacy(u.remoteHostId, CC.ApiHideUser(u.userId, viewPwd))
@@ -3775,6 +3781,7 @@ sealed class CC {
class ApiSetUserContactReceipts(val userId: Long, val userMsgReceiptSettings: UserMsgReceiptSettings): CC()
class ApiSetUserGroupReceipts(val userId: Long, val userMsgReceiptSettings: UserMsgReceiptSettings): CC()
class ApiSetUserAutoAcceptMemberContacts(val userId: Long, val enable: Boolean): CC()
class ApiSetUserAutoAcceptGroupInvitations(val userId: Long, val enable: Boolean): CC()
class ApiHideUser(val userId: Long, val viewPwd: String): CC()
class ApiUnhideUser(val userId: Long, val viewPwd: String): CC()
class ApiMuteUser(val userId: Long): CC()
@@ -3965,6 +3972,7 @@ sealed class CC {
"/_set receipts groups $userId ${onOff(mrs.enable)} clear_overrides=${onOff(mrs.clearOverrides)}"
}
is ApiSetUserAutoAcceptMemberContacts -> "/_set accept member contacts $userId ${onOff(enable)}"
is ApiSetUserAutoAcceptGroupInvitations -> "/_set accept group invitations $userId ${onOff(enable)}"
is ApiHideUser -> "/_hide user $userId ${json.encodeToString(viewPwd)}"
is ApiUnhideUser -> "/_unhide user $userId ${json.encodeToString(viewPwd)}"
is ApiMuteUser -> "/_mute user $userId"
@@ -4176,6 +4184,7 @@ sealed class CC {
is ApiSetUserContactReceipts -> "apiSetUserContactReceipts"
is ApiSetUserGroupReceipts -> "apiSetUserGroupReceipts"
is ApiSetUserAutoAcceptMemberContacts -> "apiSetUserAutoAcceptMemberContacts"
is ApiSetUserAutoAcceptGroupInvitations -> "apiSetUserAutoAcceptGroupInvitations"
is ApiHideUser -> "apiHideUser"
is ApiUnhideUser -> "apiUnhideUser"
is ApiMuteUser -> "apiMuteUser"
@@ -502,14 +502,17 @@ fun ChatView(
groupMembersJob = scope.launch(Dispatchers.Default) {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
val stats = r?.second
val (_, code) = if (member.memberActive) {
val (updatedMember, code) = if (member.memberActive) {
val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId)
member to memCode?.second
(memCode?.first ?: r?.first ?: member) to memCode?.second
} else {
member to null
(r?.first ?: member) to null
}
if (!isActive || chatModel.chatId.value != groupInfo.id) return@launch
// members are not loaded in large groups, so only the opened member is added to the model
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(chatRh, groupInfo, updatedMember)
}
setGroupMembers(chatRh, groupInfo, chatModel)
if (!isActive) return@launch
if (chatsCtx.secondaryContextFilter == null) {
ModalManager.end.closeModals()
@@ -753,6 +753,11 @@ private fun saveArchiveFromURI(importedArchiveURI: URI): String? {
if (inputStream != null && archiveName != null) {
val archivePath = "$databaseExportDir${File.separator}$archiveName"
val destFile = File(archivePath)
// resolves symlinks, so it also catches a final component linking outside the folder
if (destFile.canonicalFile.parentFile != databaseExportDir.canonicalFile) {
Log.e(TAG, "saveArchiveFromURI path outside of export folder")
return null
}
Files.copy(inputStream, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
archivePath
} else {
@@ -31,7 +31,8 @@ fun AppBarTitle(
val connection = if (enableAlphaChanges) handler?.connection else null
LaunchedEffect(title) {
if (enableAlphaChanges) {
handler?.title?.value = title
// the app bar shows a single line, so the line breaks of the large title are replaced with spaces
handler?.title?.value = title.replace("\n", " ")
} else {
handler?.connection?.scrollTrackingEnabled = false
}
@@ -1,6 +1,10 @@
package chat.simplex.common.views.onboarding
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
@@ -8,15 +12,38 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalUriHandler
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withLink
import androidx.compose.ui.text.withStyle
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.BuildConfigCommon
@@ -35,6 +62,7 @@ import chat.simplex.common.views.usersettings.showAddShortLinkAlert
import chat.simplex.res.MR
import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource
import kotlin.math.absoluteValue
@Composable
fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Boolean = false, close: () -> Unit) {
@@ -914,14 +942,15 @@ private val versionDescriptions: List<VersionDescription> = listOf(
)
),
VersionDescription(
version = "v7.0",
// the trailing space differs from the previously released "v7.0", so that What's new is shown again
version = if (isInUs()) "v7.0.1" else "v7.0",
post = null,
features = listOf(
// VersionFeature.FeatureView(
// icon = null,
// titleId = MR.strings.v7_0_invest,
// view = { _ -> InvestInSimpleXChatView() }
// ),
VersionFeature.FeatureView(
icon = null,
titleId = MR.strings.v7_0_invest,
view = { modalManager -> InvestInSimpleXChatView(modalManager) }
),
VersionFeature.FeatureDescription(
icon = MR.images.ic_alternate_email,
titleId = MR.strings.v7_0_simplex_names,
@@ -956,57 +985,294 @@ fun shouldShowWhatsNew(m: ChatModel): Boolean {
return v != lastVersion
}
// private const val WEFUNDER_URL = "https://wefunder.com/simplexchat"
//
// @Composable
// private fun InvestInSimpleXChatView() {
// if (platform.androidIsPlayStoreBuild) {
// LaunchedEffect(Unit) { if (androidPlayStoreCountry.value == null) platform.androidLoadPlayStoreCountry() }
// if (androidPlayStoreCountry.value != "US") return
// }
// val uriHandler = LocalUriHandler.current
// Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(bottom = 12.dp)) {
// Column(modifier = Modifier.weight(1f)) {
// Row(
// verticalAlignment = Alignment.CenterVertically,
// horizontalArrangement = Arrangement.spacedBy(8.dp),
// modifier = Modifier.padding(bottom = 4.dp)
// ) {
// Icon(painterResource(MR.images.ic_redeem), stringResource(MR.strings.v7_0_invest), tint = MaterialTheme.colors.secondary)
// Text(
// generalGetString(MR.strings.v7_0_invest),
// maxLines = 2,
// overflow = TextOverflow.Ellipsis,
// style = MaterialTheme.typography.h4,
// fontWeight = FontWeight.Medium,
// modifier = Modifier.padding(bottom = 6.dp)
// )
// }
// Text(generalGetString(MR.strings.v7_0_invest_descr), fontSize = 15.sp, modifier = Modifier.padding(bottom = 4.dp))
// Row(
// verticalAlignment = Alignment.CenterVertically,
// horizontalArrangement = Arrangement.spacedBy(8.dp),
// modifier = Modifier
// .clickable(
// interactionSource = remember { MutableInteractionSource() },
// indication = null
// ) {
// uriHandler.openExternalLink(WEFUNDER_URL)
// }
// ) {
// Text(stringResource(MR.strings.v7_0_invest_learn_more), color = MaterialTheme.colors.primary, fontSize = 15.sp)
// Icon(painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.v7_0_invest_learn_more), tint = MaterialTheme.colors.primary)
// }
// }
// if (BuildConfigCommon.SIMPLEX_ASSETS) {
// Image(
// painterResource(if (isInDarkTheme()) MR.images.own_stake_light else MR.images.own_stake),
// contentDescription = null,
// modifier = Modifier.width(80.dp)
// )
// }
// }
// }
private const val WEFUNDER_URL = "https://wefunder.com/simplex.chat"
private const val CROWDFUNDING_CONTACT_URI = "simplex:/a#JxGcOA1_QhlmVFzYYabloMbvMZk5Y9d9iS3ITDnhzYo?h=smp11.simplex.im"
// the center modal takes the remaining width of the window, so the image is limited to its design width
private val MAX_CROWDFUNDING_IMAGE_WIDTH = DEFAULT_MIN_CENTER_MODAL_WIDTH
// the width of the page images shipped with the desktop app, so that they are never upscaled
private val CROWDFUNDING_PAGE_IMAGE_WIDTH = DEFAULT_MIN_CENTER_MODAL_WIDTH
// the corner radius the images are designed with, and the same radius as a share of their design width
private val CROWDFUNDING_IMAGE_CORNER_RADIUS = 12.dp
private const val CROWDFUNDING_IMAGE_CORNER_RADIUS_RATIO = 0.03f
private class CrowdfundingLayout(
val maxImageWidth: Dp,
val imageShape: Shape,
// the modal manager that shows the page in the center of the window, or null when nothing does
private val centerOfWindow: ModalManager?
) {
fun inCenterOfWindow(modalManager: ModalManager) = modalManager === centerOfWindow
}
// the images are designed for the width of a phone screen, which Android always gives them. On desktop
// they are limited to their own width, and their radius is scaled with them, as they are still shown
// wider than designed: a fixed radius would not only look almost square, but would also leave the corners
// baked into the jpegs visible - they have black behind them, as jpegs have no transparency
private val crowdfundingLayout = if (appPlatform.isDesktop)
CrowdfundingLayout(CROWDFUNDING_PAGE_IMAGE_WIDTH, object : Shape {
override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline =
Outline.Rounded(RoundRect(size.toRect(), CornerRadius(size.width * CROWDFUNDING_IMAGE_CORNER_RADIUS_RATIO)))
}, ModalManager.center)
else
CrowdfundingLayout(Dp.Unspecified, RoundedCornerShape(CROWDFUNDING_IMAGE_CORNER_RADIUS), null)
// Google Play policy restricts promoting investments, so Play builds only show it in the US
@Composable
fun crowdfundingAvailable(): Boolean {
if (!platform.androidIsPlayStoreBuild) return true
if (androidPlayStoreCountry.value == null) {
LaunchedEffect(Unit) {
if (androidPlayStoreCountry.value == null) platform.androidLoadPlayStoreCountry()
}
}
return isInUs()
}
fun isInUs(): Boolean =
androidPlayStoreCountry.value == "US"
|| androidPlayStoreCountry.value == ""
|| androidPlayStoreCountry.value == null
@Composable
private fun InvestInSimpleXChatView(modalManager: ModalManager) {
if (!crowdfundingAvailable()) return
val showGetStake = { modalManager.showModalCloseable(cardScreen = true) { close -> GetStakeView(fromSettings = false, inCenterOfWindow = crowdfundingLayout.inCenterOfWindow(modalManager), close = close) } }
Column(modifier = Modifier.padding(bottom = 12.dp)) {
Text(
generalGetString(MR.strings.v7_0_invest),
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(bottom = 6.dp)
)
Text(
buildAnnotatedString {
append(generalGetString(MR.strings.v7_0_invest_descr))
append(" ")
withStyle(SpanStyle(color = MaterialTheme.colors.primary)) {
append(generalGetString(MR.strings.learn_more))
}
},
fontSize = 15.sp,
modifier = Modifier
.pointerHoverIcon(PointerIcon.Hand)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = showGetStake
)
)
if (BuildConfigCommon.SIMPLEX_ASSETS) {
Image(
painterResource(MR.images.crowdfunding_1),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.padding(top = 8.dp)
.widthIn(max = MAX_CROWDFUNDING_IMAGE_WIDTH)
.fillMaxWidth()
.clip(crowdfundingLayout.imageShape)
.pointerHoverIcon(PointerIcon.Hand)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = showGetStake
)
)
}
}
}
private class CrowdfundingSlide(
val image: ImageResource,
val heading: String,
val info: String?,
val text: String,
)
// not localized: the page is only shown to US investors, and the text duplicates the images
private val getStakeSlides: List<CrowdfundingSlide> = listOf(
CrowdfundingSlide(
MR.images.crowdfunding_1,
"The first and the only messaging network without any user IDs",
null,
"By investing, you can benefit from the company growth, and help us build the future of private and secure communications."
),
CrowdfundingSlide(
MR.images.crowdfunding_2,
"480,000+ users joined on their own",
null,
"SimpleX users have been more than doubling every year without any paid marketing, and donated over \$650,000."
),
CrowdfundingSlide(
MR.images.crowdfunding_3,
"Developers already bet on SimpleX success",
"Independent developers created moderation and AI bots, Telegram bridges, and a public server registry.",
"Every service developers build on SimpleX Network may increase its value, and bring new users to SimpleX Chat."
),
CrowdfundingSlide(
MR.images.crowdfunding_4,
"Revenue plan: free for users, channels & businesses pay",
"SimpleX Chat plans to earn from the infrastructure and services that creators, businesses and large communities need as they grow.",
"Read about how we plan to make SimpleX Chat and network profitable, and about all the investment terms on Wefunder."
),
)
@Composable
fun GetStakeView(fromSettings: Boolean, inCenterOfWindow: Boolean = false, close: () -> Unit) {
val uriHandler = LocalUriHandler.current
val stopped = chatModel.chatRunning.value == false
@Composable
fun slideImage(slide: CrowdfundingSlide) {
if (BuildConfigCommon.SIMPLEX_ASSETS) {
Image(
painterResource(slide.image),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.widthIn(max = crowdfundingLayout.maxImageWidth)
.fillMaxWidth()
.clip(crowdfundingLayout.imageShape)
.fullScreenOnClick(slide.image)
)
} else {
Text(slide.heading, style = MaterialTheme.typography.h4, fontWeight = FontWeight.Medium)
if (slide.info != null) {
Text(slide.info, Modifier.padding(top = 4.dp), lineHeight = 24.sp)
}
}
}
ColumnWithScrollBar(Modifier.pinchZoom().padding(horizontal = DEFAULT_PADDING)) {
// in the center of the window the page is wide enough for the title to fit on one line
val title = "Get a stake in\nSimpleX Chat"
AppBarTitle(if (inCenterOfWindow) title.replace("\n", " ") else title, withPadding = false)
// What's new already shows the image of the first slide, above the link that opens this page
if (fromSettings) {
slideImage(getStakeSlides[0])
}
Text(
buildAnnotatedString {
append(getStakeSlides[0].text)
// only the link is clickable, the rest of the paragraph is not
withLink(LinkAnnotation.Url(WEFUNDER_URL) { uriHandler.openUriCatching(WEFUNDER_URL) }) {
withStyle(SpanStyle(color = MaterialTheme.colors.primary, fontWeight = FontWeight.Bold)) {
append(" Learn more and invest on Wefunder.")
}
}
},
Modifier.padding(top = if (fromSettings) 8.dp else 0.dp),
lineHeight = 24.sp
)
getStakeSlides.drop(1).forEach { slide ->
Column(Modifier.padding(top = DEFAULT_PADDING * 1.5f)) {
slideImage(slide)
Text(slide.text, Modifier.padding(top = 8.dp), lineHeight = 24.sp)
}
}
Column(
Modifier.fillMaxWidth().padding(top = DEFAULT_PADDING * 2),
horizontalAlignment = Alignment.CenterHorizontally
) {
OnboardingActionButton(
if (appPlatform.isAndroid) Modifier.fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.v7_0_invest_learn_more,
onboarding = null,
onclick = { uriHandler.openUriCatching(WEFUNDER_URL) }
)
if (!chatModel.desktopNoUserNoRemote) {
TextButtonBelowOnboardingButton(
"or ask SimpleX team",
onClick = if (stopped) null else ({
close()
uriHandler.openVerifiedSimplexUri(CROWDFUNDING_CONTACT_URI)
})
)
}
}
}
}
// there is no pinch gesture with a mouse, so on desktop a slide is opened full screen instead
@Composable
private fun Modifier.fullScreenOnClick(image: ImageResource): Modifier {
if (!appPlatform.isDesktop) return this
return pointerHoverIcon(PointerIcon.Hand).clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
ModalManager.fullscreen.showCustomModal { close ->
BackHandler(onBack = close)
Box(
Modifier
.fillMaxSize()
.background(Color.Black)
.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = close),
contentAlignment = Alignment.Center
) {
Image(painterResource(image), contentDescription = null, contentScale = ContentScale.Fit, modifier = Modifier.fillMaxSize())
}
}
}
}
private const val MAX_PAGE_ZOOM = 5f
/**
* The slide images contain small text that is unreadable at screen width, so the page can be pinch-zoomed.
* Android only: pinch is unavailable with a mouse.
*/
@Composable
private fun Modifier.pinchZoom(): Modifier {
if (!appPlatform.isAndroid) return this
var scale by remember { mutableStateOf(1f) }
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
var size by remember { mutableStateOf(IntSize.Zero) }
return this
.onGloballyPositioned { size = it.size }
.graphicsLayer {
scaleX = scale
scaleY = scale
translationX = offsetX
translationY = offsetY
}
.pointerInput(Unit) {
awaitEachGesture {
// the initial pass, as the scroll of the same column is applied after this modifier and would take the gesture first
awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
var taken: Boolean? = null
do {
val event = awaitPointerEvent(PointerEventPass.Initial)
val multiTouch = event.changes.count { it.pressed } > 1
if (multiTouch || scale > 1f) {
scale = (scale * event.calculateZoom()).coerceIn(1f, MAX_PAGE_ZOOM)
val pan = event.calculatePan()
// the page is scaled around its center, so it can be panned by half of the overflow in each direction
val maxX = size.width * (scale - 1f) / 2
val maxY = size.height * (scale - 1f) / 2
val pannedY = offsetY + pan.y * scale
// the clamp is applied even when the gesture is not taken: at scale 1 both bounds
// are 0, which resets the offsets after zooming back out
offsetX = (offsetX + pan.x * scale).coerceIn(-maxX, maxX)
offsetY = pannedY.coerceIn(-maxY, maxY)
// two fingers always mean zoom, taken without a touch slop: waiting for one would let
// the scroll reach its own slop first and scroll the page. A one finger drag is left
// to the scroll at the edges, decided once so it cannot alternate mid drag
if (multiTouch) taken = true
else if (taken == null && pan.y != 0f) taken = pannedY.absoluteValue < maxY
if (taken == true) event.changes.forEach { if (it.pressed) it.consume() }
}
} while (event.changes.any { it.pressed })
}
}
}
@Composable
fun CreateUpdateAddressShortLinkView(modalManager: ModalManager) {
@@ -87,13 +87,19 @@ fun PrivacySettingsView(
val currentUser = chatModel.currentUser.value
if (currentUser != null && !chatModel.desktopNoUserNoRemote) {
SectionDividerSpaced()
ContacRequestsFromGroupsSection(
AutoAcceptSection(
currentUser = currentUser,
setAutoAcceptGrpDirectInvs = { enable ->
setAutoAcceptMemberContacts = { enable ->
withApi {
chatModel.controller.apiSetUserAutoAcceptMemberContacts(currentUser, enable)
chatModel.currentUser.value = currentUser.copy(autoAcceptMemberContacts = enable)
}
},
setAutoAcceptGroupInvitations = { enable ->
withApi {
chatModel.controller.apiSetUserAutoAcceptGroupInvitations(currentUser, enable)
chatModel.currentUser.value = currentUser.copy(autoAcceptGroupInvitations = enable)
}
}
)
}
@@ -333,16 +339,26 @@ expect fun PrivacyDeviceSection(
)
@Composable
private fun ContacRequestsFromGroupsSection(
private fun AutoAcceptSection(
currentUser: User,
setAutoAcceptGrpDirectInvs: (Boolean) -> Unit
setAutoAcceptMemberContacts: (Boolean) -> Unit,
setAutoAcceptGroupInvitations: (Boolean) -> Unit
) {
SectionView(stringResource(MR.strings.settings_section_title_contact_requests_from_groups)) {
SettingsActionItemWithContent(painterResource(MR.images.ic_check), stringResource(MR.strings.auto_accept_contact)) {
// legacy string key names, reused for their values so this section stays translated
SectionView(stringResource(MR.strings.auto_accept_contact)) {
SettingsActionItemWithContent(painterResource(MR.images.ic_person), stringResource(MR.strings.settings_section_title_contact_requests_from_groups)) {
DefaultSwitch(
checked = currentUser.autoAcceptMemberContacts,
onCheckedChange = { enable ->
setAutoAcceptGrpDirectInvs(enable)
setAutoAcceptMemberContacts(enable)
}
)
}
SettingsActionItemWithContent(painterResource(MR.images.ic_group), stringResource(MR.strings.group_invitations)) {
DefaultSwitch(
checked = currentUser.autoAcceptGroupInvitations,
onCheckedChange = { enable ->
setAutoAcceptGroupInvitations(enable)
}
)
}
@@ -350,7 +366,7 @@ private fun ContacRequestsFromGroupsSection(
SectionTextFooter(
remember(currentUser.displayName) {
buildAnnotatedString {
append(generalGetString(MR.strings.this_setting_is_for_your_current_profile) + " ")
append(generalGetString(MR.strings.these_settings_are_for_your_current_profile) + " ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(currentUser.displayName)
}
@@ -387,7 +403,7 @@ private fun DeliveryReceiptsSection(
SectionTextFooter(
remember(currentUser.displayName) {
buildAnnotatedString {
append(generalGetString(MR.strings.receipts_section_description) + " ")
append(generalGetString(MR.strings.these_settings_are_for_your_current_profile) + " ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(currentUser.displayName)
}
@@ -29,8 +29,10 @@ import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.database.DatabaseView
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.migration.MigrateFromDeviceView
import chat.simplex.common.views.onboarding.GetStakeView
import chat.simplex.common.views.onboarding.SimpleXInfo
import chat.simplex.common.views.onboarding.WhatsNewView
import chat.simplex.common.views.onboarding.crowdfundingAvailable
import chat.simplex.common.views.usersettings.networkAndServers.NetworkAndServersView
import chat.simplex.res.MR
@@ -110,6 +112,17 @@ fun SettingsLayout(
AppShutdownItem()
AppVersionItem(showVersion)
}
if (crowdfundingAvailable()) {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.v7_0_invest)) {
SettingsActionItem(
painterResource(MR.images.ic_redeem),
stringResource(MR.strings.v7_0_crowdfunding),
{ ModalManager.start.showModalCloseable(cardScreen = true) { close -> GetStakeView(fromSettings = true, close = close) } }
)
}
}
SectionBottomSpacer()
}
}
@@ -1106,7 +1106,7 @@
<string name="tap_to_activate_profile">انقر لتنشيط ملف التعريف.</string>
<string name="v4_5_transport_isolation">عزل النقل</string>
<string name="this_string_is_not_a_connection_link">هذه السلسلة ليست رابط اتصال!</string>
<string name="receipts_section_description">هذه الإعدادات لملف تعريفك الحالي</string>
<string name="these_settings_are_for_your_current_profile">هذه الإعدادات لملف تعريفك الحالي</string>
<string name="receipts_section_description_1">يمكن تجاوزها في إعدادات الاتصال والمجموعة.</string>
<string name="network_option_tcp_connection_timeout">انتهت مهلة اتصال TCP</string>
<string name="v4_5_private_filenames_descr">لحماية المنطقة الزمنية، تستخدم ملفات الصور / الصوت التوقيت العالمي المنسق (UTC).</string>
@@ -1206,6 +1206,7 @@
<string name="stop_sharing_address">Stop sharing address?</string>
<string name="stop_sharing">Stop sharing</string>
<string name="auto_accept_contact">Auto-accept</string>
<string name="group_invitations">Group invitations</string>
<string name="sent_to_your_contact_after_connection">Sent to your contact after connection.</string>
<string name="address_welcome_message">Welcome message</string>
<string name="enter_welcome_message_optional">Enter welcome message… (optional)</string>
@@ -1557,7 +1558,7 @@
<string name="if_you_enter_passcode_data_removed">If you enter this passcode when opening the app, all app data will be irreversibly removed!</string>
<string name="set_passcode">Set passcode</string>
<string name="this_setting_is_for_your_current_profile">This setting is for your current profile</string>
<string name="receipts_section_description">These settings are for your current profile</string>
<string name="these_settings_are_for_your_current_profile">These settings are for your current profile</string>
<string name="receipts_section_description_1">They can be overridden in contact and group settings.</string>
<string name="receipts_section_contacts">Contacts</string>
<string name="receipts_contacts_title_enable">Enable receipts?</string>
@@ -1602,7 +1603,7 @@
<string name="settings_section_title_chats">Chats</string>
<string name="settings_section_title_files">Files</string>
<string name="settings_section_title_delivery_receipts">Send delivery receipts to</string>
<string name="settings_section_title_contact_requests_from_groups">Contact requests from groups</string>
<string name="settings_section_title_contact_requests_from_groups">Contact requests in groups</string>
<string name="settings_section_title_about">About</string>
<string name="settings_section_title_contact">Contact</string>
<string name="settings_section_title_support_project">Support the project</string>
@@ -2736,9 +2737,10 @@
<string name="v6_5_safe_web_links_descr">- opt-in to send link previews.\n- use SOCKS proxy if enabled.\n- prevent hyperlink phishing.\n- remove link tracking.</string>
<string name="v6_5_non_profit_governance">Non-profit governance</string>
<string name="v6_5_non_profit_governance_descr">To make SimpleX Network last.</string>
<!-- <string name="v7_0_invest">Invest in SimpleX Chat</string> -->
<!-- <string name="v7_0_invest_descr">Equity crowdfunding launched!</string> -->
<!-- <string name="v7_0_invest_learn_more">Learn more on Wefunder</string> -->
<string name="v7_0_invest" translatable="false">You can now invest in SimpleX Chat! 🚀</string>
<string name="v7_0_invest_descr" translatable="false">Crowdfunding on Wefunder.</string>
<string name="v7_0_crowdfunding" translatable="false">Crowdfunding on Wefunder</string>
<string name="v7_0_invest_learn_more" translatable="false">Learn more on Wefunder</string>
<string name="v7_0_simplex_names">SimpleX public names (BETA)</string>
<string name="v7_0_simplex_names_descr">Public names for your channel or business.</string>
<string name="v7_0_channels">Better channels 📢</string>
@@ -481,7 +481,7 @@
<string name="enter_correct_passphrase">Въведи правилна парола.</string>
<string name="feature_enabled_for_you">активирано за вас</string>
<string name="enter_password_to_show">Въведи парола в търсенето</string>
<string name="receipts_section_description">Тези настройки са за текущия ви профил</string>
<string name="these_settings_are_for_your_current_profile">Тези настройки са за текущия ви профил</string>
<string name="receipts_section_description_1">Те могат да бъдат променени в настройките за всеки контакт и група.</string>
<string name="settings_developer_tools">Инструменти за разработчици</string>
<string name="receipts_contacts_disable_for_all">Деактивиране за всички</string>
@@ -1946,7 +1946,7 @@
<string name="if_you_enter_passcode_data_removed">Si introduïu aquesta contrasenya en obrir l\'aplicació, totes les dades de l\'aplicació s\'eliminaran de manera irreversible.</string>
<string name="if_you_enter_self_destruct_code">Si introduïu el vostre codi d\'autodestrucció mentre obriu l\'aplicació:</string>
<string name="set_passcode">Estableix codi</string>
<string name="receipts_section_description">Aquesta configuració és per al vostre perfil actual</string>
<string name="these_settings_are_for_your_current_profile">Aquesta configuració és per al vostre perfil actual</string>
<string name="receipts_section_description_1">Es pot canviar a la configuració de contacte i grup.</string>
<string name="privacy_media_blur_radius_off">No</string>
<string name="settings_section_title_settings">Configuració</string>
@@ -1246,7 +1246,7 @@
<string name="snd_conn_event_ratchet_sync_required">vyžadováno opětovné vyjednávání šifrování pro %s</string>
<string name="receipts_contacts_override_disabled">Odesílání potvrzení o doručení je vypnuto pro %d kontakty.</string>
<string name="sending_delivery_receipts_will_be_enabled_all_profiles">Odesílání potvrzení o doručení bude povoleno pro všechny kontakty ve všech viditelných profilech chatu.</string>
<string name="receipts_section_description">Toto nastavení je pro váš aktuální profil</string>
<string name="these_settings_are_for_your_current_profile">Toto nastavení je pro váš aktuální profil</string>
<string name="conn_event_ratchet_sync_allowed">opětovné vyjednávání šifrování povoleno</string>
<string name="snd_conn_event_ratchet_sync_allowed">opětovné vyjednávání šifrování povoleno pro %s</string>
<string name="conn_event_ratchet_sync_required">vyžadováno opětovné vyjednávání šifrování</string>
@@ -1364,7 +1364,7 @@
<string name="v5_2_favourites_filter_descr">Nach ungelesenen und favorisierten Chats filtern.</string>
<string name="sending_delivery_receipts_will_be_enabled_all_profiles">Das Senden von Empfangsbestätigungen an alle Kontakte in allen sichtbaren Chat-Profilen wird aktiviert.</string>
<string name="receipts_contacts_override_disabled">Das Senden von Bestätigungen an %d Kontakte ist deaktiviert</string>
<string name="receipts_section_description">Diese Einstellungen gelten für Ihr aktuelles Chat-Profil</string>
<string name="these_settings_are_for_your_current_profile">Diese Einstellungen gelten für Ihr aktuelles Chat-Profil</string>
<string name="receipts_section_description_1">Sie können in den Kontakt- und Gruppeneinstellungen überschrieben werden.</string>
<string name="receipts_section_contacts">Kontakte</string>
<string name="receipts_contacts_title_disable">Bestätigungen deaktivieren\?</string>
@@ -1350,7 +1350,7 @@
<string name="the_sender_will_not_be_notified">Ο αποστολέας ΔΕΝ θα ειδοποιηθεί.</string>
<string name="smp_servers_per_user">Οι διακομιστές για τις νέες συνδέσεις του τρέχοντος προφίλ συνομιλίας σου</string>
<string name="xftp_servers_per_user">Οι διακομιστές για τα νέα αρχεία του τρέχοντος προφίλ συνομιλίας σου</string>
<string name="receipts_section_description">Αυτές οι ρυθμίσεις ισχύουν για το τρέχον προφίλ σου</string>
<string name="these_settings_are_for_your_current_profile">Αυτές οι ρυθμίσεις ισχύουν για το τρέχον προφίλ σου</string>
<string name="the_text_you_pasted_is_not_a_link">Το κείμενο που επικόλλησες δεν είναι σύνδεσμος SimpleX.</string>
<string name="migrate_from_device_uploaded_archive_will_be_removed">Το αρχείο της βάσης δεδομένων που μεταφορτώθηκε, θα διαγραφεί οριστικά από τους διακομιστές.</string>
<string name="video_decoding_exception_desc">Το βίντεο δεν μπορεί να αποκωδικοποιηθεί. Δοκίμασε ένα άλλο βίντεο ή επικοινώνησε με τους προγραμματιστές.</string>
@@ -1283,7 +1283,7 @@
<string name="receipts_contacts_override_disabled">El envío de confirmaciones está desactivado para %d contactos</string>
<string name="receipts_contacts_override_enabled">El envío de confirmaciones está activado para %d contactos</string>
<string name="send_receipts">Enviar confirmaciones</string>
<string name="receipts_section_description">Esta configuración afecta a tu perfil actual</string>
<string name="these_settings_are_for_your_current_profile">Esta configuración afecta a tu perfil actual</string>
<string name="enable_receipts_all">Activar</string>
<string name="receipts_contacts_title_disable">¿Desactivar confirmaciones\?</string>
<string name="receipts_contacts_title_enable">¿Activar confirmaciones\?</string>
@@ -756,7 +756,7 @@
<string name="self_destruct_new_display_name">نام نمایشی جدید:</string>
<string name="if_you_enter_self_destruct_code">اگر کد عبور خودتخریبی خود را زمان باز کردن برنامه وارد کنید:</string>
<string name="all_app_data_will_be_cleared">تمام اطلاعات برنامه حذف می‌شود.</string>
<string name="receipts_section_description">این تنظیمات برای پروفایل فعلی شما هستند</string>
<string name="these_settings_are_for_your_current_profile">این تنظیمات برای پروفایل فعلی شما هستند</string>
<string name="receipts_contacts_override_enabled">ارسال رسید برای %d مخاطب فعال است</string>
<string name="receipts_contacts_disable_for_all">غیرفعال برای همه</string>
<string name="receipts_groups_enable_for_all">فعال برای همه گروه‌ها</string>
@@ -1272,7 +1272,7 @@
<string name="sync_connection_force_confirm">Uudelleenneuvottele</string>
<string name="sync_connection_force_question">Uudelleenneuvottele salaus\?</string>
<string name="sync_connection_force_desc">Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin!</string>
<string name="receipts_section_description">Nämä asetukset koskevat nykyistä profiiliasi</string>
<string name="these_settings_are_for_your_current_profile">Nämä asetukset koskevat nykyistä profiiliasi</string>
<string name="receipts_section_description_1">Ne voidaan ohittaa kontakti- ja ryhmäasetuksissa.</string>
<string name="conn_event_ratchet_sync_ok">salaus ok</string>
<string name="conn_event_ratchet_sync_allowed">salauksen uudelleenneuvottelu sallittu</string>
@@ -1280,7 +1280,7 @@
<string name="rcv_conn_event_verification_code_reset">code de sécurité modifié</string>
<string name="sending_delivery_receipts_will_be_enabled_all_profiles">L\'envoi d\'accusés de réception sera activé pour tous les contacts dans tous les profils de chat visibles.</string>
<string name="receipts_section_description_1">Ils peuvent être modifiés dans les paramètres des contacts et des groupes.</string>
<string name="receipts_section_description">Ces paramètres s\'appliquent à votre profil actuel</string>
<string name="these_settings_are_for_your_current_profile">Ces paramètres s\'appliquent à votre profil actuel</string>
<string name="you_can_enable_delivery_receipts_later_alert">Vous pouvez les activer ultérieurement via les paramètres de Confidentialité et Sécurité de l\'application.</string>
<string name="receipts_contacts_title_enable">Activer les accusés de réception \?</string>
<string name="receipts_contacts_title_disable">Désactiver les accusés de réception \?</string>
@@ -1475,7 +1475,7 @@
<string name="settings_is_storing_in_clear_text">A jelmondat a beállításokban egyszerű szövegként van tárolva.</string>
<string name="terminal_always_visible">Konzol megjelenítése új ablakban</string>
<string name="alert_text_msg_bad_hash">Az előző üzenet kivonata különbözik.</string>
<string name="receipts_section_description">Ezek a beállítások csak a jelenlegi csevegési profiljára vonatkoznak</string>
<string name="these_settings_are_for_your_current_profile">Ezek a beállítások csak a jelenlegi csevegési profiljára vonatkoznak</string>
<string name="loading_remote_file_desc">Várjon, amíg a fájl betöltődik a társított hordozható eszközről</string>
<string name="read_more_in_github_with_link"><![CDATA[További információkat a <font color="#0088ff">GitHub-tárolónkban</font> talál.]]></string>
<string name="error_showing_content">Hiba történt a tartalom megjelenítésekor</string>
@@ -1206,7 +1206,7 @@
<string name="receipts_groups_title_enable">Aktifkan tanda terima untuk grup?</string>
<string name="empty_chat_profile_is_created">Profil obrolan kosong dengan nama yang disediakan dibuat, dan aplikasi terbuka seperti biasa.</string>
<string name="if_you_enter_passcode_data_removed">Jika Anda memasukkan kode sandi saat membuka aplikasi, semua data aplikasi akan dihapus secara permanen!</string>
<string name="receipts_section_description">Pengaturan ini untuk profil Anda saat ini</string>
<string name="these_settings_are_for_your_current_profile">Pengaturan ini untuk profil Anda saat ini</string>
<string name="receipts_contacts_override_enabled">Kirim tanda terima diaktifkan untuk %d kontak</string>
<string name="receipts_contacts_override_disabled">Kirim tanda terima dimatikan untuk %d kontak</string>
<string name="error_loading_xftp_servers">Gagal memuat server XFTP</string>
@@ -1290,7 +1290,7 @@
<string name="sending_delivery_receipts_will_be_enabled_all_profiles">L\'invio delle ricevute di consegna sarà attivo per tutti i contatti in tutti i profili di chat visibili.</string>
<string name="receipts_contacts_override_disabled">L\'invio di ricevute è disattivato per %d contatti</string>
<string name="sync_connection_force_desc">La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione!</string>
<string name="receipts_section_description">Queste impostazioni sono per il tuo profilo attuale</string>
<string name="these_settings_are_for_your_current_profile">Queste impostazioni sono per il tuo profilo attuale</string>
<string name="receipts_section_description_1">Possono essere sovrascritte nelle impostazioni dei contatti e dei gruppi.</string>
<string name="receipts_contacts_enable_for_all">Attiva per tutti</string>
<string name="receipts_contacts_enable_keep_overrides">Attiva (mantieni sostituzioni)</string>
@@ -1291,7 +1291,7 @@
\n- קבוצות קצת יותר טובות.
\n- ועוד!</string>
<string name="sending_delivery_receipts_will_be_enabled_all_profiles">שליחת קבלות שליחה תתאפשר עבור כל אנשי הקשר בכל פרופילי הצ\'אט הגלויים.</string>
<string name="receipts_section_description">הגדרות אלו מיועדות לפרופיל הנוכחי שלך</string>
<string name="these_settings_are_for_your_current_profile">הגדרות אלו מיועדות לפרופיל הנוכחי שלך</string>
<string name="receipts_section_description_1">ניתן לעקוף אותם בהגדרות אנשי קשר וקבוצות.</string>
<string name="receipts_contacts_override_disabled">שליחת קבלות מושבתת עבור %d אנשי קשר</string>
<string name="receipts_contacts_override_enabled">שליחת קבלות מאופשרת עבור %d אנשי קשר</string>
@@ -1278,7 +1278,7 @@
<string name="fix_connection_not_supported_by_group_member">グループメンバーによる修正はサポートされていません</string>
<string name="receipts_section_contacts">連絡先</string>
<string name="receipts_section_description_1">これらは連絡先とグループの設定が優先されます。</string>
<string name="receipts_section_description">これらの設定は現在のプロファイル用です</string>
<string name="these_settings_are_for_your_current_profile">これらの設定は現在のプロファイル用です</string>
<string name="receipts_contacts_title_enable">配信通知を有効?</string>
<string name="sender_at_ts">%s : %s</string>
<string name="fix_connection">接続を修正</string>
@@ -1198,7 +1198,7 @@
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Tai yra jūsų nuoroda grupei <b>%1$s</b>!]]></string>
<string name="to_connect_via_link_title">Kad prisijungti su nuoroda</string>
<string name="this_link_is_not_a_valid_connection_link">Ši nuoroda nėra tinkama prisijungimo nuoroda!</string>
<string name="receipts_section_description">Šie nustatymai yra jūsų dabartiniam profiliui</string>
<string name="these_settings_are_for_your_current_profile">Šie nustatymai yra jūsų dabartiniam profiliui</string>
<string name="settings_is_storing_in_clear_text">Slaptafrazė saugoma nustatymuose kaip paprastas tekstas.</string>
<string name="group_invitation_tap_to_join_incognito">Bakstelėkite, kad prisijungti kaip inkognito</string>
<string name="send_receipts_disabled_alert_msg">Ši grupė turi daugiau nei %1$d narių, pristatymo kvitai nėra siunčiami.</string>
@@ -1607,7 +1607,7 @@
<string name="onboarding_network_operators_continue">Ievada tīkla operatori turpināt</string>
<string name="incoming_video_call">Ienākošais video zvans</string>
<string name="incoming_audio_call">Ienākošais audio zvans</string>
<string name="receipts_section_description">Čeki</string>
<string name="these_settings_are_for_your_current_profile">Čeki</string>
<string name="receipts_section_description_1">Čeku apraksts 1</string>
<string name="receipts_section_contacts">Čeku kontakti</string>
<string name="receipts_contacts_title_enable">Čeku kontakti iespējot</string>
@@ -1278,7 +1278,7 @@
<string name="receipts_contacts_title_disable">Ontvangst bevestiging uitschakelen\?</string>
<string name="receipts_contacts_title_enable">Ontvangst bevestiging inschakelen\?</string>
<string name="receipts_contacts_override_enabled">Het verzenden van ontvangst bevestiging is ingeschakeld voor %d-contactpersonen</string>
<string name="receipts_section_description">Deze instellingen gelden voor uw huidige profiel</string>
<string name="these_settings_are_for_your_current_profile">Deze instellingen gelden voor uw huidige profiel</string>
<string name="receipts_contacts_disable_keep_overrides">Uitschakelen (overschrijvingen behouden)</string>
<string name="receipts_contacts_enable_for_all">Inschakelen voor iedereen</string>
<string name="receipts_contacts_enable_keep_overrides">Inschakelen (overschrijvingen behouden)</string>
@@ -1287,7 +1287,7 @@
<string name="sync_connection_force_question">Renegocjować szyfrowanie\?</string>
<string name="receipts_contacts_override_enabled">Wysyłanie potwierdzeń jest włączone dla %d kontaktów</string>
<string name="sync_connection_force_desc">Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu!</string>
<string name="receipts_section_description">Te ustawienia dotyczą Twojego bieżącego profilu</string>
<string name="these_settings_are_for_your_current_profile">Te ustawienia dotyczą Twojego bieżącego profilu</string>
<string name="receipts_section_description_1">Można je nadpisać w ustawieniach kontaktu i grupy.</string>
<string name="conn_event_ratchet_sync_ok">szyfrowanie ok</string>
<string name="conn_event_ratchet_sync_allowed">renegocjacja szyfrowania dozwolona</string>
@@ -1520,7 +1520,7 @@
<string name="member_info_member_blocked">bloqueado</string>
<string name="code_you_scanned_is_not_simplex_link_qr_code">O código que você escaneou não é um QR code SimpleX.</string>
<string name="unable_to_open_browser_desc">O navegador padrão é necessário para chamadas. Configure o navegador padrão no sistema e compartilhe mais informações com os desenvolvedores.</string>
<string name="receipts_section_description">Essas configurações são para o seu perfil atual</string>
<string name="these_settings_are_for_your_current_profile">Essas configurações são para o seu perfil atual</string>
<string name="video_decoding_exception_desc">O vídeo não pode ser decodificado. Por favor, tente com um vídeo diferente ou contate os desenvolvedores.</string>
<string name="connect_plan_this_is_your_own_one_time_link">Este é o seu próprio link de uso único!</string>
<string name="remote_ctrl_error_timeout">Tempo limite atingido durante a conexão com o desktop</string>
@@ -1999,7 +1999,7 @@
<string name="call_desktop_permission_denied_title">Pentru a efectua apeluri, permiteți utilizarea microfonului. Încheiați apelul și încercați să sunați din nou.</string>
<string name="onboarding_network_operators_cant_see_who_talks_to_whom">Când sunt activați mai mulți operatori, niciunul dintre ei nu are metadate pentru a afla cine comunică cu cine.</string>
<string name="icon_descr_video_on">Video pornit</string>
<string name="receipts_section_description">Aceste setări sunt pentru profilul tău actual</string>
<string name="these_settings_are_for_your_current_profile">Aceste setări sunt pentru profilul tău actual</string>
<string name="privacy_chat_list_open_links_yes">Da</string>
<string name="settings_message_shape_tail">Coada</string>
<string name="settings_section_title_use_from_desktop">Utilizare de pe desktop</string>
@@ -1329,7 +1329,7 @@
<string name="receipts_contacts_override_enabled">Отправка отчётов о доставке включена для %d контактов</string>
<string name="sending_delivery_receipts_will_be_enabled_all_profiles">Отправка отчётов о доставке будет включена для всех контактов во всех видимых профилях чата.</string>
<string name="this_setting_is_for_your_current_profile">Установка для Вашего активного профиля</string>
<string name="receipts_section_description">Установки для Вашего активного профиля</string>
<string name="these_settings_are_for_your_current_profile">Установки для Вашего активного профиля</string>
<string name="receipts_contacts_override_disabled">Отправка отчётов о доставке выключена для %d контактов</string>
<string name="sync_connection_force_desc">Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения!</string>
<string name="v5_2_message_delivery_receipts_descr">Вторая галочка - знать, что доставлено! ✅</string>
@@ -1280,7 +1280,7 @@
<string name="in_developing_title">เร็วๆ นี้!</string>
<string name="snd_conn_event_ratchet_sync_allowed">อนุญาตให้มีการเจรจา encryption อีกครั้งสําหรับ %s</string>
<string name="recipient_colon_delivery_status">%s: %s</string>
<string name="receipts_section_description">การตั้งค่าเหล่านี้ใช้สำหรับโปรไฟล์ปัจจุบันของคุณ</string>
<string name="these_settings_are_for_your_current_profile">การตั้งค่าเหล่านี้ใช้สำหรับโปรไฟล์ปัจจุบันของคุณ</string>
<string name="receipts_section_description_1">สามารถลบล้างได้ในการตั้งค่าผู้ติดต่อและกลุ่ม</string>
<string name="receipts_contacts_disable_keep_overrides">ปิดใช้งาน (เก็บการแทนที่)</string>
<string name="receipts_contacts_enable_keep_overrides">เปิดใช้งาน (เก็บการแทนที่)</string>
@@ -1204,7 +1204,7 @@
<string name="lock_not_enabled">SimpleX Kilit aktif değil!</string>
<string name="chat_lock">SimpleX Kilit</string>
<string name="connect_via_member_address_alert_title">Doğrudan bağlanılsın mı?</string>
<string name="receipts_section_description">Bu ayarlar mevcut profiliniz içindir</string>
<string name="these_settings_are_for_your_current_profile">Bu ayarlar mevcut profiliniz içindir</string>
<string name="smp_servers_test_failed">Sunucu testi başarısız!</string>
<string name="verify_connection">Bağlantıyı onayla</string>
<string name="add_contact_or_create_group">Yeni sohbet başlat</string>
@@ -1271,7 +1271,7 @@
<string name="abort_switch_receiving_address_confirm">Скасувати</string>
<string name="choose_file_title">Виберіть файл</string>
<string name="receipts_section_contacts">Контакти</string>
<string name="receipts_section_description">Ці налаштування стосуються вашого поточного профілю</string>
<string name="these_settings_are_for_your_current_profile">Ці налаштування стосуються вашого поточного профілю</string>
<string name="receipts_contacts_title_disable">Вимкнути повідомлення про доставку?</string>
<string name="receipts_contacts_title_enable">Увімкнути повідомлення про доставку?</string>
<string name="conn_event_ratchet_sync_allowed">можлива перезапис шифрування</string>
@@ -1972,7 +1972,7 @@
<string name="the_text_you_pasted_is_not_a_link">Văn bản bạn vừa dán không phải là một đường dẫn SimpleX.</string>
<string name="member_role_will_be_changed_with_invitation">Chức vụ sẽ được đổi thành %s. Thành viên sẽ nhận được một lời mời mới.</string>
<string name="passphrase_will_be_saved_in_settings">Mật khẩu sẽ được lưu trữ trong cài đặt dưới dạng thuần văn bản sau khi bản đổi nó hoặc khởi động lại ứng dụng.</string>
<string name="receipts_section_description">Các cài đặt này là cho hồ sơ trò chuyện hiện tại của bạn</string>
<string name="these_settings_are_for_your_current_profile">Các cài đặt này là cho hồ sơ trò chuyện hiện tại của bạn</string>
<string name="member_role_will_be_changed_with_notification">Chức vụ sẽ được đổi thành %s. Tất cả mọi người trong nhóm sẽ được thông báo.</string>
<string name="migrate_from_device_uploaded_archive_will_be_removed">Bản lưu trữ cơ sở dữ liệu đã được tải lên sẽ bị xóa vĩnh viễn khỏi các máy chủ.</string>
<string name="delete_chat_profile_action_cannot_be_undone_warning">Việc này không thể được hoàn tác - hồ sơ, các liên hệ, tin nhắn và tệp của bạn sẽ biến mất mà không thể khôi phục.</string>
@@ -1344,7 +1344,7 @@
<string name="connect_via_member_address_alert_desc">连接请求将发送给该群成员。</string>
<string name="settings_is_storing_in_clear_text">密码以明文形式存储在设置中。</string>
<string name="error_synchronizing_connection">同步连接时出错</string>
<string name="receipts_section_description">这些设置适用于你当前的个人资料</string>
<string name="these_settings_are_for_your_current_profile">这些设置适用于你当前的个人资料</string>
<string name="snd_conn_event_ratchet_sync_allowed">允许为 %s 重新协商加密</string>
<string name="receipts_contacts_enable_for_all">为所有人启用</string>
<string name="conn_event_ratchet_sync_required">需要重新协商加密</string>
@@ -2444,7 +2444,7 @@
<string name="without_tor_or_vpn_ip_address_will_be_visible_to_file_servers">未使用 Tor 或 VPN 時,你的 IP 地址會對檔案伺服器可見。</string>
<string name="sanitize_links_toggle">移除連結追蹤</string>
<string name="this_setting_is_for_your_current_profile">此設定適用於你目前的個人檔案</string>
<string name="receipts_section_description">這些設定適用於你目前的個人檔案</string>
<string name="these_settings_are_for_your_current_profile">這些設定適用於你目前的個人檔案</string>
<string name="receipts_section_description_1">可在聯絡人和群組設定中覆寫這些設定。</string>
<string name="receipts_contacts_override_enabled">已為 %d 個聯絡人啟用送達回條</string>
<string name="receipts_contacts_override_disabled">已為 %d 個聯絡人停用送達回條</string>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<rect width="24" height="24" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<rect width="24" height="24" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<rect width="24" height="24" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<rect width="24" height="24" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

@@ -23,6 +23,7 @@ import javax.swing.SwingUtilities
internal class SkiaBitmapVideoSurface : VideoSurface(VideoSurfaceAdapters.getVideoSurfaceAdapter()) {
private val videoSurface = SkiaBitmapVideoSurface()
@Volatile private var mediaPlayer: MediaPlayer? = null
private lateinit var imageInfo: ImageInfo
private lateinit var frameBytes: ByteArray
private val skiaBitmap: Bitmap = Bitmap()
@@ -31,6 +32,7 @@ internal class SkiaBitmapVideoSurface : VideoSurface(VideoSurfaceAdapters.getVid
val bitmap: State<ImageBitmap?> = composeBitmap
override fun attach(mediaPlayer: MediaPlayer) {
this.mediaPlayer = mediaPlayer
videoSurface.attach(mediaPlayer)
}
@@ -39,9 +41,17 @@ internal class SkiaBitmapVideoSurface : VideoSurface(VideoSurfaceAdapters.getVid
private var sourceHeight: Int = 0
override fun getBufferFormat(sourceWidth: Int, sourceHeight: Int): BufferFormat {
this.sourceWidth = sourceWidth
this.sourceHeight = sourceHeight
return RV32BufferFormat(sourceWidth, sourceHeight)
// libvlc passes the size the decoder padded the picture to, not the size of the picture (dav1d
// pads to a multiple of 128, so 1920x1080 arrives as 1920x1152), and vlc stretches the picture to
// fill whatever size is returned. Ask for the size of the track being played instead. The format
// is negotiated more than once, and vlc has not selected the track yet on the first calls
val player = mediaPlayer
val tracks = player?.media()?.info()?.videoTracks()
val playingTrack = player?.video()?.track()
val track = tracks?.firstOrNull { it.id() == playingTrack } ?: tracks?.singleOrNull()
this.sourceWidth = track?.width()?.takeIf { it > 0 } ?: sourceWidth
this.sourceHeight = track?.height()?.takeIf { it > 0 } ?: sourceHeight
return RV32BufferFormat(this.sourceWidth, this.sourceHeight)
}
override fun allocatedBuffers(buffers: Array<ByteBuffer>) {
+1 -1
View File
@@ -345,7 +345,7 @@ class ArchiveConfig(
### Import Flow
1. User selects an archive file.
2. UI copies it to a temp location and constructs an `ArchiveConfig`.
2. UI copies it into `databaseExportDir` and constructs an `ArchiveConfig`. The destination is confined to that folder: `getFileName` returns a bare file name on every platform, and `saveArchiveFromURI` checks the canonical destination before copying.
3. Calls `apiImportArchive(config)` which sends `CC.ApiImportArchive` to the Haskell core.
4. The core extracts and replaces both databases.
5. Returns `CR.ArchiveImported` with a list of `ArchiveError` (non-fatal issues during import).