Merge branch 'master' into sq/web-glossary-fix

This commit is contained in:
M Sarmad Qadeer 2024-06-21 01:11:49 +05:00
commit 912d35b61a
53 changed files with 549 additions and 250 deletions

View file

@ -1957,12 +1957,28 @@ func processReceivedMsg(_ res: ChatResponse) async {
let state = UIRemoteCtrlSessionState.connected(remoteCtrl: remoteCtrl, sessionCode: m.remoteCtrlSession?.sessionCode ?? "") let state = UIRemoteCtrlSessionState.connected(remoteCtrl: remoteCtrl, sessionCode: m.remoteCtrlSession?.sessionCode ?? "")
m.remoteCtrlSession = m.remoteCtrlSession?.updateState(state) m.remoteCtrlSession = m.remoteCtrlSession?.updateState(state)
} }
case .remoteCtrlStopped: case let .remoteCtrlStopped(_, rcStopReason):
// This delay is needed to cancel the session that fails on network failure, // This delay is needed to cancel the session that fails on network failure,
// e.g. when user did not grant permission to access local network yet. // e.g. when user did not grant permission to access local network yet.
if let sess = m.remoteCtrlSession { if let sess = m.remoteCtrlSession {
await MainActor.run { await MainActor.run {
m.remoteCtrlSession = nil m.remoteCtrlSession = nil
dismissAllSheets() {
switch rcStopReason {
case .connectionFailed(.errorAgent(.RCP(.identity))):
AlertManager.shared.showAlertMsg(
title: "Connection with desktop stopped",
message: "This link was used with another mobile device, please create a new link on the desktop."
)
default:
AlertManager.shared.showAlert(Alert(
title: Text("Connection with desktop stopped"),
message: Text("Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers."),
primaryButton: .default(Text("Ok")),
secondaryButton: .default(Text("Copy error")) { UIPasteboard.general.string = String(describing: rcStopReason) }
))
}
}
} }
if case .connected = sess.sessionState { if case .connected = sess.sessionState {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {

View file

@ -19,6 +19,9 @@ struct ChatItemForwardingView: View {
@State private var searchText: String = "" @State private var searchText: String = ""
@FocusState private var searchFocused @FocusState private var searchFocused
@State private var alert: SomeAlert?
@State private var hasSimplexLink_: Bool?
private let chatsToForwardTo = filterChatsToForwardTo()
var body: some View { var body: some View {
NavigationView { NavigationView {
@ -35,47 +38,29 @@ struct ChatItemForwardingView: View {
} }
} }
} }
.alert(item: $alert) { $0.alert }
} }
@ViewBuilder private func forwardListView() -> some View { @ViewBuilder private func forwardListView() -> some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
let chatsToForwardTo = filterChatsToForwardTo()
if !chatsToForwardTo.isEmpty { if !chatsToForwardTo.isEmpty {
ScrollView { List {
LazyVStack(alignment: .leading, spacing: 8) { searchFieldView(text: $searchText, focussed: $searchFocused)
searchFieldView(text: $searchText, focussed: $searchFocused) .padding(.leading, 2)
.padding(.leading, 2) let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) }
let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { filterChatSearched($0, s) } ForEach(chats) { chat in
ForEach(chats) { chat in forwardListChatView(chat)
Divider() .disabled(chatModel.deletedChats.contains(chat.chatInfo.id))
forwardListNavLinkView(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 { } else {
emptyList() emptyList()
} }
} }
} }
private func filterChatsToForwardTo() -> [Chat] { private func foundChat(_ chat: Chat, _ searchStr: String) -> Bool {
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 {
let cInfo = chat.chatInfo let cInfo = chat.chatInfo
return switch cInfo { return switch cInfo {
case let .direct(contact): case let .direct(contact):
@ -91,42 +76,70 @@ struct ChatItemForwardingView: View {
} }
} }
private func canForwardToChat(_ chat: Chat) -> Bool { private func prohibitedByPref(_ chat: Chat) -> Bool {
switch chat.chatInfo { // preference checks should match checks in compose view
case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
case let .group(groupInfo): groupInfo.sendMsgEnabled let fileProhibited = (ci.content.msgContent?.isMediaOrFileAttachment ?? false) && !chat.groupFeatureEnabled(.files)
case let .local(noteFolder): noteFolder.sendMsgEnabled 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 .contactRequest: false
case .contactConnection: false case .contactConnection: false
case .invalidJSON: 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 { private func emptyList() -> some View {
Text("No filtered chats") Text("No filtered chats")
.foregroundColor(.secondary) .foregroundColor(.secondary)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }
@ViewBuilder private func forwardListNavLinkView(_ chat: Chat) -> some View { @ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View {
let prohibited = prohibitedByPref(chat)
Button { Button {
dismiss() if prohibited {
if chat.id == fromChatInfo.id { alert = SomeAlert(
composeState = ComposeState( alert: mkAlert(
message: composeState.message, title: "Cannot forward message",
preview: composeState.linkPreview != nil ? composeState.preview : .noPreview, message: "Selected chat preferences prohibit this message."
contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo) ),
) id: "forward prohibited by preferences"
)
} else { } else {
composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo) dismiss()
chatModel.chatId = chat.id 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: { } label: {
HStack { HStack {
ChatInfoImage(chat: chat, size: 30) ChatInfoImage(chat: chat, size: 30)
.padding(.trailing, 2) .padding(.trailing, 2)
Text(chat.chatInfo.chatViewName) Text(chat.chatInfo.chatViewName)
.foregroundColor(.primary) .foregroundColor(prohibited ? .secondary : .primary)
.lineLimit(1) .lineLimit(1)
if chat.chatInfo.incognito { if chat.chatInfo.incognito {
Spacer() 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 { #Preview {
ChatItemForwardingView( ChatItemForwardingView(
ci: ChatItem.getSample(1, .directSnd, .now, "hello"), ci: ChatItem.getSample(1, .directSnd, .now, "hello"),

View file

@ -286,6 +286,7 @@ struct ComposeView: View {
if chat.chatInfo.contact?.nextSendGrpInv ?? false { if chat.chatInfo.contact?.nextSendGrpInv ?? false {
ContextInvitingContactMemberView() ContextInvitingContactMemberView()
} }
// preference checks should match checks in forwarding list
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks) let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files) let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice) let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice)
@ -1065,7 +1066,7 @@ struct ComposeView: View {
} else { } else {
nil nil
} }
let simplexLink = parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false }) let simplexLink = parsedMsgHasSimplexLink(parsedMsg)
return (url, simplexLink) 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 { struct ComposeView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []) let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])

View file

@ -11,14 +11,9 @@ import SimpleXChat
import CodeScanner import CodeScanner
import AVFoundation import AVFoundation
enum SomeAlert: Identifiable { struct SomeAlert: Identifiable {
case someAlert(alert: Alert, id: String) var alert: Alert
var id: String
var id: String {
switch self {
case let .someAlert(_, id): return id
}
}
} }
private enum NewChatViewAlert: Identifiable { private enum NewChatViewAlert: Identifiable {
@ -142,8 +137,8 @@ struct NewChatView: View {
switch(a) { switch(a) {
case let .planAndConnectAlert(alert): case let .planAndConnectAlert(alert):
return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" }) return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" })
case let .newChatSomeAlert(.someAlert(alert, _)): case let .newChatSomeAlert(a):
return alert return a.alert
} }
} }
} }
@ -181,7 +176,7 @@ struct NewChatView: View {
await MainActor.run { await MainActor.run {
creatingConnReq = false creatingConnReq = false
if let apiAlert = apiAlert { 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 // showQRCodeScanner = false
connect(pastedLink) connect(pastedLink)
} else { } else {
alert = .newChatSomeAlert(alert: .someAlert( alert = .newChatSomeAlert(alert: SomeAlert(
alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."), alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."),
id: "pasteLinkView: code 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) { if strIsSimplexLink(r.string) {
connect(link) connect(link)
} else { } 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."), 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" id: "processQRCode: code is not a SimpleX link"
)) ))
} }
case let .failure(e): case let .failure(e):
logger.error("processQRCode QR code error: \(e.localizedDescription)") 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)"), alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"),
id: "processQRCode: failure" id: "processQRCode: failure"
)) ))
@ -367,11 +362,12 @@ struct ScannerInView: View {
@Binding var showQRCodeScanner: Bool @Binding var showQRCodeScanner: Bool
let processQRCode: (_ resp: Result<ScanResult, ScanError>) -> Void let processQRCode: (_ resp: Result<ScanResult, ScanError>) -> Void
@State private var cameraAuthorizationStatus: AVAuthorizationStatus? @State private var cameraAuthorizationStatus: AVAuthorizationStatus?
var scanMode: ScanMode = .continuous
var body: some View { var body: some View {
Group { Group {
if showQRCodeScanner, case .authorized = cameraAuthorizationStatus { if showQRCodeScanner, case .authorized = cameraAuthorizationStatus {
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode) CodeScannerView(codeTypes: [.qr], scanMode: scanMode, completion: processQRCode)
.aspectRatio(1, contentMode: .fit) .aspectRatio(1, contentMode: .fit)
.cornerRadius(12) .cornerRadius(12)
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
@ -436,6 +432,7 @@ struct ScannerInView: View {
} }
} }
private func linkTextView(_ link: String) -> some View { private func linkTextView(_ link: String) -> some View {
Text(link) Text(link)
.lineLimit(1) .lineLimit(1)

View file

@ -181,23 +181,27 @@ struct ConnectDesktopView: View {
} }
private func connectingDesktopView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> some View { private func connectingDesktopView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> some View {
List { ZStack {
Section("Connecting to desktop") { List {
ctrlDeviceNameText(session, rc) Section("Connecting to desktop") {
ctrlDeviceVersionText(session) ctrlDeviceNameText(session, rc)
} ctrlDeviceVersionText(session)
}
if let sessCode = session.sessionCode { if let sessCode = session.sessionCode {
Section("Session code") { Section("Session code") {
sessionCodeText(sessCode) sessionCodeText(sessCode)
}
}
Section {
disconnectButton()
} }
} }
.navigationTitle("Connecting to desktop")
Section { ProgressView().scaleEffect(2)
disconnectButton()
}
} }
.navigationTitle("Connecting to desktop")
} }
private func searchingDesktopView() -> some View { private func searchingDesktopView() -> some View {
@ -329,16 +333,10 @@ struct ConnectDesktopView: View {
} }
} }
} }
private func scanDesctopAddressView() -> some View { private func scanDesctopAddressView() -> some View {
Section("Scan QR code from desktop") { Section("Scan QR code from desktop") {
CodeScannerView(codeTypes: [.qr], scanMode: .oncePerCode, completion: processDesktopQRCode) ScannerInView(showQRCodeScanner: $showQRCodeScanner, processQRCode: processDesktopQRCode, scanMode: .oncePerCode)
.aspectRatio(1, contentMode: .fit)
.cornerRadius(12)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.padding(.horizontal)
} }
} }

View file

@ -24,11 +24,6 @@
5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; }; 5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; };
5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; }; 5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; };
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; }; 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
5C0EA13B2C0B176B00AD2E5E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1362C0B176B00AD2E5E /* libgmp.a */; };
5C0EA13C2C0B176B00AD2E5E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */; };
5C0EA13D2C0B176B00AD2E5E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1382C0B176B00AD2E5E /* libffi.a */; };
5C0EA13E2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */; };
5C0EA13F2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */; };
5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */; }; 5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */; };
5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */; }; 5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */; };
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; 5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
@ -197,6 +192,11 @@
D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; }; D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; };
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */; };
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3B2C22D78C00CBA347 /* libffi.a */; };
E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */; };
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3D2C22D78C00CBA347 /* libgmp.a */; };
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -273,11 +273,6 @@
5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; }; 5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; };
5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; }; 5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; };
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; }; 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; };
5C0EA1362C0B176B00AD2E5E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C0EA1382C0B176B00AD2E5E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a"; sourceTree = "<group>"; };
5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a"; sourceTree = "<group>"; };
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionInfo.swift; sourceTree = "<group>"; }; 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionInfo.swift; sourceTree = "<group>"; };
5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMediaView.swift; sourceTree = "<group>"; }; 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMediaView.swift; sourceTree = "<group>"; };
5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = "<group>"; }; 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = "<group>"; };
@ -492,6 +487,11 @@
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; sourceTree = "<group>"; };
E5D68D3B2C22D78C00CBA347 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; sourceTree = "<group>"; };
E5D68D3D2C22D78C00CBA347 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -529,13 +529,13 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5C0EA13C2C0B176B00AD2E5E /* libgmpxx.a in Frameworks */, E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5C0EA13B2C0B176B00AD2E5E /* libgmp.a in Frameworks */,
5C0EA13D2C0B176B00AD2E5E /* libffi.a in Frameworks */,
5C0EA13F2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5C0EA13E2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a in Frameworks */, E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */,
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */,
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */,
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -601,11 +601,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = { 5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5C0EA1382C0B176B00AD2E5E /* libffi.a */, E5D68D3B2C22D78C00CBA347 /* libffi.a */,
5C0EA1362C0B176B00AD2E5E /* libgmp.a */, E5D68D3D2C22D78C00CBA347 /* libgmp.a */,
5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */, E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */,
5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */, E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */,
5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */, E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */,
); );
path = Libraries; path = Libraries;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1552,7 +1552,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224; CURRENT_PROJECT_VERSION = 225;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@ -1577,7 +1577,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES_THIN; LLVM_LTO = YES_THIN;
MARKETING_VERSION = 5.8; MARKETING_VERSION = 5.8.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -1601,7 +1601,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224; CURRENT_PROJECT_VERSION = 225;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@ -1626,7 +1626,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.8; MARKETING_VERSION = 5.8.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -1687,7 +1687,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224; CURRENT_PROJECT_VERSION = 225;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s; GCC_OPTIMIZATION_LEVEL = s;
@ -1702,7 +1702,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.8; MARKETING_VERSION = 5.8.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1724,7 +1724,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224; CURRENT_PROJECT_VERSION = 225;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO; ENABLE_CODE_COVERAGE = NO;
@ -1739,7 +1739,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.8; MARKETING_VERSION = 5.8.1;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1761,7 +1761,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224; CURRENT_PROJECT_VERSION = 225;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@ -1787,7 +1787,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.8; MARKETING_VERSION = 5.8.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -1812,7 +1812,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 224; CURRENT_PROJECT_VERSION = 225;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@ -1838,7 +1838,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 5.8; MARKETING_VERSION = 5.8.1;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;

