diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 9b36e04522..cb2ed9d2ca 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -78,6 +78,8 @@ class AppPreferences(val context: Context) { ) val performLA = mkBoolPreference(SHARED_PREFS_PERFORM_LA, false) val laNoticeShown = mkBoolPreference(SHARED_PREFS_LA_NOTICE_SHOWN, false) + val privacyAcceptImages = mkBoolPreference(SHARED_PREFS_PRIVACY_ACCEPT_IMAGES, true) + val privacyLinkPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS, true) private fun mkIntPreference(prefName: String, default: Int) = Preference( @@ -107,6 +109,8 @@ class AppPreferences(val context: Context) { private const val SHARED_PREFS_WEBRTC_CALLS_ON_LOCK_SCREEN = "CallsOnLockScreen" private const val SHARED_PREFS_PERFORM_LA = "PerformLA" private const val SHARED_PREFS_LA_NOTICE_SHOWN = "LANoticeShown" + private const val SHARED_PREFS_PRIVACY_ACCEPT_IMAGES = "PrivacyAcceptImages" + private const val SHARED_PREFS_PRIVACY_LINK_PREVIEWS = "PrivacyLinkPreviews" } } @@ -510,13 +514,8 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager val cItem = r.chatItem.chatItem chatModel.addChatItem(cInfo, cItem) val file = cItem.file - if (cItem.content.msgContent is MsgContent.MCImage && file != null && file.fileSize <= MAX_IMAGE_SIZE) { - withApi { - val chatItem = apiReceiveFile(file.fileId) - if (chatItem != null) { - chatItemSimpleUpdate(chatItem) - } - } + if (cItem.content.msgContent is MsgContent.MCImage && file != null && file.fileSize <= MAX_IMAGE_SIZE && appPrefs.privacyAcceptImages.get()) { + withApi { receiveFile(file.fileId) } } if (!cItem.isCall && (!isAppOnForeground(appContext) || chatModel.chatId.value != cInfo.id)) { ntfManager.notifyMessageReceived(cInfo, cItem) @@ -615,6 +614,13 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager } } + suspend fun receiveFile(fileId: Long) { + val chatItem = apiReceiveFile(fileId) + if (chatItem != null) { + chatItemSimpleUpdate(chatItem) + } + } + private fun chatItemSimpleUpdate(aChatItem: AChatItem) { val cInfo = aChatItem.chatInfo val cItem = aChatItem.chatItem diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt index 08c86f7cb7..52340e2ac6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.launch @Composable fun TerminalView(chatModel: ChatModel, close: () -> Unit) { - val composeState = remember { mutableStateOf(ComposeState()) } + val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) } BackHandler(onBack = close) TerminalLayout( chatModel.terminalItems, @@ -35,7 +35,7 @@ fun TerminalView(chatModel: ChatModel, close: () -> Unit) { withApi { // show "in progress" chatModel.controller.sendCmd(CC.Console(composeState.value.message)) - composeState.value = ComposeState() + composeState.value = ComposeState(useLinkPreviews = false) // hide "in progress" } }, @@ -120,7 +120,7 @@ fun PreviewTerminalLayout() { SimpleXTheme { TerminalLayout( terminalItems = TerminalItem.sampleData, - composeState = remember { mutableStateOf(ComposeState()) }, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, sendCommand = {}, close = {} ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 2a9825bd1c..6d94fd8c74 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -44,7 +44,8 @@ import kotlinx.datetime.Clock fun ChatView(chatModel: ChatModel) { val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } val user = chatModel.currentUser.value - val composeState = remember { mutableStateOf(ComposeState()) } + val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() + val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = useLinkPreviews)) } val attachmentOption = remember { mutableStateOf(null) } val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val scope = rememberCoroutineScope() @@ -83,6 +84,7 @@ fun ChatView(chatModel: ChatModel) { scope, attachmentBottomSheetState, chatModel.chatItems, + useLinkPreviews = useLinkPreviews, back = { chatModel.chatId.value = null }, info = { ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, close) } }, openDirectChat = { contactId -> @@ -104,14 +106,7 @@ fun ChatView(chatModel: ChatModel) { } }, receiveFile = { fileId -> - withApi { - val chatItem = chatModel.controller.apiReceiveFile(fileId) - if (chatItem != null) { - val cInfo = chatItem.chatInfo - val cItem = chatItem.chatItem - chatModel.upsertChatItem(cInfo, cItem) - } - } + withApi { chatModel.controller.receiveFile(fileId) } }, startCall = { media -> val cInfo = chat.chatInfo @@ -143,6 +138,7 @@ fun ChatLayout( scope: CoroutineScope, attachmentBottomSheetState: ModalBottomSheetState, chatItems: List, + useLinkPreviews: Boolean, back: () -> Unit, info: () -> Unit, openDirectChat: (Long) -> Unit, @@ -175,7 +171,7 @@ fun ChatLayout( modifier = Modifier.navigationBarsWithImePadding() ) { contentPadding -> Box(Modifier.padding(contentPadding)) { - ChatItemsList(user, chat, composeState, chatItems, openDirectChat, deleteMessage, receiveFile, acceptCall) + ChatItemsList(user, chat, composeState, chatItems, useLinkPreviews, openDirectChat, deleteMessage, receiveFile, acceptCall) } } } @@ -261,6 +257,7 @@ fun ChatItemsList( chat: Chat, composeState: MutableState, chatItems: List, + useLinkPreviews: Boolean, openDirectChat: (Long) -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, receiveFile: (Long) -> Unit, @@ -302,11 +299,11 @@ fun ChatItemsList( } else { Spacer(Modifier.size(42.dp)) } - ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, showMember = showMember, deleteMessage = deleteMessage, receiveFile = receiveFile, acceptCall = acceptCall) + ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, showMember = showMember, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, acceptCall = acceptCall) } } else { Box(Modifier.padding(start = 86.dp, end = 12.dp)) { - ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, deleteMessage = deleteMessage, receiveFile = receiveFile, acceptCall = acceptCall) + ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, acceptCall = acceptCall) } } } else { // direct message @@ -317,7 +314,7 @@ fun ChatItemsList( end = if (sent) 12.dp else 76.dp, ) ) { - ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, deleteMessage = deleteMessage, receiveFile = receiveFile, acceptCall = acceptCall) + ChatItemView(user, chat.chatInfo, cItem, composeState, cxt, uriHandler, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, acceptCall = acceptCall) } } } @@ -375,12 +372,13 @@ fun PreviewChatLayout() { chatItems = chatItems, chatStats = Chat.ChatStats() ), - composeState = remember { mutableStateOf(ComposeState()) }, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, composeView = {}, attachmentOption = remember { mutableStateOf(null) }, scope = rememberCoroutineScope(), attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), chatItems = chatItems, + useLinkPreviews = true, back = {}, info = {}, openDirectChat = {}, @@ -421,12 +419,13 @@ fun PreviewGroupChatLayout() { chatItems = chatItems, chatStats = Chat.ChatStats() ), - composeState = remember { mutableStateOf(ComposeState()) }, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, composeView = {}, attachmentOption = remember { mutableStateOf(null) }, scope = rememberCoroutineScope(), attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), chatItems = chatItems, + useLinkPreviews = true, back = {}, info = {}, openDirectChat = {}, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt index bd87d253de..c3852b38c1 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt @@ -45,7 +45,7 @@ import java.io.File sealed class ComposePreview { object NoPreview: ComposePreview() - class CLinkPreview(val linkPreview: LinkPreview): ComposePreview() + class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview() class ImagePreview(val image: String): ComposePreview() class FilePreview(val fileName: String): ComposePreview() } @@ -60,12 +60,14 @@ data class ComposeState( val message: String = "", val preview: ComposePreview = ComposePreview.NoPreview, val contextItem: ComposeContextItem = ComposeContextItem.NoContextItem, - val inProgress: Boolean = false + val inProgress: Boolean = false, + val useLinkPreviews: Boolean ) { - constructor(editingItem: ChatItem): this( + constructor(editingItem: ChatItem, useLinkPreviews: Boolean): this ( editingItem.content.text, chatItemPreview(editingItem), - ComposeContextItem.EditingItem(editingItem) + ComposeContextItem.EditingItem(editingItem), + useLinkPreviews = useLinkPreviews ) val editing: Boolean @@ -88,7 +90,7 @@ data class ComposeState( when (preview) { is ComposePreview.ImagePreview -> false is ComposePreview.FilePreview -> false - else -> true + else -> useLinkPreviews } val linkPreview: LinkPreview? get() = @@ -124,6 +126,7 @@ fun ComposeView( val prevLinkUrl = remember { mutableStateOf(null) } val pendingLinkUrl = remember { mutableStateOf(null) } val cancelledLinks = remember { mutableSetOf() } + val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) val textStyle = remember { mutableStateOf(smallFont) } // attachments @@ -239,6 +242,7 @@ fun ComposeView( fun loadLinkPreview(url: String, wait: Long? = null) { if (pendingLinkUrl.value == url) { + composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null)) withApi { if (wait != null) delay(wait) val lp = getLinkPreview(url) @@ -277,7 +281,7 @@ fun ComposeView( is ComposePreview.CLinkPreview -> { val url = parseMessage(cs.message) val lp = composePreview.linkPreview - if (url == lp.uri) { + if (lp != null && url == lp.uri) { MsgContent.MCLink(cs.message, preview = lp) } else { MsgContent.MCText(cs.message) @@ -299,7 +303,7 @@ fun ComposeView( } fun clearState() { - composeState.value = ComposeState() + composeState.value = ComposeState(useLinkPreviews = useLinkPreviews) textStyle.value = smallFont chosenImage.value = null chosenFile.value = null @@ -397,6 +401,7 @@ fun ComposeView( if (uri != null) { cancelledLinks.add(uri) } + pendingLinkUrl.value = null composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index b4d5aa24f4..b88f5230cc 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -111,7 +111,7 @@ fun PreviewSendMsgView() { val textStyle = remember { mutableStateOf(smallFont) } SimpleXTheme { SendMsgView( - composeState = remember { mutableStateOf(ComposeState()) }, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, sendMessage = {}, onMessageChange = { _ -> }, textStyle = textStyle @@ -129,7 +129,7 @@ fun PreviewSendMsgView() { fun PreviewSendMsgViewEditing() { val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) val textStyle = remember { mutableStateOf(smallFont) } - val composeStateEditing = ComposeState(editingItem = ChatItem.getSampleData()) + val composeStateEditing = ComposeState(editingItem = ChatItem.getSampleData(), useLinkPreviews = true) SimpleXTheme { SendMsgView( composeState = remember { mutableStateOf(composeStateEditing) }, @@ -150,7 +150,7 @@ fun PreviewSendMsgViewEditing() { fun PreviewSendMsgViewInProgress() { val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) val textStyle = remember { mutableStateOf(smallFont) } - val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt"), inProgress = true) + val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt"), inProgress = true, useLinkPreviews = true) SimpleXTheme { SendMsgView( composeState = remember { mutableStateOf(composeStateInProgress) }, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt index e98f6b2b90..139e8e31a9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt @@ -26,7 +26,8 @@ import chat.simplex.app.views.helpers.* fun CIImageView( image: String, file: CIFile?, - showMenu: MutableState + showMenu: MutableState, + receiveFile: (Long) -> Unit ) { @Composable fun loadingIndicator() { @@ -98,11 +99,21 @@ fun CIImageView( }) } else { imageView(base64ToBitmap(image), onClick = { - if (file != null && file.fileStatus == CIFileStatus.RcvAccepted) - AlertManager.shared.showAlertMsg( - generalGetString(R.string.waiting_for_image), - generalGetString(R.string.image_will_be_received_when_contact_is_online) - ) + if (file != null) { + when (file.fileStatus) { + CIFileStatus.RcvInvitation -> + receiveFile(file.fileId) + CIFileStatus.RcvAccepted -> + AlertManager.shared.showAlertMsg( + generalGetString(R.string.waiting_for_image), + generalGetString(R.string.image_will_be_received_when_contact_is_online) + ) + CIFileStatus.RcvTransfer -> {} // ? + CIFileStatus.RcvComplete -> {} // ? + CIFileStatus.RcvCancelled -> {} // TODO + else -> {} + } + } }) } loadingIndicator() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt index c81adc6a0c..8ff0593d65 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt @@ -36,6 +36,7 @@ fun ChatItemView( cxt: Context, uriHandler: UriHandler? = null, showMember: Boolean = false, + useLinkPreviews: Boolean, deleteMessage: (Long, CIDeleteMode) -> Unit, receiveFile: (Long) -> Unit, acceptCall: (Contact) -> Unit @@ -69,7 +70,7 @@ fun ChatItemView( ) { ItemAction(stringResource(R.string.reply_verb), Icons.Outlined.Reply, onClick = { if (composeState.value.editing) { - composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem)) + composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) } else { composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) } @@ -98,7 +99,7 @@ fun ChatItemView( } if (cItem.meta.editable) { ItemAction(stringResource(R.string.edit_verb), Icons.Filled.Edit, onClick = { - composeState.value = ComposeState(editingItem = cItem) + composeState.value = ComposeState(editingItem = cItem, useLinkPreviews = useLinkPreviews) showMenu.value = false }) } @@ -203,7 +204,8 @@ fun PreviewChatItemView() { ChatItem.getSampleData( 1, CIDirection.DirectSnd(), Clock.System.now(), "hello" ), - composeState = remember { mutableStateOf(ComposeState()) }, + useLinkPreviews = true, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, cxt = LocalContext.current, deleteMessage = { _, _ -> }, receiveFile = {}, @@ -220,7 +222,8 @@ fun PreviewChatItemViewDeletedContent() { User.sampleData, ChatInfo.Direct.sampleData, ChatItem.getDeletedContentSampleData(), - composeState = remember { mutableStateOf(ComposeState()) }, + useLinkPreviews = true, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, cxt = LocalContext.current, deleteMessage = { _, _ -> }, receiveFile = {}, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index 2ebd299f92..9654107e08 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -121,7 +121,7 @@ fun FramedItemView( Column(Modifier.fillMaxWidth()) { when (val mc = ci.content.msgContent) { is MsgContent.MCImage -> { - CIImageView(image = mc.image, file = ci.file, showMenu) + CIImageView(image = mc.image, file = ci.file, showMenu, receiveFile) if (mc.text == "") { metaColor = Color.White } else { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt index e4afa68202..b9ae1c1140 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt @@ -11,6 +11,7 @@ import androidx.compose.material.icons.outlined.Close import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource @@ -66,23 +67,36 @@ suspend fun getLinkPreview(url: String): LinkPreview? { @Composable -fun ComposeLinkView(linkPreview: LinkPreview, cancelPreview: () -> Unit) { +fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit) { Row( Modifier.fillMaxWidth().padding(top = 8.dp).background(SentColorLight), verticalAlignment = Alignment.CenterVertically ) { - val imageBitmap = base64ToBitmap(linkPreview.image).asImageBitmap() - Image( - imageBitmap, - stringResource(R.string.image_descr_link_preview), - modifier = Modifier.width(80.dp).height(60.dp).padding(end = 8.dp) - ) - Column(Modifier.fillMaxWidth().weight(1F)) { - Text(linkPreview.title, maxLines = 1, overflow = TextOverflow.Ellipsis) - Text( - linkPreview.uri, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.body2 + if (linkPreview == null) { + Box( + Modifier.fillMaxWidth().weight(1f).height(60.dp).padding(start = 16.dp), + contentAlignment = Alignment.CenterStart + ) { + CircularProgressIndicator( + Modifier.size(16.dp), + color = HighOrLowlight, + strokeWidth = 2.dp + ) + } + } else { + val imageBitmap = base64ToBitmap(linkPreview.image).asImageBitmap() + Image( + imageBitmap, + stringResource(R.string.image_descr_link_preview), + modifier = Modifier.width(80.dp).height(60.dp).padding(end = 8.dp) ) + Column(Modifier.fillMaxWidth().weight(1F)) { + Text(linkPreview.title, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text( + linkPreview.uri, maxLines = 1, overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.body2 + ) + } } IconButton(onClick = cancelPreview, modifier = Modifier.padding(0.dp)) { Icon( @@ -139,4 +153,12 @@ fun PreviewComposeLinkView() { SimpleXTheme { ComposeLinkView(LinkPreview.sampleData) { -> } } -} \ No newline at end of file +} + +@Preview(showBackground = true) +@Composable +fun PreviewComposeLinkViewLoading() { + SimpleXTheme { + ComposeLinkView(null) { -> } + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt index 5037647184..d93bf927c2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt @@ -34,7 +34,7 @@ fun CallSettingsLayout( ) { val lockCallState = remember { mutableStateOf(callOnLockScreen.get()) } Text( - stringResource(R.string.call_settings), + stringResource(R.string.your_calls), Modifier.padding(bottom = 24.dp), style = MaterialTheme.typography.h1 ) @@ -71,6 +71,7 @@ fun SharedPreferenceToggle( checkedThumbColor = MaterialTheme.colors.primary, uncheckedThumbColor = HighOrLowlight ), + modifier = Modifier.padding(end = 6.dp) ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt new file mode 100644 index 0000000000..5e6ae30448 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt @@ -0,0 +1,65 @@ +package chat.simplex.app.views.usersettings + +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import chat.simplex.app.R +import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.HighOrLowlight + +@Composable +fun PrivacySettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { + @Composable fun divider() = Divider(Modifier.padding(horizontal = 8.dp)) + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + stringResource(R.string.your_privacy), + style = MaterialTheme.typography.h1, + modifier = Modifier.padding(start = 8.dp, bottom = 24.dp) + ) + ChatLockSection(chatModel.performLA, setPerformLA) + Spacer(Modifier.height(24.dp)) + + AutoAcceptImagesSection(chatModel.controller.appPrefs.privacyAcceptImages) + divider() + LinkPreviewsSection(chatModel.controller.appPrefs.privacyLinkPreviews) + divider() + } +} + +@Composable private fun AutoAcceptImagesSection(prefAcceptImages: Preference) { + SettingsSectionView() { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Outlined.Image, + contentDescription = stringResource(R.string.auto_accept_images), + tint = HighOrLowlight, + ) + Spacer(Modifier.padding(horizontal = 4.dp)) + SharedPreferenceToggle(stringResource(R.string.auto_accept_images), prefAcceptImages) + } + } +} + +@Composable private fun LinkPreviewsSection(prefLinkPreviews: Preference) { + SettingsSectionView() { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Outlined.TravelExplore, + contentDescription = stringResource(R.string.send_link_previews), + tint = HighOrLowlight, + ) + Spacer(Modifier.padding(horizontal = 4.dp)) + SharedPreferenceToggle(stringResource(R.string.send_link_previews), prefLinkPreviews) + } + } +} \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index acbeb5b237..0436233d39 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -47,7 +47,6 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { profile = user.profile, runServiceInBackground = chatModel.runServiceInBackground, setRunServiceInBackground = ::setRunServiceInBackground, - performLA = chatModel.performLA, setPerformLA = setPerformLA, showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } }, showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } }, @@ -65,7 +64,6 @@ fun SettingsLayout( profile: Profile, runServiceInBackground: MutableState, setRunServiceInBackground: (Boolean) -> Unit, - performLA: MutableState, setPerformLA: (Boolean) -> Unit, showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), @@ -103,7 +101,7 @@ fun SettingsLayout( CallSettingsSection(showModal) divider() - ChatLockSection(performLA, setPerformLA) + PrivacySettingsSection(showModal, setPerformLA) divider() PrivateNotificationsSection(runServiceInBackground, setRunServiceInBackground) divider() @@ -154,6 +152,18 @@ fun SettingsLayout( } } +@Composable private fun PrivacySettingsSection(showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), setPerformLA: (Boolean) -> Unit) { + SettingsSectionView(showModal { PrivacySettingsView(it, setPerformLA) }) { + Icon( + Icons.Outlined.Lock, + contentDescription = stringResource(R.string.privacy_and_security), + tint = HighOrLowlight, + ) + Spacer(Modifier.padding(horizontal = 4.dp)) + Text(stringResource(R.string.privacy_and_security)) + } +} + @Composable private fun HelpViewSection(showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { SettingsSectionView(showModal { HelpView(it) }) { Icon( @@ -258,13 +268,13 @@ fun SettingsLayout( checkedThumbColor = MaterialTheme.colors.primary, uncheckedThumbColor = HighOrLowlight ), - modifier = Modifier.padding(end = 8.dp) + modifier = Modifier.padding(end = 6.dp) ) } } } -@Composable private fun ChatLockSection(performLA: MutableState, setPerformLA: (Boolean) -> Unit) { +@Composable fun ChatLockSection(performLA: MutableState, setPerformLA: (Boolean) -> Unit) { SettingsSectionView() { Row(verticalAlignment = Alignment.CenterVertically) { Icon( @@ -286,7 +296,7 @@ fun SettingsLayout( checkedThumbColor = MaterialTheme.colors.primary, uncheckedThumbColor = HighOrLowlight ), - modifier = Modifier.padding(end = 8.dp) + modifier = Modifier.padding(end = 6.dp) ) } } @@ -362,7 +372,6 @@ fun PreviewSettingsLayout() { profile = Profile.sampleData, runServiceInBackground = remember { mutableStateOf(true) }, setRunServiceInBackground = {}, - performLA = remember { mutableStateOf(false) }, setPerformLA = {}, showModal = { {} }, showCustomModal = { {} }, diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 1f1121ef43..458c0e8c19 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -168,7 +168,7 @@ Разрешение не получено! Камера Галерея - Файлы\n(v2.0) + Файлы Спасибо, что установили SimpleX Chat! @@ -379,7 +379,8 @@ аудиозвонок - Настройки звонков + Аудио- и видеозвонки + Ваши звонки Соединяться через сервер (relay) Звонки на экране блокировки: Принимать @@ -421,4 +422,10 @@ повторное сообщение Пропущенные сообщения Это может случится, когда:\n1. Сервер удалил сообщения, если они не были доставлены в течение 30 дней.\n2. Сервер, через который вы получаете сообщения от контакта, был обновлён и перезапущен.\n3. Соединение компроментировано.\nПожалуйста, соединитесь с девелоперами через Настройки, чтобы получать уведомления о серверах.\nМы планируем добавить избыточную доставку сообщений, чтобы не терять сообщения. + + + Конфиденциальность + Конфиденциальность + Автоприем изображений + Отправлять картинки ссылок diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index b5b808d43b..fe61acfaa1 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -169,7 +169,7 @@ Permission Denied! Use Camera From Gallery - Choose file\n(new in v2.0) + Choose file Thank you for installing SimpleX Chat! @@ -381,7 +381,8 @@ audio call - Call settings + Audio & video calls + Your calls Connect via relay Calls on lock screen: Accept @@ -423,4 +424,10 @@ duplicate message Skipped messages It can happen when:\n1. The messages expire on the server if they were not received for 30 days,\n2. The server you use to receive the messages from this contact was updated and restarted.\n3. The connection is compromised.\nPlease connect to the developers via Settings to receive the updates about the servers.\nWe will be adding server redundancy to prevent lost messages. + + + Privacy & security + Your privacy + Auto-accept images + Send link previews diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 56edef0d2d..e44e40ec5e 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -578,7 +578,8 @@ func processReceivedMsg(_ res: ChatResponse) { m.addChatItem(cInfo, cItem) if case .image = cItem.content.msgContent, let file = cItem.file, - file.fileSize <= maxImageSize { + file.fileSize <= maxImageSize, + UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES) { Task { await receiveFile(fileId: file.fileId) } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 3c3b1c1454..68024cd5e0 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -10,7 +10,7 @@ import SwiftUI enum ComposePreview { case noPreview - case linkPreview(linkPreview: LinkPreview) + case linkPreview(linkPreview: LinkPreview?) case imagePreview(imagePreview: String) case filePreview(fileName: String) } @@ -26,6 +26,7 @@ struct ComposeState { var preview: ComposePreview var contextItem: ComposeContextItem var inProgress: Bool = false + var useLinkPreviews: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) init( message: String = "", @@ -80,7 +81,7 @@ struct ComposeState { case .filePreview: return false default: - return true + return useLinkPreviews } } @@ -406,11 +407,13 @@ struct ComposeView: View { if let uri = composeState.linkPreview()?.uri.absoluteString { cancelledLinks.insert(uri) } + pendingLinkUrl = nil composeState = composeState.copy(preview: .noPreview) } private func loadLinkPreview(_ url: URL) { if pendingLinkUrl == url { + composeState = composeState.copy(preview: .linkPreview(linkPreview: nil)) getLinkPreview(url: url) { linkPreview in if let linkPreview = linkPreview, pendingLinkUrl == url { @@ -432,6 +435,7 @@ struct ComposeView: View { switch (composeState.preview) { case let .linkPreview(linkPreview: linkPreview): if let url = parseMessage(composeState.message), + let linkPreview = linkPreview, url == linkPreview.uri { return .link(text: composeState.message, preview: linkPreview) } else { diff --git a/apps/ios/Shared/Views/Helpers/CIImageView.swift b/apps/ios/Shared/Views/Helpers/CIImageView.swift index 7fd1b6ded9..7b8d2efdfd 100644 --- a/apps/ios/Shared/Views/Helpers/CIImageView.swift +++ b/apps/ios/Shared/Views/Helpers/CIImageView.swift @@ -47,11 +47,23 @@ struct CIImageView: View { let uiImage = UIImage(data: data) { imageView(uiImage) .onTapGesture { - if case .rcvAccepted = file?.fileStatus { - AlertManager.shared.showAlertMsg( - title: "Waiting for image", - message: "Image will be received when your contact is online, please wait or check later!" - ) + if let file = file { + switch file.fileStatus { + case .rcvInvitation: + Task { + await receiveFile(fileId: file.fileId) + // TODO image accepted alert? + } + case .rcvAccepted: + AlertManager.shared.showAlertMsg( + title: "Waiting for image", + message: "Image will be received when your contact is online, please wait or check later!" + ) + case .rcvTransfer: () // ? + case .rcvComplete: () // ? + case .rcvCancelled: () // TODO + default: () + } } } } diff --git a/apps/ios/Shared/Views/Helpers/ComposeLinkView.swift b/apps/ios/Shared/Views/Helpers/ComposeLinkView.swift index 6ab5e49980..7b24a06f82 100644 --- a/apps/ios/Shared/Views/Helpers/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Helpers/ComposeLinkView.swift @@ -41,10 +41,32 @@ func getLinkPreview(url: URL, cb: @escaping (LinkPreview?) -> Void) { struct ComposeLinkView: View { @Environment(\.colorScheme) var colorScheme - let linkPreview: LinkPreview + let linkPreview: LinkPreview? var cancelPreview: (() -> Void)? = nil var body: some View { + HStack(alignment: .center, spacing: 8) { + if let linkPreview = linkPreview { + linkPreviewView(linkPreview) + } else { + ProgressView() + .padding(.leading, 12) + .frame(maxWidth: .infinity, minHeight: 60, maxHeight: 60, alignment: .leading) + } + if let cancelPreview = cancelPreview { + Button { cancelPreview() } label: { + Image(systemName: "multiply") + } + } + } + .padding(.vertical, 1) + .padding(.trailing, 12) + .background(colorScheme == .light ? sentColorLight : sentColorDark) + .frame(maxWidth: .infinity) + .padding(.top, 8) + } + + private func linkPreviewView(_ linkPreview: LinkPreview) -> some View { HStack(alignment: .center, spacing: 8) { if let data = Data(base64Encoded: dropImagePrefix(linkPreview.image)), let uiImage = UIImage(data: data) { @@ -62,18 +84,8 @@ struct ComposeLinkView: View { .foregroundColor(.secondary) } .padding(.vertical, 5) - .frame(maxWidth: .infinity) - if let cancelPreview = cancelPreview { - Button { cancelPreview() } label: { - Image(systemName: "multiply") - } - } + .frame(maxWidth: .infinity, minHeight: 60, maxHeight: 60) } - .padding(.vertical, 1) - .padding(.trailing, 12) - .background(colorScheme == .light ? sentColorLight : sentColorDark) - .frame(maxWidth: .infinity) - .padding(.top, 8) } } @@ -85,7 +97,11 @@ struct SmallLinkPreview_Previews: PreviewProvider { description: "", image: "data:image/jpg;base64,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" ) - ComposeLinkView(linkPreview: preview, cancelPreview: {}) - .previewLayout(.fixed(width: 360, height: 200)) + Group { + ComposeLinkView(linkPreview: preview, cancelPreview: {}) + .previewLayout(.fixed(width: 360, height: 200)) + ComposeLinkView(linkPreview: nil, cancelPreview: {}) + .previewLayout(.fixed(width: 360, height: 200)) + } } } diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift new file mode 100644 index 0000000000..33794cfbd5 --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -0,0 +1,125 @@ +// +// PrivacySettings.swift +// SimpleX (iOS) +// +// Created by Evgeny on 29/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct PrivacySettings: View { + @AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true + @AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true + + var body: some View { + VStack { + List { + Section("Device") { + SimplexLockSetting() + } + Section("Chats") { + settingsRow("photo") { + Toggle("Auto-accept images", isOn: $autoAcceptImages) + } + settingsRow("network") { + Toggle("Send link previews", isOn: $useLinkPreviews) + } + } + } + } + } +} + +struct SimplexLockSetting: View { + @AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false + @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false + @State var performLA: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) + @State private var performLAToggleReset = false + @State var laAlert: laSettingViewAlert? = nil + + enum laSettingViewAlert: Identifiable { + case laTurnedOnAlert + case laFailedAlert + case laUnavailableInstructionAlert + case laUnavailableTurningOffAlert + + var id: laSettingViewAlert { get { self } } + } + + var body: some View { + settingsRow("lock") { + Toggle("SimpleX Lock", isOn: $performLA) + } + .onChange(of: performLA) { performLAToggle in + prefLANoticeShown = true + if performLAToggleReset { + performLAToggleReset = false + } else { + if performLAToggle { + enableLA() + } else { + disableLA() + } + } + } + .alert(item: $laAlert) { alertItem in + switch alertItem { + case .laTurnedOnAlert: return laTurnedOnAlert() + case .laFailedAlert: return laFailedAlert() + case .laUnavailableInstructionAlert: return laUnavailableInstructionAlert() + case .laUnavailableTurningOffAlert: return laUnavailableTurningOffAlert() + } + } + + } + + private func enableLA() { + authenticate(reason: NSLocalizedString("Enable SimpleX Lock", comment: "authentication reason")) { laResult in + switch laResult { + case .success: + prefPerformLA = true + laAlert = .laTurnedOnAlert + case .failed: + prefPerformLA = false + withAnimation() { + performLA = false + } + performLAToggleReset = true + laAlert = .laFailedAlert + case .unavailable: + prefPerformLA = false + withAnimation() { + performLA = false + } + performLAToggleReset = true + laAlert = .laUnavailableInstructionAlert + } + } + } + + private func disableLA() { + authenticate(reason: NSLocalizedString("Disable SimpleX Lock", comment: "authentication reason")) { laResult in + switch (laResult) { + case .success: + prefPerformLA = false + case .failed: + prefPerformLA = true + withAnimation() { + performLA = true + } + performLAToggleReset = true + laAlert = .laFailedAlert + case .unavailable: + prefPerformLA = false + laAlert = .laUnavailableTurningOffAlert + } + } + } +} + +struct PrivacySettings_Previews: PreviewProvider { + static var previews: some View { + PrivacySettings() + } +} diff --git a/apps/ios/Shared/Views/UserSettings/SettingsButton.swift b/apps/ios/Shared/Views/UserSettings/SettingsButton.swift index 23033fd7cf..7292fd4373 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsButton.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsButton.swift @@ -11,14 +11,13 @@ import SwiftUI struct SettingsButton: View { @EnvironmentObject var chatModel: ChatModel @State private var showSettings = false - @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false var body: some View { Button { showSettings = true } label: { Image(systemName: "gearshape") } .sheet(isPresented: $showSettings, content: { - SettingsView(showSettings: $showSettings, performLA: prefPerformLA) + SettingsView(showSettings: $showSettings) }) } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 980d29c7ec..4ef9730c69 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -20,6 +20,8 @@ let DEFAULT_PERFORM_LA = "performLocalAuthentication" let DEFAULT_USE_NOTIFICATIONS = "useNotifications" let DEFAULT_PENDING_CONNECTIONS = "pendingConnections" let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay" +let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" +let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" let appDefaults: [String:Any] = [ DEFAULT_SHOW_LA_NOTICE: false, @@ -27,7 +29,9 @@ let appDefaults: [String:Any] = [ DEFAULT_PERFORM_LA: false, DEFAULT_USE_NOTIFICATIONS: false, DEFAULT_PENDING_CONNECTIONS: true, - DEFAULT_WEBRTC_POLICY_RELAY: true + DEFAULT_WEBRTC_POLICY_RELAY: true, + DEFAULT_PRIVACY_ACCEPT_IMAGES: true, + DEFAULT_PRIVACY_LINK_PREVIEWS: true ] private var indent: CGFloat = 36 @@ -36,24 +40,10 @@ struct SettingsView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var chatModel: ChatModel @Binding var showSettings: Bool - @State var performLA: Bool = false - @AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false - @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @AppStorage(DEFAULT_USE_NOTIFICATIONS) private var useNotifications = false @AppStorage(DEFAULT_PENDING_CONNECTIONS) private var pendingConnections = true - @State private var performLAToggleReset = false @State var showNotificationsAlert: Bool = false @State var whichNotificationsAlert = NotificationAlert.enable - @State var alert: SettingsViewAlert? = nil - - enum SettingsViewAlert: Identifiable { - case laTurnedOnAlert - case laFailedAlert - case laUnavailableInstructionAlert - case laUnavailableTurningOffAlert - - var id: SettingsViewAlert { get { self } } - } var body: some View { let user: User = chatModel.currentUser! @@ -79,12 +69,15 @@ struct SettingsView: View { Section("Settings") { NavigationLink { CallSettings() - .navigationTitle("Call settings") + .navigationTitle("Your calls") } label: { - settingsRow("video") { Text("Call settings") } + settingsRow("video") { Text("Audio & video calls") } } - settingsRow("lock") { - Toggle("SimpleX Lock", isOn: $performLA) + NavigationLink { + PrivacySettings() + .navigationTitle("Your privacy") + } label: { + settingsRow("lock") { Text("Privacy & security") } } settingsRow("link") { Toggle("Show pending connections", isOn: $pendingConnections) @@ -156,76 +149,6 @@ struct SettingsView: View { } } .navigationTitle("Your settings") - .onChange(of: performLA) { performLAToggle in - prefLANoticeShown = true - if performLAToggleReset { - performLAToggleReset = false - } else { - if performLAToggle { - enableLA() - } else { - disableLA() - } - } - } - .alert(item: $alert) { alertItem in - switch alertItem { - case .laTurnedOnAlert: return laTurnedOnAlert() - case .laFailedAlert: return laFailedAlert() - case .laUnavailableInstructionAlert: return laUnavailableInstructionAlert() - case .laUnavailableTurningOffAlert: return laUnavailableTurningOffAlert() - } - } - } - } - - private func enableLA() { - authenticate(reason: NSLocalizedString("Enable SimpleX Lock", comment: "authentication reason")) { laResult in - switch laResult { - case .success: - prefPerformLA = true - alert = .laTurnedOnAlert - case .failed: - prefPerformLA = false - withAnimation() { - performLA = false - } - performLAToggleReset = true - alert = .laFailedAlert - case .unavailable: - prefPerformLA = false - withAnimation() { - performLA = false - } - performLAToggleReset = true - alert = .laUnavailableInstructionAlert - } - } - } - - private func disableLA() { - authenticate(reason: NSLocalizedString("Disable SimpleX Lock", comment: "authentication reason")) { laResult in - switch (laResult) { - case .success: - prefPerformLA = false - case .failed: - prefPerformLA = true - withAnimation() { - performLA = true - } - performLAToggleReset = true - alert = .laFailedAlert - case .unavailable: - prefPerformLA = false - alert = .laUnavailableTurningOffAlert - } - } - } - - private func settingsRow(_ icon: String, content: @escaping () -> Content) -> some View { - ZStack(alignment: .leading) { - Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center).foregroundColor(.secondary) - content().padding(.leading, indent) } } @@ -326,6 +249,13 @@ struct SettingsView: View { } } +func settingsRow(_ icon: String, content: @escaping () -> Content) -> some View { + ZStack(alignment: .leading) { + Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center).foregroundColor(.secondary) + content().padding(.leading, indent) + } +} + struct ProfilePreview: View { var profileOf: NamedChat var color = Color(uiColor: .tertiarySystemGroupedBackground) diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 7437693637..ad1421a492 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -176,6 +176,11 @@ Attach No comment provided by engineer. + + Audio & video calls + Audio & video calls + No comment provided by engineer. + Authentication failed Authentication failed @@ -186,16 +191,16 @@ Authentication unavailable No comment provided by engineer. + + Auto-accept images + Auto-accept images + No comment provided by engineer. + Call already ended! Call already ended! No comment provided by engineer. - - Call settings - Call settings - No comment provided by engineer. - Cancel Cancel @@ -227,8 +232,8 @@ notification - Choose file (new in v2.0) - Choose file (new in v2.0) + Choose file + Choose file No comment provided by engineer. @@ -456,6 +461,11 @@ Develop No comment provided by engineer. + + Device + Device + No comment provided by engineer. + Device authentication is disabled. Turning off SimpleX Lock. Device authentication is disabled. Turning off SimpleX Lock. @@ -776,6 +786,11 @@ We will be adding server redundancy to prevent lost messages. Pre-arrange the calls, as notifications arrive with a delay (we are improving it). No comment provided by engineer. + + Privacy & security + Privacy & security + No comment provided by engineer. + Privacy redefined Privacy redefined @@ -856,6 +871,11 @@ We will be adding server redundancy to prevent lost messages. Scan contact's QR code No comment provided by engineer. + + Send link previews + Send link previews + No comment provided by engineer. + Server connected Server connected @@ -1137,6 +1157,11 @@ To connect, please ask your contact to create another connection link and check Your SimpleX contact address No comment provided by engineer. + + Your calls + Your calls + No comment provided by engineer. + Your chat address Your chat address @@ -1174,6 +1199,11 @@ You can cancel this connection and remove the contact (and try later with a new Your contact sent a file that is larger than currently supported maximum size (%@). No comment provided by engineer. + + Your privacy + Your privacy + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings index a87ccd3341..813c6f5981 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* No comment provided by engineer. */ -"Choose file" = "Choose file (new in v2.0)"; - /* No comment provided by engineer. */ "Connecting server…" = "Connecting to server…"; diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 9fbd918ca5..665b324bb8 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -176,6 +176,11 @@ Прикрепить No comment provided by engineer. + + Audio & video calls + Аудио- и видеозвонки + No comment provided by engineer. + Authentication failed Ошибка аутентификации @@ -186,16 +191,16 @@ Аутентификация недоступна No comment provided by engineer. + + Auto-accept images + Автоприем изображений + No comment provided by engineer. + Call already ended! Звонок уже завершен! No comment provided by engineer. - - Call settings - Настройки звонков - No comment provided by engineer. - Cancel Отменить @@ -213,7 +218,7 @@ Chats - Назад + Чаты back button to return to chats list @@ -227,8 +232,8 @@ notification - Choose file (new in v2.0) - Выбрать файл (v2.0) + Choose file + Выбрать файл No comment provided by engineer. @@ -456,6 +461,11 @@ Для разработчиков No comment provided by engineer. + + Device + Устройство + No comment provided by engineer. + Device authentication is disabled. Turning off SimpleX Lock. Аутентификация устройства выключена. Отключение блокировки SimpleX Chat. @@ -776,6 +786,11 @@ We will be adding server redundancy to prevent lost messages. Назначайте звонки заранее, поскольку уведомления приходят с задержкой (мы улучшаем это) No comment provided by engineer. + + Privacy & security + Конфиденциальность + No comment provided by engineer. + Privacy redefined Более конфиденциальный @@ -856,6 +871,11 @@ We will be adding server redundancy to prevent lost messages. Сосканировать QR код контакта No comment provided by engineer. + + Send link previews + Отправлять картинки ссылок + No comment provided by engineer. + Server connected Установлено соединение с сервером @@ -1137,6 +1157,11 @@ To connect, please ask your contact to create another connection link and check Ваш SimpleX адрес No comment provided by engineer. + + Your calls + Ваши звонки + No comment provided by engineer. + Your chat address Ваш SimpleX адрес @@ -1174,6 +1199,11 @@ You can cancel this connection and remove the contact (and try later with a new Ваш контакт отправил файл, размер которого превышает максимальный размер (%@). No comment provided by engineer. + + Your privacy + Конфиденциальность + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings index a87ccd3341..813c6f5981 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* No comment provided by engineer. */ -"Choose file" = "Choose file (new in v2.0)"; - /* No comment provided by engineer. */ "Connecting server…" = "Connecting to server…"; diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 1cafbbcfcd..0fa41d3d1b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; }; 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; }; 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; }; + 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; }; 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; }; 5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; }; @@ -156,6 +157,7 @@ 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = ""; }; 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = ""; }; 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrityErrorItemView.swift; sourceTree = ""; }; + 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = ""; }; 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = ""; }; 5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; }; @@ -468,6 +470,7 @@ 5CB924D327A853F100ACCCDD /* SettingsButton.swift */, 5CB924D627A8563F00ACCCDD /* SettingsView.swift */, 5C05DF522840AA1D00C683F9 /* CallSettings.swift */, + 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */, 5CB924E327A8683A00ACCCDD /* UserAddress.swift */, 5CB924E027A867BA00ACCCDD /* UserProfile.swift */, 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */, @@ -684,6 +687,7 @@ buildActionMask = 2147483647; files = ( 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */, + 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */, 5CDCAD7F281894FB00503DA2 /* API.swift in Sources */, 5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */, 5CDCAD81281A7E2700503DA2 /* Notifications.swift in Sources */, diff --git a/apps/ios/en.lproj/Localizable.strings b/apps/ios/en.lproj/Localizable.strings index a87ccd3341..813c6f5981 100644 --- a/apps/ios/en.lproj/Localizable.strings +++ b/apps/ios/en.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* No comment provided by engineer. */ -"Choose file" = "Choose file (new in v2.0)"; - /* No comment provided by engineer. */ "Connecting server…" = "Connecting to server…"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 04638574d2..ec3d6440db 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -119,6 +119,9 @@ /* No comment provided by engineer. */ "Attach" = "Прикрепить"; +/* No comment provided by engineer. */ +"Audio & video calls" = "Аудио- и видеозвонки"; + /* No comment provided by engineer. */ "audio call (not e2e encrypted)" = "аудиозвонок (не e2e зашифрованный)"; @@ -128,6 +131,9 @@ /* No comment provided by engineer. */ "Authentication unavailable" = "Аутентификация недоступна"; +/* No comment provided by engineer. */ +"Auto-accept images" = "Автоприем изображений"; + /* integrity error chat item */ "bad message hash" = "ошибка хэш сообщения"; @@ -146,9 +152,6 @@ /* call status */ "call in progress" = "активный звонок"; -/* No comment provided by engineer. */ -"Call settings" = "Настройки звонков"; - /* call status */ "calling…" = "входящий звонок…"; @@ -162,7 +165,7 @@ "Chat with the developers" = "Соединиться с разработчиками"; /* back button to return to chats list */ -"Chats" = "Назад"; +"Chats" = "Чаты"; /* No comment provided by engineer. */ "Check messages" = "Проверять сообщения"; @@ -171,7 +174,7 @@ "Checking new messages..." = "Проверяются новые сообщения..."; /* No comment provided by engineer. */ -"Choose file" = "Выбрать файл (v2.0)"; +"Choose file" = "Выбрать файл"; /* No comment provided by engineer. */ "Choose from library" = "Выбрать из библиотеки"; @@ -338,6 +341,9 @@ /* No comment provided by engineer. */ "Develop" = "Для разработчиков"; +/* No comment provided by engineer. */ +"Device" = "Устройство"; + /* No comment provided by engineer. */ "Device authentication is disabled. Turning off SimpleX Lock." = "Аутентификация устройства выключена. Отключение блокировки SimpleX Chat."; @@ -554,6 +560,9 @@ /* No comment provided by engineer. */ "Pre-arrange the calls, as notifications arrive with a delay (we are improving it)." = "Назначайте звонки заранее, поскольку уведомления приходят с задержкой (мы улучшаем это)"; +/* No comment provided by engineer. */ +"Privacy & security" = "Конфиденциальность"; + /* No comment provided by engineer. */ "Privacy redefined" = "Более конфиденциальный"; @@ -611,6 +620,9 @@ /* No comment provided by engineer. */ "secret" = "секрет"; +/* No comment provided by engineer. */ +"Send link previews" = "Отправлять картинки ссылок"; + /* No comment provided by engineer. */ "Server connected" = "Установлено соединение с сервером"; @@ -809,6 +821,9 @@ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Вы будете аутентифицированы при запуске и возобновлении приложения, которое было 30 секунд в фоновом режиме."; +/* No comment provided by engineer. */ +"Your calls" = "Ваши звонки"; + /* No comment provided by engineer. */ "Your chat address" = "Ваш SimpleX адрес"; @@ -830,6 +845,9 @@ /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ваш контакт отправил файл, размер которого превышает максимальный размер (%@)."; +/* No comment provided by engineer. */ +"Your privacy" = "Конфиденциальность"; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ваш профиль хранится на вашем устройстве и отправляется только вашим контактам.\nSimpleX серверы не могут получить доступ к вашему профилю.";