mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Merge branch 'master' into f/ios-servers-summary
This commit is contained in:
@@ -19,6 +19,9 @@ struct ChatItemForwardingView: View {
|
||||
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocused
|
||||
@State private var alert: SomeAlert?
|
||||
@State private var hasSimplexLink_: Bool?
|
||||
private let chatsToForwardTo = filterChatsToForwardTo()
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -35,47 +38,29 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
@ViewBuilder private func forwardListView() -> some View {
|
||||
VStack(alignment: .leading) {
|
||||
let chatsToForwardTo = filterChatsToForwardTo()
|
||||
if !chatsToForwardTo.isEmpty {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 8) {
|
||||
searchFieldView(text: $searchText, focussed: $searchFocused)
|
||||
.padding(.leading, 2)
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { filterChatSearched($0, s) }
|
||||
ForEach(chats) { chat in
|
||||
Divider()
|
||||
forwardListNavLinkView(chat)
|
||||
.disabled(chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
}
|
||||
List {
|
||||
searchFieldView(text: $searchText, focussed: $searchFocused)
|
||||
.padding(.leading, 2)
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) }
|
||||
ForEach(chats) { chat in
|
||||
forwardListChatView(chat)
|
||||
.disabled(chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func filterChatsToForwardTo() -> [Chat] {
|
||||
var filteredChats = chatModel.chats.filter({ canForwardToChat($0) })
|
||||
if let index = filteredChats.firstIndex(where: { $0.chatInfo.chatType == .local }) {
|
||||
let privateNotes = filteredChats.remove(at: index)
|
||||
filteredChats.insert(privateNotes, at: 0)
|
||||
}
|
||||
return filteredChats
|
||||
}
|
||||
|
||||
private func filterChatSearched(_ chat: Chat, _ searchStr: String) -> Bool {
|
||||
private func foundChat(_ chat: Chat, _ searchStr: String) -> Bool {
|
||||
let cInfo = chat.chatInfo
|
||||
return switch cInfo {
|
||||
case let .direct(contact):
|
||||
@@ -91,42 +76,70 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func canForwardToChat(_ chat: Chat) -> Bool {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv
|
||||
case let .group(groupInfo): groupInfo.sendMsgEnabled
|
||||
case let .local(noteFolder): noteFolder.sendMsgEnabled
|
||||
private func prohibitedByPref(_ chat: Chat) -> Bool {
|
||||
// preference checks should match checks in compose view
|
||||
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
|
||||
let fileProhibited = (ci.content.msgContent?.isMediaOrFileAttachment ?? false) && !chat.groupFeatureEnabled(.files)
|
||||
let voiceProhibited = (ci.content.msgContent?.isVoice ?? false) && !chat.chatInfo.featureEnabled(.voice)
|
||||
return switch chat.chatInfo {
|
||||
case .direct: voiceProhibited
|
||||
case .group: simplexLinkProhibited || fileProhibited || voiceProhibited
|
||||
case .local: false
|
||||
case .contactRequest: false
|
||||
case .contactConnection: false
|
||||
case .invalidJSON: false
|
||||
}
|
||||
}
|
||||
|
||||
private var hasSimplexLink: Bool {
|
||||
if let hasSimplexLink_ { return hasSimplexLink_ }
|
||||
let r =
|
||||
if let mcText = ci.content.msgContent?.text,
|
||||
let parsedMsg = parseSimpleXMarkdown(mcText) {
|
||||
parsedMsgHasSimplexLink(parsedMsg)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
hasSimplexLink_ = r
|
||||
return r
|
||||
}
|
||||
|
||||
private func emptyList() -> some View {
|
||||
Text("No filtered chats")
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
@ViewBuilder private func forwardListNavLinkView(_ chat: Chat) -> some View {
|
||||
|
||||
@ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View {
|
||||
let prohibited = prohibitedByPref(chat)
|
||||
Button {
|
||||
dismiss()
|
||||
if chat.id == fromChatInfo.id {
|
||||
composeState = ComposeState(
|
||||
message: composeState.message,
|
||||
preview: composeState.linkPreview != nil ? composeState.preview : .noPreview,
|
||||
contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo)
|
||||
)
|
||||
if prohibited {
|
||||
alert = SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Cannot forward message",
|
||||
message: "Selected chat preferences prohibit this message."
|
||||
),
|
||||
id: "forward prohibited by preferences"
|
||||
)
|
||||
} else {
|
||||
composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo)
|
||||
chatModel.chatId = chat.id
|
||||
dismiss()
|
||||
if chat.id == fromChatInfo.id {
|
||||
composeState = ComposeState(
|
||||
message: composeState.message,
|
||||
preview: composeState.linkPreview != nil ? composeState.preview : .noPreview,
|
||||
contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo)
|
||||
)
|
||||
} else {
|
||||
composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo)
|
||||
chatModel.chatId = chat.id
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
ChatInfoImage(chat: chat, size: 30)
|
||||
.padding(.trailing, 2)
|
||||
Text(chat.chatInfo.chatViewName)
|
||||
.foregroundColor(.primary)
|
||||
.foregroundColor(prohibited ? .secondary : .primary)
|
||||
.lineLimit(1)
|
||||
if chat.chatInfo.incognito {
|
||||
Spacer()
|
||||
@@ -142,6 +155,27 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func filterChatsToForwardTo() -> [Chat] {
|
||||
var filteredChats = ChatModel.shared.chats.filter { c in
|
||||
c.chatInfo.chatType != .local && canForwardToChat(c)
|
||||
}
|
||||
if let privateNotes = ChatModel.shared.chats.first(where: { $0.chatInfo.chatType == .local }) {
|
||||
filteredChats.insert(privateNotes, at: 0)
|
||||
}
|
||||
return filteredChats
|
||||
}
|
||||
|
||||
private func canForwardToChat(_ chat: Chat) -> Bool {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv
|
||||
case let .group(groupInfo): groupInfo.sendMsgEnabled
|
||||
case let .local(noteFolder): noteFolder.sendMsgEnabled
|
||||
case .contactRequest: false
|
||||
case .contactConnection: false
|
||||
case .invalidJSON: false
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ChatItemForwardingView(
|
||||
ci: ChatItem.getSample(1, .directSnd, .now, "hello"),
|
||||
|
||||
@@ -286,6 +286,7 @@ struct ComposeView: View {
|
||||
if chat.chatInfo.contact?.nextSendGrpInv ?? false {
|
||||
ContextInvitingContactMemberView()
|
||||
}
|
||||
// preference checks should match checks in forwarding list
|
||||
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
|
||||
let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
|
||||
let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice)
|
||||
@@ -1065,7 +1066,7 @@ struct ComposeView: View {
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
let simplexLink = parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
|
||||
let simplexLink = parsedMsgHasSimplexLink(parsedMsg)
|
||||
return (url, simplexLink)
|
||||
}
|
||||
|
||||
@@ -1105,6 +1106,10 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func parsedMsgHasSimplexLink(_ parsedMsg: [FormattedText]) -> Bool {
|
||||
parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
|
||||
}
|
||||
|
||||
struct ComposeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
|
||||
@@ -252,7 +252,7 @@ struct SMPServerSummaryView: View {
|
||||
} footer: {
|
||||
if let known = summary.known, known {
|
||||
// TODO open settings?
|
||||
Text("Server can be configured in **Settings** -> **Network & servers** -> **SMP servers**")
|
||||
Text("Server is configured in **Settings** -> **Network & servers** -> **SMP servers**.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,9 @@ import SimpleXChat
|
||||
import CodeScanner
|
||||
import AVFoundation
|
||||
|
||||
enum SomeAlert: Identifiable {
|
||||
case someAlert(alert: Alert, id: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .someAlert(_, id): return id
|
||||
}
|
||||
}
|
||||
struct SomeAlert: Identifiable {
|
||||
var alert: Alert
|
||||
var id: String
|
||||
}
|
||||
|
||||
private enum NewChatViewAlert: Identifiable {
|
||||
@@ -142,8 +137,8 @@ struct NewChatView: View {
|
||||
switch(a) {
|
||||
case let .planAndConnectAlert(alert):
|
||||
return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" })
|
||||
case let .newChatSomeAlert(.someAlert(alert, _)):
|
||||
return alert
|
||||
case let .newChatSomeAlert(a):
|
||||
return a.alert
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +176,7 @@ struct NewChatView: View {
|
||||
await MainActor.run {
|
||||
creatingConnReq = false
|
||||
if let apiAlert = apiAlert {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(alert: apiAlert, id: "createInvitation error"))
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(alert: apiAlert, id: "createInvitation error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,7 +310,7 @@ private struct ConnectView: View {
|
||||
// showQRCodeScanner = false
|
||||
connect(pastedLink)
|
||||
} else {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(
|
||||
alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."),
|
||||
id: "pasteLinkView: code is not a SimpleX link"
|
||||
))
|
||||
@@ -338,14 +333,14 @@ private struct ConnectView: View {
|
||||
if strIsSimplexLink(r.string) {
|
||||
connect(link)
|
||||
} else {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(
|
||||
alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."),
|
||||
id: "processQRCode: code is not a SimpleX link"
|
||||
))
|
||||
}
|
||||
case let .failure(e):
|
||||
logger.error("processQRCode QR code error: \(e.localizedDescription)")
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert = .newChatSomeAlert(alert: SomeAlert(
|
||||
alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"),
|
||||
id: "processQRCode: failure"
|
||||
))
|
||||
|
||||
@@ -3440,6 +3440,15 @@ public enum MsgContent: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public var isMediaOrFileAttachment: Bool {
|
||||
switch self {
|
||||
case .image: true
|
||||
case .video: true
|
||||
case .file: true
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
var cmdString: String {
|
||||
"json \(encodeJSON(self))"
|
||||
}
|
||||
|
||||
+14
@@ -2925,6 +2925,20 @@ sealed class MsgContent {
|
||||
@Serializable(with = MsgContentSerializer::class) class MCFile(override val text: String): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent()
|
||||
|
||||
val isVoice: Boolean get() =
|
||||
when (this) {
|
||||
is MCVoice -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val isMediaOrFileAttachment: Boolean get() =
|
||||
when (this) {
|
||||
is MCImage -> true
|
||||
is MCVideo -> true
|
||||
is MCFile -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val cmdString: String get() =
|
||||
if (this is MCUnknown) "json $json" else "json ${json.encodeToString(this)}"
|
||||
}
|
||||
|
||||
+43
-11
@@ -9,30 +9,55 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.views.helpers.ProfileImage
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
fun ShareListNavLinkView(
|
||||
chat: Chat,
|
||||
chatModel: ChatModel,
|
||||
isMediaOrFileAttachment: Boolean,
|
||||
isVoice: Boolean,
|
||||
hasSimplexLink: Boolean
|
||||
) {
|
||||
val stopped = chatModel.chatRunning.value == false
|
||||
when (chat.chatInfo) {
|
||||
is ChatInfo.Direct ->
|
||||
is ChatInfo.Direct -> {
|
||||
val voiceProhibited = isVoice && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
|
||||
ShareListNavLinkLayout(
|
||||
chatLinkPreview = { SharePreviewView(chat) },
|
||||
click = { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) },
|
||||
chatLinkPreview = { SharePreviewView(chat, disabled = voiceProhibited) },
|
||||
click = {
|
||||
if (voiceProhibited) {
|
||||
showForwardProhibitedByPrefAlert()
|
||||
} else {
|
||||
directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel)
|
||||
}
|
||||
},
|
||||
stopped
|
||||
)
|
||||
is ChatInfo.Group ->
|
||||
}
|
||||
is ChatInfo.Group -> {
|
||||
val simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
|
||||
val fileProhibited = isMediaOrFileAttachment && !chat.groupFeatureEnabled(GroupFeature.Files)
|
||||
val voiceProhibited = isVoice && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
|
||||
val prohibitedByPref = simplexLinkProhibited || fileProhibited || voiceProhibited
|
||||
ShareListNavLinkLayout(
|
||||
chatLinkPreview = { SharePreviewView(chat) },
|
||||
click = { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) },
|
||||
chatLinkPreview = { SharePreviewView(chat, disabled = prohibitedByPref) },
|
||||
click = {
|
||||
if (prohibitedByPref) {
|
||||
showForwardProhibitedByPrefAlert()
|
||||
} else {
|
||||
groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel)
|
||||
}
|
||||
},
|
||||
stopped
|
||||
)
|
||||
}
|
||||
is ChatInfo.Local ->
|
||||
ShareListNavLinkLayout(
|
||||
chatLinkPreview = { SharePreviewView(chat) },
|
||||
chatLinkPreview = { SharePreviewView(chat, disabled = false) },
|
||||
click = { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) },
|
||||
stopped
|
||||
)
|
||||
@@ -40,6 +65,13 @@ fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showForwardProhibitedByPrefAlert() {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.cannot_share_message_alert_title),
|
||||
text = generalGetString(MR.strings.cannot_share_message_alert_text),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShareListNavLinkLayout(
|
||||
chatLinkPreview: @Composable () -> Unit,
|
||||
@@ -53,7 +85,7 @@ private fun ShareListNavLinkLayout(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SharePreviewView(chat: Chat) {
|
||||
private fun SharePreviewView(chat: Chat, disabled: Boolean) {
|
||||
Row(
|
||||
Modifier.fillMaxSize(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -70,7 +102,7 @@ private fun SharePreviewView(chat: Chat) {
|
||||
}
|
||||
Text(
|
||||
chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
color = if (chat.chatInfo.incognito) Indigo else Color.Unspecified
|
||||
color = if (disabled) MaterialTheme.colors.secondary else if (chat.chatInfo.incognito) Indigo else Color.Unspecified
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+51
-3
@@ -31,13 +31,44 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
|
||||
scaffoldState = scaffoldState,
|
||||
topBar = { Column { ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } } },
|
||||
) {
|
||||
val sharedContent = chatModel.sharedContent.value
|
||||
var isMediaOrFileAttachment = false
|
||||
var isVoice = false
|
||||
var hasSimplexLink = false
|
||||
when (sharedContent) {
|
||||
is SharedContent.Text ->
|
||||
hasSimplexLink = hasSimplexLink(sharedContent.text)
|
||||
is SharedContent.Media -> {
|
||||
isMediaOrFileAttachment = true
|
||||
hasSimplexLink = hasSimplexLink(sharedContent.text)
|
||||
}
|
||||
is SharedContent.File -> {
|
||||
isMediaOrFileAttachment = true
|
||||
hasSimplexLink = hasSimplexLink(sharedContent.text)
|
||||
}
|
||||
is SharedContent.Forward -> {
|
||||
val mc = sharedContent.chatItem.content.msgContent
|
||||
if (mc != null) {
|
||||
isMediaOrFileAttachment = mc.isMediaOrFileAttachment
|
||||
isVoice = mc.isVoice
|
||||
hasSimplexLink = hasSimplexLink(mc.text)
|
||||
}
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
Box(Modifier.padding(it)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
if (chatModel.chats.isNotEmpty()) {
|
||||
ShareList(chatModel, search = searchInList)
|
||||
ShareList(
|
||||
chatModel,
|
||||
search = searchInList,
|
||||
isMediaOrFileAttachment = isMediaOrFileAttachment,
|
||||
isVoice = isVoice,
|
||||
hasSimplexLink = hasSimplexLink
|
||||
)
|
||||
} else {
|
||||
EmptyList()
|
||||
}
|
||||
@@ -54,6 +85,11 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasSimplexLink(msg: String): Boolean {
|
||||
val parsedMsg = parseToMarkdown(msg) ?: return false
|
||||
return parsedMsg.any { ft -> ft.format is Format.SimplexLink }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyList() {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
@@ -141,7 +177,13 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShareList(chatModel: ChatModel, search: String) {
|
||||
private fun ShareList(
|
||||
chatModel: ChatModel,
|
||||
search: String,
|
||||
isMediaOrFileAttachment: Boolean,
|
||||
isVoice: Boolean,
|
||||
hasSimplexLink: Boolean
|
||||
) {
|
||||
val chats by remember(search) {
|
||||
derivedStateOf {
|
||||
val sorted = chatModel.chats.toList().sortedByDescending { it.chatInfo is ChatInfo.Local }
|
||||
@@ -156,7 +198,13 @@ private fun ShareList(chatModel: ChatModel, search: String) {
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
items(chats) { chat ->
|
||||
ShareListNavLinkView(chat, chatModel)
|
||||
ShareListNavLinkView(
|
||||
chat,
|
||||
chatModel,
|
||||
isMediaOrFileAttachment = isMediaOrFileAttachment,
|
||||
isVoice = isVoice,
|
||||
hasSimplexLink = hasSimplexLink
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +358,8 @@
|
||||
<string name="share_image">Share media…</string>
|
||||
<string name="share_file">Share file…</string>
|
||||
<string name="forward_message">Forward message…</string>
|
||||
<string name="cannot_share_message_alert_title">Cannot send message</string>
|
||||
<string name="cannot_share_message_alert_text">Selected chat preferences prohibit this message.</string>
|
||||
|
||||
<!-- ComposeView.kt, helpers -->
|
||||
<string name="attach">Attach</string>
|
||||
|
||||
@@ -26,11 +26,11 @@ android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
kotlin.jvm.target=11
|
||||
|
||||
android.version_name=5.8
|
||||
android.version_code=219
|
||||
android.version_name=5.8.1
|
||||
android.version_code=221
|
||||
|
||||
desktop.version_name=5.8
|
||||
desktop.version_code=53
|
||||
desktop.version_name=5.8.1
|
||||
desktop.version_code=54
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
|
||||
Reference in New Issue
Block a user