View file

@ -980,7 +980,7 @@ public enum ChatResponse: Decodable, Error {
case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)" case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)"
case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)" case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)"
case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl)
case .remoteCtrlStopped: return noDetails case let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))"
case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)")
case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))" case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))"
case .cmdOk: return noDetails case .cmdOk: return noDetails

View file

@ -3438,6 +3438,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 { var cmdString: String {
"json \(encodeJSON(self))" "json \(encodeJSON(self))"
} }

View file

@ -2925,6 +2925,20 @@ sealed class MsgContent {
@Serializable(with = MsgContentSerializer::class) class MCFile(override val text: String): 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() @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() = val cmdString: String get() =
if (this is MCUnknown) "json $json" else "json ${json.encodeToString(this)}" if (this is MCUnknown) "json $json" else "json ${json.encodeToString(this)}"
} }

View file

@ -1,9 +1,18 @@
package chat.simplex.common.model package chat.simplex.common.model
import SectionItemView
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.getNetCfg
import chat.simplex.common.model.ChatController.setNetCfg import chat.simplex.common.model.ChatController.setNetCfg
import chat.simplex.common.model.ChatModel.updatingChatsMutex import chat.simplex.common.model.ChatModel.updatingChatsMutex
@ -12,7 +21,6 @@ import dev.icerock.moko.resources.compose.painterResource
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.* import chat.simplex.common.views.call.*
import chat.simplex.common.views.chat.group.toggleShowMemberMessages
import chat.simplex.common.views.migration.MigrationFileLinkData import chat.simplex.common.views.migration.MigrationFileLinkData
import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.common.views.usersettings.* import chat.simplex.common.views.usersettings.*
@ -20,6 +28,7 @@ import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration import com.charleskorn.kaml.YamlConfiguration
import chat.simplex.res.MR import chat.simplex.res.MR
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@ -2194,15 +2203,43 @@ object ChatController {
val sess = chatModel.remoteCtrlSession.value val sess = chatModel.remoteCtrlSession.value
if (sess != null) { if (sess != null) {
chatModel.remoteCtrlSession.value = null chatModel.remoteCtrlSession.value = null
ModalManager.fullscreen.closeModals()
fun showAlert(chatError: ChatError) { fun showAlert(chatError: ChatError) {
AlertManager.shared.showAlertMsg( when {
generalGetString(MR.strings.remote_ctrl_was_disconnected_title), r.rcStopReason is RemoteCtrlStopReason.ConnectionFailed
if (chatError is ChatError.ChatErrorRemoteCtrl) { && r.rcStopReason.chatError is ChatError.ChatErrorAgent
chatError.remoteCtrlError.localizedString && r.rcStopReason.chatError.agentError is AgentErrorType.RCP
} else { && r.rcStopReason.chatError.agentError.rcpErr is RCErrorType.IDENTITY ->
generalGetString(MR.strings.remote_ctrl_disconnected_with_reason).format(chatError.string) AlertManager.shared.showAlertMsg(
} title = generalGetString(MR.strings.remote_ctrl_was_disconnected_title),
) text = generalGetString(MR.strings.remote_ctrl_connection_stopped_identity_desc)
)
else ->
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.remote_ctrl_was_disconnected_title),
text = if (chatError is ChatError.ChatErrorRemoteCtrl) {
chatError.remoteCtrlError.localizedString
} else {
generalGetString(MR.strings.remote_ctrl_connection_stopped_desc)
},
buttons = {
Column {
SectionItemView({
AlertManager.shared.hideAlert()
}) {
Text(stringResource(MR.strings.ok), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
val clipboard = LocalClipboardManager.current
SectionItemView({
clipboard.setText(AnnotatedString(json.encodeToString(r.rcStopReason)))
AlertManager.shared.hideAlert()
}) {
Text(stringResource(MR.strings.copy_error), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
}
}
)
}
} }
when (r.rcStopReason) { when (r.rcStopReason) {
is RemoteCtrlStopReason.DiscoveryFailed -> showAlert(r.rcStopReason.chatError) is RemoteCtrlStopReason.DiscoveryFailed -> showAlert(r.rcStopReason.chatError)
@ -4716,7 +4753,7 @@ sealed class CR {
(if (remoteCtrl_ == null) "null" else json.encodeToString(remoteCtrl_)) + (if (remoteCtrl_ == null) "null" else json.encodeToString(remoteCtrl_)) +
"\nsessionCode: $sessionCode" "\nsessionCode: $sessionCode"
is RemoteCtrlConnected -> json.encodeToString(remoteCtrl) is RemoteCtrlConnected -> json.encodeToString(remoteCtrl)
is RemoteCtrlStopped -> noDetails() is RemoteCtrlStopped -> "rcsState: $rcsState\nrcsStopReason: $rcStopReason"
is ContactPQAllowed -> withUser(user, "contact: ${contact.id}\npqEncryption: $pqEncryption") is ContactPQAllowed -> withUser(user, "contact: ${contact.id}\npqEncryption: $pqEncryption")
is ContactPQEnabled -> withUser(user, "contact: ${contact.id}\npqEnabled: $pqEnabled") is ContactPQEnabled -> withUser(user, "contact: ${contact.id}\npqEnabled: $pqEnabled")
is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" + is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" +

View file

@ -9,30 +9,55 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.common.views.helpers.ProfileImage
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR import chat.simplex.res.MR
@Composable @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 val stopped = chatModel.chatRunning.value == false
when (chat.chatInfo) { when (chat.chatInfo) {
is ChatInfo.Direct -> is ChatInfo.Direct -> {
val voiceProhibited = isVoice && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
ShareListNavLinkLayout( ShareListNavLinkLayout(
chatLinkPreview = { SharePreviewView(chat) }, chatLinkPreview = { SharePreviewView(chat, disabled = voiceProhibited) },
click = { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) }, click = {
if (voiceProhibited) {
showForwardProhibitedByPrefAlert()
} else {
directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel)
}
},
stopped 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( ShareListNavLinkLayout(
chatLinkPreview = { SharePreviewView(chat) }, chatLinkPreview = { SharePreviewView(chat, disabled = prohibitedByPref) },
click = { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) }, click = {
if (prohibitedByPref) {
showForwardProhibitedByPrefAlert()
} else {
groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel)
}
},
stopped stopped
) )
}
is ChatInfo.Local -> is ChatInfo.Local ->
ShareListNavLinkLayout( ShareListNavLinkLayout(
chatLinkPreview = { SharePreviewView(chat) }, chatLinkPreview = { SharePreviewView(chat, disabled = false) },
click = { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) }, click = { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) },
stopped 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 @Composable
private fun ShareListNavLinkLayout( private fun ShareListNavLinkLayout(
chatLinkPreview: @Composable () -> Unit, chatLinkPreview: @Composable () -> Unit,
@ -53,7 +85,7 @@ private fun ShareListNavLinkLayout(
} }
@Composable @Composable
private fun SharePreviewView(chat: Chat) { private fun SharePreviewView(chat: Chat, disabled: Boolean) {
Row( Row(
Modifier.fillMaxSize(), Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
@ -70,7 +102,7 @@ private fun SharePreviewView(chat: Chat) {
} }
Text( Text(
chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis, 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
) )
} }
} }

View file

@ -31,13 +31,44 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
topBar = { Column { ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } } }, 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)) { Box(Modifier.padding(it)) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { ) {
if (chatModel.chats.isNotEmpty()) { if (chatModel.chats.isNotEmpty()) {
ShareList(chatModel, search = searchInList) ShareList(
chatModel,
search = searchInList,
isMediaOrFileAttachment = isMediaOrFileAttachment,
isVoice = isVoice,
hasSimplexLink = hasSimplexLink
)
} else { } else {
EmptyList() 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 @Composable
private fun EmptyList() { private fun EmptyList() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
@ -141,7 +177,13 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
} }
@Composable @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) { val chats by remember(search) {
derivedStateOf { derivedStateOf {
val sorted = chatModel.chats.toList().sortedByDescending { it.chatInfo is ChatInfo.Local } 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() modifier = Modifier.fillMaxWidth()
) { ) {
items(chats) { chat -> items(chats) { chat ->
ShareListNavLinkView(chat, chatModel) ShareListNavLinkView(
chat,
chatModel,
isMediaOrFileAttachment = isMediaOrFileAttachment,
isVoice = isVoice,
hasSimplexLink = hasSimplexLink
)
} }
} }
} }

View file

@ -448,6 +448,9 @@ private fun stopChat(m: ChatModel, progressIndicator: MutableState<Boolean>? = n
progressIndicator?.value = true progressIndicator?.value = true
stopChatAsync(m) stopChatAsync(m)
platform.androidChatStopped() platform.androidChatStopped()
// close chat view for desktop
chatModel.chatId.value = null
ModalManager.end.closeModals()
onStop?.invoke() onStop?.invoke()
} catch (e: Error) { } catch (e: Error) {
m.chatRunning.value = true m.chatRunning.value = true

View file

@ -15,6 +15,7 @@ import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
@ -166,6 +167,24 @@ private fun ConnectingDesktop(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) {
SectionView { SectionView {
DisconnectButton(onClick = ::disconnectDesktop) DisconnectButton(onClick = ::disconnectDesktop)
} }
ProgressIndicator()
}
@Composable
private fun ProgressIndicator() {
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = MaterialTheme.colors.secondary,
strokeWidth = 3.dp
)
}
} }
@Composable @Composable

View file

@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.item.ClickableText import chat.simplex.common.views.chat.item.ClickableText
@ -58,6 +59,8 @@ fun NetworkAndServersView() {
smpProxyFallback = smpProxyFallback, smpProxyFallback = smpProxyFallback,
proxyPort = proxyPort, proxyPort = proxyPort,
toggleSocksProxy = { enable -> toggleSocksProxy = { enable ->
val def = NetCfg.defaults
val proxyDef = NetCfg.proxyDefaults
if (enable) { if (enable) {
AlertManager.shared.showAlertDialog( AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.network_enable_socks), title = generalGetString(MR.strings.network_enable_socks),
@ -65,7 +68,19 @@ fun NetworkAndServersView() {
confirmText = generalGetString(MR.strings.confirm_verb), confirmText = generalGetString(MR.strings.confirm_verb),
onConfirm = { onConfirm = {
withBGApi { withBGApi {
val conf = NetCfg.proxyDefaults.withHostPort(chatModel.controller.appPrefs.networkProxyHostPort.get()) var conf = controller.getNetCfg().withHostPort(controller.appPrefs.networkProxyHostPort.get())
if (conf.tcpConnectTimeout == def.tcpConnectTimeout) {
conf = conf.copy(tcpConnectTimeout = proxyDef.tcpConnectTimeout)
}
if (conf.tcpTimeout == def.tcpTimeout) {
conf = conf.copy(tcpTimeout = proxyDef.tcpTimeout)
}
if (conf.tcpTimeoutPerKb == def.tcpTimeoutPerKb) {
conf = conf.copy(tcpTimeoutPerKb = proxyDef.tcpTimeoutPerKb)
}
if (conf.rcvConcurrency == def.rcvConcurrency) {
conf = conf.copy(rcvConcurrency = proxyDef.rcvConcurrency)
}
chatModel.controller.apiSetNetworkConfig(conf) chatModel.controller.apiSetNetworkConfig(conf)
chatModel.controller.setNetCfg(conf) chatModel.controller.setNetCfg(conf)
networkUseSocksProxy.value = true networkUseSocksProxy.value = true
@ -80,7 +95,19 @@ fun NetworkAndServersView() {
confirmText = generalGetString(MR.strings.confirm_verb), confirmText = generalGetString(MR.strings.confirm_verb),
onConfirm = { onConfirm = {
withBGApi { withBGApi {
val conf = NetCfg.defaults var conf = controller.getNetCfg().copy(socksProxy = null)
if (conf.tcpConnectTimeout == proxyDef.tcpConnectTimeout) {
conf = conf.copy(tcpConnectTimeout = def.tcpConnectTimeout)
}
if (conf.tcpTimeout == proxyDef.tcpTimeout) {
conf = conf.copy(tcpTimeout = def.tcpTimeout)
}
if (conf.tcpTimeoutPerKb == proxyDef.tcpTimeoutPerKb) {
conf = conf.copy(tcpTimeoutPerKb = def.tcpTimeoutPerKb)
}
if (conf.rcvConcurrency == proxyDef.rcvConcurrency) {
conf = conf.copy(rcvConcurrency = def.rcvConcurrency)
}
chatModel.controller.apiSetNetworkConfig(conf) chatModel.controller.apiSetNetworkConfig(conf)
chatModel.controller.setNetCfg(conf) chatModel.controller.setNetCfg(conf)
networkUseSocksProxy.value = false networkUseSocksProxy.value = false

View file

@ -358,6 +358,8 @@
<string name="share_image">Share media…</string> <string name="share_image">Share media…</string>
<string name="share_file">Share file…</string> <string name="share_file">Share file…</string>
<string name="forward_message">Forward message…</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 --> <!-- ComposeView.kt, helpers -->
<string name="attach">Attach</string> <string name="attach">Attach</string>
@ -1921,6 +1923,9 @@
<string name="remote_ctrl_was_disconnected_title">Connection stopped</string> <string name="remote_ctrl_was_disconnected_title">Connection stopped</string>
<string name="remote_host_disconnected_from"><![CDATA[Disconnected from mobile <b>%s</b> with the reason: %s]]></string> <string name="remote_host_disconnected_from"><![CDATA[Disconnected from mobile <b>%s</b> with the reason: %s]]></string>
<string name="remote_ctrl_disconnected_with_reason">Disconnected with the reason: %s</string> <string name="remote_ctrl_disconnected_with_reason">Disconnected with the reason: %s</string>
<string name="remote_ctrl_connection_stopped_desc">Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers.</string>
<string name="remote_ctrl_connection_stopped_identity_desc">This link was used with another mobile device, please create a new link on the desktop.</string>
<string name="copy_error">Copy error</string>
<string name="disconnect_desktop_question">Disconnect desktop?</string> <string name="disconnect_desktop_question">Disconnect desktop?</string>
<string name="only_one_device_can_work_at_the_same_time">Only one device can work at the same time</string> <string name="only_one_device_can_work_at_the_same_time">Only one device can work at the same time</string>
<string name="open_on_mobile_and_scan_qr_code"><![CDATA[Open <i>Use from desktop</i> in mobile app and scan QR code.]]></string> <string name="open_on_mobile_and_scan_qr_code"><![CDATA[Open <i>Use from desktop</i> in mobile app and scan QR code.]]></string>

View file

@ -14,8 +14,10 @@ import com.russhwolf.settings.*
import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.desc.desc import dev.icerock.moko.resources.desc.desc
import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.concurrent.Executors
@Composable @Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font = actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font =
@ -59,8 +61,11 @@ private val settingsThemesProps =
Properties() Properties()
.also { props -> try { settingsThemesFile.reader().use { props.load(it) } } catch (e: Exception) { /**/ } } .also { props -> try { settingsThemesFile.reader().use { props.load(it) } } catch (e: Exception) { /**/ } }
actual val settings: Settings = PropertiesSettings(settingsProps) { withApi { settingsFile.writer().use { settingsProps.store(it, "") } } }
actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) { withApi { settingsThemesFile.writer().use { settingsThemesProps.store(it, "") } } } private val settingsWriterThread = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
actual val settings: Settings = PropertiesSettings(settingsProps) { CoroutineScope(settingsWriterThread).launch { settingsFile.writer().use { settingsProps.store(it, "") } } }
actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) { CoroutineScope(settingsWriterThread).launch { settingsThemesFile.writer().use { settingsThemesProps.store(it, "") } } }
actual fun windowOrientation(): WindowOrientation = actual fun windowOrientation(): WindowOrientation =
if (simplexWindowState.windowState.size.width > simplexWindowState.windowState.size.height) { if (simplexWindowState.windowState.size.width > simplexWindowState.windowState.size.height) {

View file

@ -26,11 +26,11 @@ android.enableJetifier=true
kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11 kotlin.jvm.target=11
android.version_name=5.8 android.version_name=5.8.1
android.version_code=219 android.version_code=221
desktop.version_name=5.8 desktop.version_name=5.8.1
desktop.version_code=53 desktop.version_code=54
kotlin.version=1.9.23 kotlin.version=1.9.23
gradle.plugin.version=8.2.0 gradle.plugin.version=8.2.0

View file

@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: 3c0cd7efcc3d3058d940c7a9667faef2dc6de6cc tag: 8a3b72458f917e9867f4e3640dda0fa1827ff6cf
source-repository-package source-repository-package
type: git type: git

View file

@ -7,7 +7,7 @@ revision: 11.02.2024
| Updated 23.03.2024 | Languages: EN | | Updated 23.03.2024 | Languages: EN |
# Download SimpleX apps # Download SimpleX apps
The latest stable version is v5.6. The latest stable version is v5.8.
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases). You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).

View file

@ -331,7 +331,7 @@ disconnect: off
[WEB] [WEB]
# Set path to generate static mini-site for server information and qr codes/links # Set path to generate static mini-site for server information and qr codes/links
static_path: <WRITABLE_PATH_TO_STORE_WEBSITE> static_path: /var/opt/simplex/www
# Run an embedded server on this port # Run an embedded server on this port
# Onion sites can use any port and register it in the hidden service config. # Onion sites can use any port and register it in the hidden service config.
@ -601,13 +601,7 @@ SMP-server versions starting from `v5.8.0-beta.0` can be configured to PROXY smp
SMP-server versions starting from `v5.8.0` can be configured to serve Web page with server information that can include admin info, server info, provider info, etc. Run the following commands as `root` user. SMP-server versions starting from `v5.8.0` can be configured to serve Web page with server information that can include admin info, server info, provider info, etc. Run the following commands as `root` user.
1. Create folder to store webserver static files and assign correct permissions: 1. Add the following to your smp-server configuration (please modify fields in [INFORMATION] section to include relevant information):
```sh
mkdir -p /var/www/smp-server-web && chown smp:smp /var/www/smp-server-web
```
2. Add the following to your smp-server configuration (please modify fields in [INFORMATION] section to include relevant information):
```sh ```sh
vim /etc/opt/simplex/smp-server.ini vim /etc/opt/simplex/smp-server.ini
@ -615,7 +609,7 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w
```ini ```ini
[WEB] [WEB]
static_path: /var/www/smp-server-web static_path: /var/opt/simplex/www
[INFORMATION] [INFORMATION]
# AGPLv3 license requires that you make any source code modifications # AGPLv3 license requires that you make any source code modifications
@ -656,7 +650,7 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w
hosting_country: <HOSTING_PROVIDER_LOCATION> hosting_country: <HOSTING_PROVIDER_LOCATION>
``` ```
3. Install the webserver. For easy deployment we'll describe the installtion process of [Caddy](https://caddyserver.com) webserver on Ubuntu server: 2. Install the webserver. For easy deployment we'll describe the installtion process of [Caddy](https://caddyserver.com) webserver on Ubuntu server:
1. Install the packages: 1. Install the packages:
@ -684,7 +678,7 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w
[Full Caddy instllation instructions](https://caddyserver.com/docs/install) [Full Caddy instllation instructions](https://caddyserver.com/docs/install)
4. Replace Caddy configuration with the following (don't forget to replace `<YOUR_DOMAIN>`): 3. Replace Caddy configuration with the following (don't forget to replace `<YOUR_DOMAIN>`):
```sh ```sh
vim /etc/caddy/Caddyfile vim /etc/caddy/Caddyfile
@ -692,20 +686,20 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w
```caddy ```caddy
<YOUR_DOMAIN> { <YOUR_DOMAIN> {
root * /var/www/simplex root * /var/opt/simplex/www
file_server file_server
} }
``` ```
5. Enable and start Caddy service: 4. Enable and start Caddy service:
```sh ```sh
systemctl enable --now caddy systemctl enable --now caddy
``` ```
6. Upgrade your smp-server to latest version - [Updating your smp server](#updating-your-smp-server) 5. Upgrade your smp-server to latest version - [Updating your smp server](#updating-your-smp-server)
7. Access the webpage you've deployed from your browser. You should see the smp-server information that you've provided in your ini file. 6. Access the webpage you've deployed from your browser. You should see the smp-server information that you've provided in your ini file.
## Documentation ## Documentation

View file

@ -1,5 +1,5 @@
name: simplex-chat name: simplex-chat
version: 5.8.0.5 version: 5.8.1.0
#synopsis: #synopsis:
#description: #description:
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme

View file

@ -47,7 +47,7 @@ for ORIG_NAME in "${ORIG_NAMES[@]}"; do
#(cd apk && 7z a -r -mx=0 -tzip ../$ORIG_NAME resources.arsc) #(cd apk && 7z a -r -mx=0 -tzip ../$ORIG_NAME resources.arsc)
ALL_TOOLS=("$sdk_dir"/build-tools/*/) ALL_TOOLS=("$sdk_dir"/build-tools/*/)
BIN_DIR="${ALL_TOOLS[1]}" BIN_DIR="${ALL_TOOLS[${#ALL_TOOLS[@]}-1]}"
"$BIN_DIR"/zipalign -p -f 4 "$ORIG_NAME" "$ORIG_NAME"-2 "$BIN_DIR"/zipalign -p -f 4 "$ORIG_NAME" "$ORIG_NAME"-2

View file

@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."3c0cd7efcc3d3058d940c7a9667faef2dc6de6cc" = "09fx6bj5f25v6a34lkfggj3a1yqrg1xz9fv0dg9vb87pcajhkrq0"; "https://github.com/simplex-chat/simplexmq.git"."8a3b72458f917e9867f4e3640dda0fa1827ff6cf" = "1mmxdaj563kjmlkacxdnq62n6mzw9khampzaqghnk6iiwzdig0qy";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";

View file

@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: simplex-chat name: simplex-chat
version: 5.8.0.5 version: 5.8.1.0
category: Web, System, Services, Cryptography category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat author: simplex.chat

View file

@ -104,7 +104,7 @@ import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), Migrati
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
import Simplex.Messaging.Client (ProxyClientError (..), NetworkConfig (..), defaultNetworkConfig) import Simplex.Messaging.Client (NetworkConfig (..), ProxyClientError (..), defaultNetworkConfig)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import qualified Simplex.Messaging.Crypto.File as CF import qualified Simplex.Messaging.Crypto.File as CF
@ -218,11 +218,12 @@ newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Boo
newChatController newChatController
ChatDatabase {chatStore, agentStore} ChatDatabase {chatStore, agentStore}
user user
cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, deviceNameForRemote} cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, deviceNameForRemote, confirmMigrations}
ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, simpleNetCfg, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, deviceName, optFilesFolder, optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize} ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, simpleNetCfg, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable, yesToUpMigrations}, deviceName, optFilesFolder, optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize}
backgroundMode = do backgroundMode = do
let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False} let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False}
config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable} confirmMigrations' = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations
config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable, confirmMigrations = confirmMigrations'}
firstTime = dbNew chatStore firstTime = dbNew chatStore
currentUser <- newTVarIO user currentUser <- newTVarIO user
currentRemoteHost <- newTVarIO Nothing currentRemoteHost <- newTVarIO Nothing
@ -762,28 +763,31 @@ processChatCommand' vr = \case
_ -> throwChatError CEInvalidChatItemUpdate _ -> throwChatError CEInvalidChatItemUpdate
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
CTGroup -> withGroupLock "updateChatItem" chatId $ do CTGroup -> withGroupLock "updateChatItem" chatId $ do
Group gInfo@GroupInfo {groupId} ms <- withStore $ \db -> getGroup db vr user chatId Group gInfo@GroupInfo {groupId, membership} ms <- withStore $ \db -> getGroup db vr user chatId
assertUserGroupRole gInfo GRAuthor assertUserGroupRole gInfo GRAuthor
cci <- withStore $ \db -> getGroupCIWithReactions db user gInfo itemId if prohibitedSimplexLinks gInfo membership mc
case cci of then pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText GFSimplexLinks))
CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable}, content = ciContent} -> do else do
case (ciContent, itemSharedMsgId, editable) of cci <- withStore $ \db -> getGroupCIWithReactions db user gInfo itemId
(CISndMsgContent oldMC, Just itemSharedMId, True) -> do case cci of
let changed = mc /= oldMC CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable}, content = ciContent} -> do
if changed || fromMaybe False itemLive case (ciContent, itemSharedMsgId, editable) of
then do (CISndMsgContent oldMC, Just itemSharedMId, True) -> do
(SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) let changed = mc /= oldMC
ci' <- withStore' $ \db -> do if changed || fromMaybe False itemLive
currentTs <- liftIO getCurrentTime then do
when changed $ (SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive))
addInitialAndNewCIVersions db itemId (chatItemTs' ci, oldMC) (currentTs, mc) ci' <- withStore' $ \db -> do
let edited = itemLive /= Just True currentTs <- liftIO getCurrentTime
updateGroupChatItem db user groupId ci (CISndMsgContent mc) edited live $ Just msgId when changed $
startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci' addInitialAndNewCIVersions db itemId (chatItemTs' ci, oldMC) (currentTs, mc)
pure $ CRChatItemUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci') let edited = itemLive /= Just True
else pure $ CRChatItemNotChanged user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) updateGroupChatItem db user groupId ci (CISndMsgContent mc) edited live $ Just msgId
_ -> throwChatError CEInvalidChatItemUpdate startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci'
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate pure $ CRChatItemUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci')
else pure $ CRChatItemNotChanged user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci)
_ -> throwChatError CEInvalidChatItemUpdate
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
CTLocal -> do CTLocal -> do
(nf@NoteFolder {noteFolderId}, cci) <- withStore $ \db -> (,) <$> getNoteFolder db user chatId <*> getLocalChatItem db user chatId itemId (nf@NoteFolder {noteFolderId}, cci) <- withStore $ \db -> (,) <$> getNoteFolder db user chatId <*> getLocalChatItem db user chatId itemId
case cci of case cci of
@ -1357,6 +1361,9 @@ processChatCommand' vr = \case
pure $ CRNetworkConfig cfg pure $ CRNetworkConfig cfg
APISetNetworkInfo info -> lift (withAgent' (`setUserNetworkInfo` info)) >> ok_ APISetNetworkInfo info -> lift (withAgent' (`setUserNetworkInfo` info)) >> ok_
ReconnectAllServers -> withUser' $ \_ -> lift (withAgent' reconnectAllServers) >> ok_ ReconnectAllServers -> withUser' $ \_ -> lift (withAgent' reconnectAllServers) >> ok_
ReconnectServer userId srv -> withUserId userId $ \user -> do
lift (withAgent' $ \a -> reconnectSMPServer a (aUserId user) srv)
ok_
APISetChatSettings (ChatRef cType chatId) chatSettings -> withUser $ \user -> case cType of APISetChatSettings (ChatRef cType chatId) chatSettings -> withUser $ \user -> case cType of
CTDirect -> do CTDirect -> do
ct <- withStore $ \db -> do ct <- withStore $ \db -> do
@ -2918,9 +2925,17 @@ prohibitedGroupContent :: GroupInfo -> GroupMember -> MsgContent -> Maybe f -> M
prohibitedGroupContent gInfo m mc file_ prohibitedGroupContent gInfo m mc file_
| isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice | isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice
| not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles | not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles
| not (groupFeatureMemberAllowed SGFSimplexLinks m gInfo) && containsFormat isSimplexLink (parseMarkdown $ msgContentText mc) = Just GFSimplexLinks | prohibitedSimplexLinks gInfo m mc = Just GFSimplexLinks
| otherwise = Nothing | otherwise = Nothing
prohibitedSimplexLinks :: GroupInfo -> GroupMember -> MsgContent -> Bool
prohibitedSimplexLinks gInfo m mc =
not (groupFeatureMemberAllowed SGFSimplexLinks m gInfo)
&& maybe False (any ftIsSimplexLink) (parseMaybeMarkdownList $ msgContentText mc)
where
ftIsSimplexLink :: FormattedText -> Bool
ftIsSimplexLink FormattedText {format} = maybe False isSimplexLink format
roundedFDCount :: Int -> Int roundedFDCount :: Int -> Int
roundedFDCount n roundedFDCount n
| n <= 0 = 4 | n <= 0 = 4
@ -3214,7 +3229,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete}
forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci
throwChatError $ CEFileNotApproved fileId unknownSrvs throwChatError $ CEFileNotApproved fileId unknownSrvs
getNetworkConfig :: CM' NetworkConfig getNetworkConfig :: CM' NetworkConfig
getNetworkConfig = withAgent' $ liftIO . getNetworkConfig' getNetworkConfig = withAgent' $ liftIO . getNetworkConfig'
resetRcvCIFileStatus :: User -> FileTransferId -> CIFileStatus 'MDRcv -> CM (Maybe AChatItem) resetRcvCIFileStatus :: User -> FileTransferId -> CIFileStatus 'MDRcv -> CM (Maybe AChatItem)
@ -5250,18 +5265,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
groupMsgToView gInfo ci' {reactions} groupMsgToView gInfo ci' {reactions}
groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> CM () groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> CM ()
groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc msg@RcvMessage {msgId} brokerTs ttl_ live_ = groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc msg@RcvMessage {msgId} brokerTs ttl_ live_
updateRcvChatItem `catchCINotFound` \_ -> do | prohibitedSimplexLinks gInfo m mc =
-- This patches initial sharedMsgId into chat item when locally deleted chat item messageWarning $ "x.msg.update ignored: feature not allowed " <> groupFeatureNameText GFSimplexLinks
-- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete). | otherwise = do
-- Chat item and update message which created it will have different sharedMsgId in this case... updateRcvChatItem `catchCINotFound` \_ -> do
let timed_ = rcvGroupCITimed gInfo ttl_ -- This patches initial sharedMsgId into chat item when locally deleted chat item
ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg (Just sharedMsgId) brokerTs content Nothing timed_ live -- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete).
ci' <- withStore' $ \db -> do -- Chat item and update message which created it will have different sharedMsgId in this case...
createChatItemVersion db (chatItemId' ci) brokerTs mc let timed_ = rcvGroupCITimed gInfo ttl_
ci' <- updateGroupChatItem db user groupId ci content True live Nothing ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg (Just sharedMsgId) brokerTs content Nothing timed_ live
blockedMember m ci' $ markGroupChatItemBlocked db user gInfo ci' ci' <- withStore' $ \db -> do
toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci') createChatItemVersion db (chatItemId' ci) brokerTs mc
ci' <- updateGroupChatItem db user groupId ci content True live Nothing
blockedMember m ci' $ markGroupChatItemBlocked db user gInfo ci'
toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci')
where where
content = CIRcvMsgContent mc content = CIRcvMsgContent mc
live = fromMaybe False live_ live = fromMaybe False live_
@ -7410,6 +7428,7 @@ chatCommandP =
"/_network " *> (APISetNetworkConfig <$> jsonP), "/_network " *> (APISetNetworkConfig <$> jsonP),
("/network " <|> "/net ") *> (SetNetworkConfig <$> netCfgP), ("/network " <|> "/net ") *> (SetNetworkConfig <$> netCfgP),
("/network" <|> "/net") $> APIGetNetworkConfig, ("/network" <|> "/net") $> APIGetNetworkConfig,
"/reconnect " *> (ReconnectServer <$> A.decimal <* A.space <*> strP),
"/reconnect" $> ReconnectAllServers, "/reconnect" $> ReconnectAllServers,
"/_settings " *> (APISetChatSettings <$> chatRefP <* A.space <*> jsonP), "/_settings " *> (APISetChatSettings <$> chatRefP <* A.space <*> jsonP),
"/_member settings #" *> (APISetMemberSettings <$> A.decimal <* A.space <*> A.decimal <* A.space <*> jsonP), "/_member settings #" *> (APISetMemberSettings <$> A.decimal <* A.space <*> A.decimal <* A.space <*> jsonP),

View file

@ -355,6 +355,7 @@ data ChatCommand
| SetNetworkConfig SimpleNetCfg | SetNetworkConfig SimpleNetCfg
| APISetNetworkInfo UserNetworkInfo | APISetNetworkInfo UserNetworkInfo
| ReconnectAllServers | ReconnectAllServers
| ReconnectServer UserId SMPServer
| APISetChatSettings ChatRef ChatSettings | APISetChatSettings ChatRef ChatSettings
| APISetMemberSettings GroupId GroupMemberId GroupMemberSettings | APISetMemberSettings GroupId GroupMemberId GroupMemberSettings
| APIContactInfo ContactId | APIContactInfo ContactId

View file

@ -144,10 +144,6 @@ markdownToList (m1 :|: m2) = markdownToList m1 <> markdownToList m2
parseMarkdown :: Text -> Markdown parseMarkdown :: Text -> Markdown
parseMarkdown s = fromRight (unmarked s) $ A.parseOnly (markdownP <* A.endOfInput) s parseMarkdown s = fromRight (unmarked s) $ A.parseOnly (markdownP <* A.endOfInput) s
containsFormat :: (Format -> Bool) -> Markdown -> Bool
containsFormat p (Markdown f _) = maybe False p f
containsFormat p (m1 :|: m2) = containsFormat p m1 || containsFormat p m2
isSimplexLink :: Format -> Bool isSimplexLink :: Format -> Bool
isSimplexLink = \case isSimplexLink = \case
SimplexLink {} -> True; SimplexLink {} -> True;

View file

@ -198,7 +198,8 @@ mobileChatOpts dbFilePrefix =
logAgent = Nothing, logAgent = Nothing,
logFile = Nothing, logFile = Nothing,
tbqSize = 1024, tbqSize = 1024,
highlyAvailable = False highlyAvailable = False,
yesToUpMigrations = False
}, },
deviceName = Nothing, deviceName = Nothing,
chatCmd = "", chatCmd = "",

View file

@ -62,7 +62,8 @@ data CoreChatOpts = CoreChatOpts
logAgent :: Maybe LogLevel, logAgent :: Maybe LogLevel,
logFile :: Maybe FilePath, logFile :: Maybe FilePath,
tbqSize :: Natural, tbqSize :: Natural,
highlyAvailable :: Bool highlyAvailable :: Bool,
yesToUpMigrations :: Bool
} }
data ChatCmdLog = CCLAll | CCLMessages | CCLNone data ChatCmdLog = CCLAll | CCLMessages | CCLNone
@ -204,6 +205,12 @@ coreChatOptsP appDir defaultDbFileName = do
( long "ha" ( long "ha"
<> help "Run as a highly available client (this may increase traffic in groups)" <> help "Run as a highly available client (this may increase traffic in groups)"
) )
yesToUpMigrations <-
switch
( long "--yes-migrate"
<> short 'y'
<> help "Automatically confirm \"up\" database migrations"
)
pure pure
CoreChatOpts CoreChatOpts
{ dbFilePrefix, { dbFilePrefix,
@ -217,7 +224,8 @@ coreChatOptsP appDir defaultDbFileName = do
logAgent = if logAgent || logLevel == CLLDebug then Just $ agentLogLevel logLevel else Nothing, logAgent = if logAgent || logLevel == CLLDebug then Just $ agentLogLevel logLevel else Nothing,
logFile, logFile,
tbqSize, tbqSize,
highlyAvailable highlyAvailable,
yesToUpMigrations
} }
where where
useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p

View file

@ -54,8 +54,9 @@ import qualified Simplex.FileTransfer.Transport as XFTP
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..)) import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..))
import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..))
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP))
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
import Simplex.Messaging.Client (SMPProxyMode (..), SMPProxyFallback) import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import qualified Simplex.Messaging.Crypto.Ratchet as CR import qualified Simplex.Messaging.Crypto.Ratchet as CR
@ -67,7 +68,7 @@ import qualified Simplex.Messaging.Protocol as SMP
import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Transport.Client (TransportHost (..))
import Simplex.Messaging.Util (safeDecodeUtf8, tshow) import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
import Simplex.Messaging.Version hiding (version) import Simplex.Messaging.Version hiding (version)
import Simplex.RemoteControl.Types (RCCtrlAddress (..)) import Simplex.RemoteControl.Types (RCCtrlAddress (..), RCErrorType (..))
import System.Console.ANSI.Types import System.Console.ANSI.Types
type CurrentTime = UTCTime type CurrentTime = UTCTime
@ -350,7 +351,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
] ]
CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, ctrlDeviceName} -> CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, ctrlDeviceName} ->
["remote controller " <> sShow rcId <> " session started with " <> plain ctrlDeviceName] ["remote controller " <> sShow rcId <> " session started with " <> plain ctrlDeviceName]
CRRemoteCtrlStopped {} -> ["remote controller stopped"] CRRemoteCtrlStopped {rcStopReason} -> viewRemoteCtrlStopped rcStopReason
CRContactPQEnabled u c (CR.PQEncryption pqOn) -> ttyUser u [ttyContact' c <> ": " <> (if pqOn then "quantum resistant" else "standard") <> " end-to-end encryption enabled"] CRContactPQEnabled u c (CR.PQEncryption pqOn) -> ttyUser u [ttyContact' c <> ": " <> (if pqOn then "quantum resistant" else "standard") <> " end-to-end encryption enabled"]
CRSQLResult rows -> map plain rows CRSQLResult rows -> map plain rows
CRSlowSQLQueries {chatQueries, agentQueries} -> CRSlowSQLQueries {chatQueries, agentQueries} ->
@ -1843,7 +1844,7 @@ viewCallAnswer ct WebRTCSession {rtcSession = answer, rtcIceCandidates = iceCand
[ ttyContact' ct <> " continued the WebRTC call", [ ttyContact' ct <> " continued the WebRTC call",
"To connect, please paste the data below in your browser window you opened earlier and click Connect button", "To connect, please paste the data below in your browser window you opened earlier and click Connect button",
"", "",
viewJSON WCCallAnswer {answer, iceCandidates} viewJSON WCCallAnswer {answer, iceCandidates}
] ]
callMediaStr :: CallType -> StyledString callMediaStr :: CallType -> StyledString
@ -1914,6 +1915,12 @@ viewRemoteCtrl CtrlAppInfo {deviceName, appVersionRange = AppVersionRange _ (App
| otherwise = "" | otherwise = ""
showCompatible = if compatible then "" else ", " <> bold' "not compatible" showCompatible = if compatible then "" else ", " <> bold' "not compatible"
viewRemoteCtrlStopped :: RemoteCtrlStopReason -> [StyledString]
viewRemoteCtrlStopped = \case
RCSRConnectionFailed (ChatErrorAgent (RCP RCEIdentity) _) ->
["remote controller stopped: this link was used with another controller, please create a new link on the host"]
_ -> ["remote controller stopped"]
viewChatError :: Bool -> ChatLogLevel -> Bool -> ChatError -> [StyledString] viewChatError :: Bool -> ChatLogLevel -> Bool -> ChatError -> [StyledString]
viewChatError isCmd logLevel testView = \case viewChatError isCmd logLevel testView = \case
ChatError err -> case err of ChatError err -> case err of

View file

@ -101,7 +101,8 @@ testCoreOpts =
logAgent = Nothing, logAgent = Nothing,
logFile = Nothing, logFile = Nothing,
tbqSize = 16, tbqSize = 16,
highlyAvailable = False highlyAvailable = False,
yesToUpMigrations = False
} }
getTestOpts :: Bool -> ScrubbedBytes -> ChatOpts getTestOpts :: Bool -> ScrubbedBytes -> ChatOpts

View file

@ -2032,10 +2032,19 @@ testGroupPrefsSimplexLinksForRole = testChat3 aliceProfile bobProfile cathProfil
threadDelay 1000000 threadDelay 1000000
bob ##> "/c" bob ##> "/c"
inv <- getInvitation bob inv <- getInvitation bob
bob ##> ("#team " <> inv) bob ##> ("#team \"" <> inv <> "\\ntest\"")
bob <## "bad chat command: feature not allowed SimpleX links"
bob ##> ("/_send #1 json {\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}")
bob <## "bad chat command: feature not allowed SimpleX links" bob <## "bad chat command: feature not allowed SimpleX links"
(alice </) (alice </)
(cath </) (cath </)
bob `send` ("@alice \"" <> inv <> "\\ntest\"")
bob <# ("@alice " <> inv)
bob <## "test"
alice <# ("bob> " <> inv)
alice <## "test"
bob ##> "#team <- @alice https://simplex.chat"
bob <## "bad chat command: feature not allowed SimpleX links"
alice #> ("#team " <> inv) alice #> ("#team " <> inv)
bob <# ("#team alice> " <> inv) bob <# ("#team alice> " <> inv)
cath <# ("#team alice> " <> inv) cath <# ("#team alice> " <> inv)

View file

@ -210,3 +210,10 @@ multilineMarkdownList = describe "multiline markdown" do
parseMaybeMarkdownList "http://simplex.chat\ntext 1\ntext 2\nhttp://app.simplex.chat" `shouldBe` Just [uri' "http://simplex.chat", "\ntext 1\ntext 2\n", uri' "http://app.simplex.chat"] parseMaybeMarkdownList "http://simplex.chat\ntext 1\ntext 2\nhttp://app.simplex.chat" `shouldBe` Just [uri' "http://simplex.chat", "\ntext 1\ntext 2\n", uri' "http://app.simplex.chat"]
it "no markdown" do it "no markdown" do
parseMaybeMarkdownList "not a\nmarkdown" `shouldBe` Nothing parseMaybeMarkdownList "not a\nmarkdown" `shouldBe` Nothing
let inv = "/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
it "multiline with simplex link" do
parseMaybeMarkdownList ("https://simplex.chat" <> inv <> "\ntext")
`shouldBe` Just
[ FormattedText (Just $ SimplexLink XLInvitation ("simplex:" <> inv) ["smp.simplex.im"]) ("https://simplex.chat" <> inv),
"\ntext"
]

View file

@ -119,7 +119,7 @@ remoteHandshakeRejectTest = testChat3 aliceProfile aliceDesktopProfile bobProfil
inv <- getTermLine desktop inv <- getTermLine desktop
mobileBob ##> ("/connect remote ctrl " <> inv) mobileBob ##> ("/connect remote ctrl " <> inv)
mobileBob <## ("connecting new remote controller: My desktop, v" <> versionNumber) mobileBob <## ("connecting new remote controller: My desktop, v" <> versionNumber)
mobileBob <## "remote controller stopped" mobileBob <## "remote controller stopped: this link was used with another controller, please create a new link on the host"
-- the server remains active after rejecting invalid client -- the server remains active after rejecting invalid client
mobile ##> ("/connect remote ctrl " <> inv) mobile ##> ("/connect remote ctrl " <> inv)

View file

@ -31,7 +31,7 @@
"simplex-explained-tab-2-p-1": "لكل اتصال، تستخدم قائمتي انتظار منفصلتين للمُراسلة لإرسال واستلام الرسائل عبر خوادم مختلفة.", "simplex-explained-tab-2-p-1": "لكل اتصال، تستخدم قائمتي انتظار منفصلتين للمُراسلة لإرسال واستلام الرسائل عبر خوادم مختلفة.",
"simplex-explained-tab-2-p-2": "تقوم الخوادم بتمرير الرسائل في اتجاه واحد فقط، دون الحصول على الصورة الكاملة لمُحادثات المستخدم أو اتصالاته.", "simplex-explained-tab-2-p-2": "تقوم الخوادم بتمرير الرسائل في اتجاه واحد فقط، دون الحصول على الصورة الكاملة لمُحادثات المستخدم أو اتصالاته.",
"simplex-explained-tab-3-p-1": "تحتوي الخوادم على بيانات اعتماد مجهولة منفصلة لكل قائمة انتظار، ولا تعرف المستخدمين الذين ينتمون إليهم.", "simplex-explained-tab-3-p-1": "تحتوي الخوادم على بيانات اعتماد مجهولة منفصلة لكل قائمة انتظار، ولا تعرف المستخدمين الذين ينتمون إليهم.",
"copyright-label": "مشروع مفتوح المصدر © SimpleX 2020-2023", "copyright-label": "مشروع مفتوح المصدر © SimpleX 2020-2024",
"simplex-chat-protocol": "بروتوكول دردشة SimpleX", "simplex-chat-protocol": "بروتوكول دردشة SimpleX",
"developers": "المطورين", "developers": "المطورين",
"hero-subheader": "أول نظام مُراسلة<br> دون معرّفات مُستخدم", "hero-subheader": "أول نظام مُراسلة<br> دون معرّفات مُستخدم",

View file

@ -21,7 +21,7 @@
"smp-protocol": "СМП Протокол", "smp-protocol": "СМП Протокол",
"chat-protocol": "Чат протокол", "chat-protocol": "Чат протокол",
"donate": "Дарете", "donate": "Дарете",
"copyright-label": "© 2020-2023 SimpleX | Проект с отворен код", "copyright-label": "© 2020-2024 SimpleX | Проект с отворен код",
"simplex-chat-protocol": "SimpleX Чат протокол", "simplex-chat-protocol": "SimpleX Чат протокол",
"terminal-cli": "Системна конзола", "terminal-cli": "Системна конзола",
"terms-and-privacy-policy": "Условия и политика за поверителност", "terms-and-privacy-policy": "Условия и политика за поверителност",

View file

@ -25,7 +25,7 @@
"smp-protocol": "SMP protokol", "smp-protocol": "SMP protokol",
"chat-protocol": "Chat protokol", "chat-protocol": "Chat protokol",
"donate": "Darovat", "donate": "Darovat",
"copyright-label": "© 2020-2023 SimpleX | Projekt s otevřeným zdrojovým kódem", "copyright-label": "© 2020-2024 SimpleX | Projekt s otevřeným zdrojovým kódem",
"simplex-chat-protocol": "SimpleX Chat protokol", "simplex-chat-protocol": "SimpleX Chat protokol",
"terminal-cli": "Terminálové rozhraní příkazového řádku", "terminal-cli": "Terminálové rozhraní příkazového řádku",
"terms-and-privacy-policy": "Podmínky a zásady ochrany osobních údajů", "terms-and-privacy-policy": "Podmínky a zásady ochrany osobních údajů",

View file

@ -21,7 +21,7 @@
"smp-protocol": "SMP Protokoll", "smp-protocol": "SMP Protokoll",
"chat-bot-example": "Beispiel für einen Chatbot", "chat-bot-example": "Beispiel für einen Chatbot",
"donate": "Spenden", "donate": "Spenden",
"copyright-label": "© 2020-2023 SimpleX | Open-Source Projekt", "copyright-label": "© 2020-2024 SimpleX | Open-Source Projekt",
"chat-protocol": "Chat Protokoll", "chat-protocol": "Chat Protokoll",
"simplex-chat-protocol": "SimpleX Chat Protokoll", "simplex-chat-protocol": "SimpleX Chat Protokoll",
"terminal-cli": "Terminal Kommandozeilen-Schnittstelle", "terminal-cli": "Terminal Kommandozeilen-Schnittstelle",

View file

@ -21,7 +21,7 @@
"smp-protocol": "SMP protocol", "smp-protocol": "SMP protocol",
"chat-protocol": "Chat protocol", "chat-protocol": "Chat protocol",
"donate": "Donate", "donate": "Donate",
"copyright-label": "© 2020-2023 SimpleX | Open-Source Project", "copyright-label": "© 2020-2024 SimpleX | Open-Source Project",
"simplex-chat-protocol": "SimpleX Chat protocol", "simplex-chat-protocol": "SimpleX Chat protocol",
"terminal-cli": "Terminal CLI", "terminal-cli": "Terminal CLI",
"terms-and-privacy-policy": "Privacy Policy", "terms-and-privacy-policy": "Privacy Policy",

View file

@ -10,7 +10,7 @@
"simplex-explained-tab-3-p-2": "El usuario puede mejorar aún más la privacidad de sus metadatos haciendo uso de la red Tor para acceder a los servidores, evitando así la correlación por dirección IP.", "simplex-explained-tab-3-p-2": "El usuario puede mejorar aún más la privacidad de sus metadatos haciendo uso de la red Tor para acceder a los servidores, evitando así la correlación por dirección IP.",
"smp-protocol": "Protocolo SMP", "smp-protocol": "Protocolo SMP",
"donate": "Donación", "donate": "Donación",
"copyright-label": "© 2020-2023 SimpleX | Proyecto de Código Abierto", "copyright-label": "© 2020-2024 SimpleX | Proyecto de Código Abierto",
"simplex-chat-protocol": "Protocolo de SimpleX Chat", "simplex-chat-protocol": "Protocolo de SimpleX Chat",
"terms-and-privacy-policy": "Términos y Política de Privacidad", "terms-and-privacy-policy": "Términos y Política de Privacidad",
"hero-header": "Privacidad redefinida", "hero-header": "Privacidad redefinida",

View file

@ -21,7 +21,7 @@
"smp-protocol": "Protocole SMP", "smp-protocol": "Protocole SMP",
"chat-protocol": "Protocole de chat", "chat-protocol": "Protocole de chat",
"donate": "Faire un don", "donate": "Faire un don",
"copyright-label": "© 2020-2023 SimpleX | Projet Open-Source", "copyright-label": "© 2020-2024 SimpleX | Projet Open-Source",
"simplex-chat-protocol": "Protocole SimpleX Chat", "simplex-chat-protocol": "Protocole SimpleX Chat",
"terminal-cli": "Terminal CLI", "terminal-cli": "Terminal CLI",
"terms-and-privacy-policy": "Politique de confidentialité", "terms-and-privacy-policy": "Politique de confidentialité",

View file

@ -20,7 +20,7 @@
"smp-protocol": "SMP protokoll", "smp-protocol": "SMP protokoll",
"chat-protocol": "Csevegés protokoll", "chat-protocol": "Csevegés protokoll",
"donate": "Támogatás", "donate": "Támogatás",
"copyright-label": "© 2020-2023 SimpleX | Nyílt forráskódú projekt", "copyright-label": "© 2020-2024 SimpleX | Nyílt forráskódú projekt",
"simplex-chat-protocol": "SimpleX Chat protokoll", "simplex-chat-protocol": "SimpleX Chat protokoll",
"terminal-cli": "Terminál CLI", "terminal-cli": "Terminál CLI",
"terms-and-privacy-policy": "Adatvédelmi irányelvek", "terms-and-privacy-policy": "Adatvédelmi irányelvek",
@ -256,4 +256,4 @@
"simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül", "simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül",
"simplex-chat-repo": "SimpleX Chat tároló", "simplex-chat-repo": "SimpleX Chat tároló",
"stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók" "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók"
} }

View file

@ -10,7 +10,7 @@
"simplex-explained-tab-3-p-1": "I server hanno credenziali anonime separate per ogni coda e non sanno a quali utenti appartengano.", "simplex-explained-tab-3-p-1": "I server hanno credenziali anonime separate per ogni coda e non sanno a quali utenti appartengano.",
"chat-protocol": "Protocollo di chat", "chat-protocol": "Protocollo di chat",
"donate": "Dona", "donate": "Dona",
"copyright-label": "© 2020-2023 SimpleX | Progetto Open-Source", "copyright-label": "© 2020-2024 SimpleX | Progetto Open-Source",
"simplex-chat-protocol": "Protocollo di SimpleX Chat", "simplex-chat-protocol": "Protocollo di SimpleX Chat",
"terminal-cli": "Terminale CLI", "terminal-cli": "Terminale CLI",
"terms-and-privacy-policy": "Informativa sulla privacy", "terms-and-privacy-policy": "Informativa sulla privacy",

View file

@ -52,7 +52,7 @@
"chat-protocol": "チャットプロトコル", "chat-protocol": "チャットプロトコル",
"chat-bot-example": "チャットボットの例", "chat-bot-example": "チャットボットの例",
"donate": "寄付", "donate": "寄付",
"copyright-label": "© 2020-2023 SimpleX | Open-Source Project", "copyright-label": "© 2020-2024 SimpleX | Open-Source Project",
"hero-p-1": "他のアプリにはユーザー ID があります: Signal、Matrix、Session、Briar、Jami、Cwtch など。<br> SimpleX にはありません。<strong>乱数さえもありません</strong>。<br> これにより、プライバシーが大幅に向上します。", "hero-p-1": "他のアプリにはユーザー ID があります: Signal、Matrix、Session、Briar、Jami、Cwtch など。<br> SimpleX にはありません。<strong>乱数さえもありません</strong>。<br> これにより、プライバシーが大幅に向上します。",
"copy-the-command-below-text": "以下のコマンドをコピーしてチャットで使用します:", "copy-the-command-below-text": "以下のコマンドをコピーしてチャットで使用します:",
"simplex-private-card-9-point-1": "各メッセージ キューは、異なる送信アドレスと受信アドレスを使用してメッセージを一方向に渡します。", "simplex-private-card-9-point-1": "各メッセージ キューは、異なる送信アドレスと受信アドレスを使用してメッセージを一方向に渡します。",

View file

@ -17,7 +17,7 @@
"chat-bot-example": "Chatbot voorbeeld", "chat-bot-example": "Chatbot voorbeeld",
"smp-protocol": "SMP protocol", "smp-protocol": "SMP protocol",
"donate": "Doneer", "donate": "Doneer",
"copyright-label": "© 2020-2023 SimpleX | Open-sourceproject", "copyright-label": "© 2020-2024 SimpleX | Open-sourceproject",
"simplex-chat-protocol": "SimpleX Chat protocol", "simplex-chat-protocol": "SimpleX Chat protocol",
"terminal-cli": "Terminal CLI", "terminal-cli": "Terminal CLI",
"terms-and-privacy-policy": "Privacybeleid", "terms-and-privacy-policy": "Privacybeleid",

View file

@ -15,7 +15,7 @@
"smp-protocol": "Protokół SMP", "smp-protocol": "Protokół SMP",
"chat-protocol": "Protokół czatu", "chat-protocol": "Protokół czatu",
"donate": "Darowizna", "donate": "Darowizna",
"copyright-label": "© 2020-2023 SimpleX | Projekt Open-Source", "copyright-label": "© 2020-2024 SimpleX | Projekt Open-Source",
"simplex-chat-protocol": "Protokół SimpleX Chat", "simplex-chat-protocol": "Protokół SimpleX Chat",
"terminal-cli": "Terminal CLI", "terminal-cli": "Terminal CLI",
"terms-and-privacy-policy": "Polityka prywatności", "terms-and-privacy-policy": "Polityka prywatności",

View file

@ -25,7 +25,7 @@
"smp-protocol": "Protocolo SMP", "smp-protocol": "Protocolo SMP",
"chat-protocol": "Protocolo de bate-papo", "chat-protocol": "Protocolo de bate-papo",
"donate": "Doar", "donate": "Doar",
"copyright-label": "© 2020-2023 SimpleX | Projeto de Código Livre", "copyright-label": "© 2020-2024 SimpleX | Projeto de Código Livre",
"simplex-chat-protocol": "Protocolo Chat SimpleX", "simplex-chat-protocol": "Protocolo Chat SimpleX",
"terminal-cli": "CLI Terminal", "terminal-cli": "CLI Terminal",
"hero-header": "Privacidade redefinida", "hero-header": "Privacidade redefinida",

View file

@ -1,6 +1,6 @@
{ {
"copy-the-command-below-text": "скопируйте приведенную ниже команду и используйте ее в чате:", "copy-the-command-below-text": "скопируйте приведенную ниже команду и используйте ее в чате:",
"copyright-label": "© 2020-2023 SimpleX | Проект с открытым исходным кодом", "copyright-label": "© 2020-2024 SimpleX | Проект с открытым исходным кодом",
"chat-bot-example": "Пример Чат бота", "chat-bot-example": "Пример Чат бота",
"simplex-private-card-9-point-1": "Каждая очередь сообщений передает сообщения в одном направлении с разными адресами отправки и получения.", "simplex-private-card-9-point-1": "Каждая очередь сообщений передает сообщения в одном направлении с разными адресами отправки и получения.",
"simplex-private-card-1-point-2": "Криптобокс NaCL в каждой очереди для предотвращения корреляции трафика между очередями сообщений, в случае компрометации TLS.", "simplex-private-card-1-point-2": "Криптобокс NaCL в каждой очереди для предотвращения корреляции трафика между очередями сообщений, в случае компрометации TLS.",

View file

@ -58,7 +58,7 @@ active_blog: true
</div> </div>
<div class="p-6 md:py-8 flex-[2.5] flex flex-col"> <div class="p-6 md:py-8 flex-[2.5] flex flex-col">
<div> <div>
<h1 class="text-grey-black dark:text-white text-lg md:text-xl font-bold "> <h1 class="text-grey-black dark:text-white !text-lg md:!text-xl font-bold ">
<a href="{{ blog.url }}">{{ blog.data.title | safe }}</a> <a href="{{ blog.url }}">{{ blog.data.title | safe }}</a>
</h1> </h1>
<p class="text-sm text-[#A8B0B4] font-medium mt-2 mb-4 tracking-[0.03em]"> <p class="text-sm text-[#A8B0B4] font-medium mt-2 mb-4 tracking-[0.03em]">

View file

@ -11,28 +11,31 @@ metadata:
email: chat@simplex.chat email: chat@simplex.chat
--- ---
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="{{ metadata.url }}"> <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ metadata.language }}">
<id>{{ metadata.url }}</id>
<link type="text/html" rel="alternate" href="{{ metadata.url }}"/>
<link type="application/atom+xml" rel="self" href="{{ permalink | absoluteUrl(metadata.url) }}"/>
<title>{{ metadata.title }}</title> <title>{{ metadata.title }}</title>
<subtitle>{{ metadata.subtitle }}</subtitle> <subtitle>{{ metadata.subtitle }}</subtitle>
<link href="{{ permalink | absoluteUrl(metadata.url) }}" rel="self"/>
<link href="{{ metadata.url }}"/>
<updated>{{ collections.blogs | getNewestCollectionItemDate | dateToRfc3339 }}</updated> <updated>{{ collections.blogs | getNewestCollectionItemDate | dateToRfc3339 }}</updated>
<id>{{ metadata.url }}</id>
<author> <author>
<name>{{ metadata.author.name }}</name> <name>{{ metadata.author.name }}</name>
<email>{{ metadata.author.email }}</email> <email>{{ metadata.author.email }}</email>
</author> </author>
{%- for blog in collections.blogs | reverse %} {%- for blog in collections.blogs | reverse %}
{%- if not blog.data.draft %} {%- if not blog.data.draft %}
{%- set absolutePostUrl = blog.url | absoluteUrl(metadata.url) %} {%- set absolutePostUrl = blog.data.permalink | absoluteUrl(metadata.url) %}
<entry> <entry>
<id>{{ blog.data.permalink | absoluteUrl(metadata.url) }}</id>
<!-- <updated>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</updated> -->
<updated>{{ blog.data.date | dateToRfc3339 }}</updated>
<link rel="alternate" type="text/html" href="{{ absolutePostUrl }}"/>
<title>{{ blog.data.title }}</title> <title>{{ blog.data.title }}</title>
<link href="{{ absolutePostUrl }}"/> <content type="html">{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
{# <updated>{{ blog.date | dateToRfc3339 }}</updated> #} <author>
<updated>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</updated> <name>{{ metadata.author.name }}</name>
<id>{{ absolutePostUrl }}</id> <email>{{ metadata.author.email }}</email>
<content xml:lang="{{ metadata.language }}" type="html">{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content> </author>
{# <content xml:lang="{{ metadata.language }}" type="html">{{ blog.templateContent | striptags | truncate(200) }}</content> #}
</entry> </entry>
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}

View file

@ -26,8 +26,8 @@ metadata:
<link>{{ absolutePostUrl }}</link> <link>{{ absolutePostUrl }}</link>
<description>{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</description> <description>{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</description>
{# <description>{{ blog.templateContent | striptags | truncate(200) }}</description> #} {# <description>{{ blog.templateContent | striptags | truncate(200) }}</description> #}
{# <pubDate>{{ blog.data.date | dateToRfc822 }}</pubDate> #} <pubDate>{{ blog.data.date | dateToRfc822 }}</pubDate>
<pubDate>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</pubDate> {# <pubDate>{{ blog.data.date.toUTCString().split(' ').slice(1, 4).join(' ') }}</pubDate> #}
<dc:creator>{{ metadata.author.name }}</dc:creator> <dc:creator>{{ metadata.author.name }}</dc:creator>
<guid>{{ absolutePostUrl }}</guid> <guid>{{ absolutePostUrl }}</guid>
</item> </item>

View file

@ -46,6 +46,10 @@ img{
-ms-user-select: none; /* For Internet Explorer and Edge */ -ms-user-select: none; /* For Internet Explorer and Edge */
} }
a{
word-wrap: break-word;
}
/* #comparison::before { /* #comparison::before {
display: block; display: block;
content: " "; content: " ";