diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 7237711a2a..f90653534c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -19,6 +19,9 @@ struct ChatItemForwardingView: View { @State private var searchText: String = "" @FocusState private var searchFocused + @State private var alert: SomeAlert? + @State private var hasSimplexLink_: Bool? + private let chatsToForwardTo = filterChatsToForwardTo() var body: some View { NavigationView { @@ -35,47 +38,29 @@ struct ChatItemForwardingView: View { } } } + .alert(item: $alert) { $0.alert } } @ViewBuilder private func forwardListView() -> some View { VStack(alignment: .leading) { - let chatsToForwardTo = filterChatsToForwardTo() if !chatsToForwardTo.isEmpty { - ScrollView { - LazyVStack(alignment: .leading, spacing: 8) { - searchFieldView(text: $searchText, focussed: $searchFocused) - .padding(.leading, 2) - let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase - let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { filterChatSearched($0, s) } - ForEach(chats) { chat in - Divider() - forwardListNavLinkView(chat) - .disabled(chatModel.deletedChats.contains(chat.chatInfo.id)) - } + List { + searchFieldView(text: $searchText, focussed: $searchFocused) + .padding(.leading, 2) + let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase + let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) } + ForEach(chats) { chat in + forwardListChatView(chat) + .disabled(chatModel.deletedChats.contains(chat.chatInfo.id)) } - .padding(.horizontal) - .padding(.vertical, 8) - .background(Color(uiColor: .systemBackground)) - .cornerRadius(12) - .padding(.horizontal) } - .background(Color(.systemGroupedBackground)) } else { emptyList() } } } - private func filterChatsToForwardTo() -> [Chat] { - var filteredChats = chatModel.chats.filter({ canForwardToChat($0) }) - if let index = filteredChats.firstIndex(where: { $0.chatInfo.chatType == .local }) { - let privateNotes = filteredChats.remove(at: index) - filteredChats.insert(privateNotes, at: 0) - } - return filteredChats - } - - private func filterChatSearched(_ chat: Chat, _ searchStr: String) -> Bool { + private func foundChat(_ chat: Chat, _ searchStr: String) -> Bool { let cInfo = chat.chatInfo return switch cInfo { case let .direct(contact): @@ -91,42 +76,70 @@ struct ChatItemForwardingView: View { } } - private func canForwardToChat(_ chat: Chat) -> Bool { - switch chat.chatInfo { - case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv - case let .group(groupInfo): groupInfo.sendMsgEnabled - case let .local(noteFolder): noteFolder.sendMsgEnabled + private func prohibitedByPref(_ chat: Chat) -> Bool { + // preference checks should match checks in compose view + let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks) + let fileProhibited = (ci.content.msgContent?.isMediaOrFileAttachment ?? false) && !chat.groupFeatureEnabled(.files) + let voiceProhibited = (ci.content.msgContent?.isVoice ?? false) && !chat.chatInfo.featureEnabled(.voice) + return switch chat.chatInfo { + case .direct: voiceProhibited + case .group: simplexLinkProhibited || fileProhibited || voiceProhibited + case .local: false case .contactRequest: false case .contactConnection: false case .invalidJSON: false } } + private var hasSimplexLink: Bool { + if let hasSimplexLink_ { return hasSimplexLink_ } + let r = + if let mcText = ci.content.msgContent?.text, + let parsedMsg = parseSimpleXMarkdown(mcText) { + parsedMsgHasSimplexLink(parsedMsg) + } else { + false + } + hasSimplexLink_ = r + return r + } + private func emptyList() -> some View { Text("No filtered chats") .foregroundColor(.secondary) .frame(maxWidth: .infinity) } - - @ViewBuilder private func forwardListNavLinkView(_ chat: Chat) -> some View { + + @ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View { + let prohibited = prohibitedByPref(chat) Button { - dismiss() - if chat.id == fromChatInfo.id { - composeState = ComposeState( - message: composeState.message, - preview: composeState.linkPreview != nil ? composeState.preview : .noPreview, - contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo) - ) + if prohibited { + alert = SomeAlert( + alert: mkAlert( + title: "Cannot forward message", + message: "Selected chat preferences prohibit this message." + ), + id: "forward prohibited by preferences" + ) } else { - composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo) - chatModel.chatId = chat.id + dismiss() + if chat.id == fromChatInfo.id { + composeState = ComposeState( + message: composeState.message, + preview: composeState.linkPreview != nil ? composeState.preview : .noPreview, + contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo) + ) + } else { + composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo) + chatModel.chatId = chat.id + } } } label: { HStack { ChatInfoImage(chat: chat, size: 30) .padding(.trailing, 2) Text(chat.chatInfo.chatViewName) - .foregroundColor(.primary) + .foregroundColor(prohibited ? .secondary : .primary) .lineLimit(1) if chat.chatInfo.incognito { Spacer() @@ -142,6 +155,27 @@ struct ChatItemForwardingView: View { } } +private func filterChatsToForwardTo() -> [Chat] { + var filteredChats = ChatModel.shared.chats.filter { c in + c.chatInfo.chatType != .local && canForwardToChat(c) + } + if let privateNotes = ChatModel.shared.chats.first(where: { $0.chatInfo.chatType == .local }) { + filteredChats.insert(privateNotes, at: 0) + } + return filteredChats +} + +private func canForwardToChat(_ chat: Chat) -> Bool { + switch chat.chatInfo { + case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv + case let .group(groupInfo): groupInfo.sendMsgEnabled + case let .local(noteFolder): noteFolder.sendMsgEnabled + case .contactRequest: false + case .contactConnection: false + case .invalidJSON: false + } +} + #Preview { ChatItemForwardingView( ci: ChatItem.getSample(1, .directSnd, .now, "hello"), diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 3f1824cd6a..8a0ad9a023 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -286,6 +286,7 @@ struct ComposeView: View { if chat.chatInfo.contact?.nextSendGrpInv ?? false { ContextInvitingContactMemberView() } + // preference checks should match checks in forwarding list let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks) let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files) let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice) @@ -1065,7 +1066,7 @@ struct ComposeView: View { } else { nil } - let simplexLink = parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false }) + let simplexLink = parsedMsgHasSimplexLink(parsedMsg) return (url, simplexLink) } @@ -1105,6 +1106,10 @@ struct ComposeView: View { } } +func parsedMsgHasSimplexLink(_ parsedMsg: [FormattedText]) -> Bool { + parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false }) +} + struct ComposeView_Previews: PreviewProvider { static var previews: some View { let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 442f933ace..4b1f72345a 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -11,14 +11,9 @@ import SimpleXChat import CodeScanner import AVFoundation -enum SomeAlert: Identifiable { - case someAlert(alert: Alert, id: String) - - var id: String { - switch self { - case let .someAlert(_, id): return id - } - } +struct SomeAlert: Identifiable { + var alert: Alert + var id: String } private enum NewChatViewAlert: Identifiable { @@ -142,8 +137,8 @@ struct NewChatView: View { switch(a) { case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" }) - case let .newChatSomeAlert(.someAlert(alert, _)): - return alert + case let .newChatSomeAlert(a): + return a.alert } } } @@ -181,7 +176,7 @@ struct NewChatView: View { await MainActor.run { creatingConnReq = false if let apiAlert = apiAlert { - alert = .newChatSomeAlert(alert: .someAlert(alert: apiAlert, id: "createInvitation error")) + alert = .newChatSomeAlert(alert: SomeAlert(alert: apiAlert, id: "createInvitation error")) } } } @@ -315,7 +310,7 @@ private struct ConnectView: View { // showQRCodeScanner = false connect(pastedLink) } else { - alert = .newChatSomeAlert(alert: .someAlert( + alert = .newChatSomeAlert(alert: SomeAlert( alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."), id: "pasteLinkView: code is not a SimpleX link" )) @@ -338,14 +333,14 @@ private struct ConnectView: View { if strIsSimplexLink(r.string) { connect(link) } else { - alert = .newChatSomeAlert(alert: .someAlert( + alert = .newChatSomeAlert(alert: SomeAlert( alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."), id: "processQRCode: code is not a SimpleX link" )) } case let .failure(e): logger.error("processQRCode QR code error: \(e.localizedDescription)") - alert = .newChatSomeAlert(alert: .someAlert( + alert = .newChatSomeAlert(alert: SomeAlert( alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"), id: "processQRCode: failure" )) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index eb352bbfdf..ddc4c73e7c 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -24,11 +24,6 @@ 5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; }; 5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.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 */; }; 5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */; }; 5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; @@ -202,6 +197,11 @@ D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; 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 */ /* Begin PBXContainerItemProxy section */ @@ -278,11 +278,6 @@ 5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = ""; }; 5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = ""; }; 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = ""; }; - 5C0EA1362C0B176B00AD2E5E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5C0EA1382C0B176B00AD2E5E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 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 = ""; }; - 5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a"; sourceTree = ""; }; 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionInfo.swift; sourceTree = ""; }; 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMediaView.swift; sourceTree = ""; }; 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = ""; }; @@ -502,6 +497,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; }; 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; }; + E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; sourceTree = ""; }; + E5D68D3B2C22D78C00CBA347 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 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 = ""; }; + E5D68D3D2C22D78C00CBA347 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -539,13 +539,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; 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 */, - 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 */, - 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; }; @@ -611,11 +611,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C0EA1382C0B176B00AD2E5E /* libffi.a */, - 5C0EA1362C0B176B00AD2E5E /* libgmp.a */, - 5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */, - 5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */, - 5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */, + E5D68D3B2C22D78C00CBA347 /* libffi.a */, + E5D68D3D2C22D78C00CBA347 /* libgmp.a */, + E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */, + E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */, + E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */, ); path = Libraries; sourceTree = ""; @@ -1562,7 +1562,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 225; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1587,7 +1587,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 5.8; + MARKETING_VERSION = 5.8.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1611,7 +1611,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 225; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1636,7 +1636,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8; + MARKETING_VERSION = 5.8.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1697,7 +1697,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 225; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -1712,7 +1712,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8; + MARKETING_VERSION = 5.8.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1734,7 +1734,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 225; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -1749,7 +1749,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8; + MARKETING_VERSION = 5.8.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1771,7 +1771,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 225; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1797,7 +1797,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8; + MARKETING_VERSION = 5.8.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -1822,7 +1822,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 225; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1848,7 +1848,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8; + MARKETING_VERSION = 5.8.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 4c62fe4c8b..1f58ee2363 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -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 { "json \(encodeJSON(self))" } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 89af46edb5..0ebe77e524 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2925,6 +2925,20 @@ sealed class MsgContent { @Serializable(with = MsgContentSerializer::class) class MCFile(override val text: String): MsgContent() @Serializable(with = MsgContentSerializer::class) class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent() + val isVoice: Boolean get() = + when (this) { + is MCVoice -> true + else -> false + } + + val isMediaOrFileAttachment: Boolean get() = + when (this) { + is MCImage -> true + is MCVideo -> true + is MCFile -> true + else -> false + } + val cmdString: String get() = if (this is MCUnknown) "json $json" else "json ${json.encodeToString(this)}" } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt index 1de1e40afb..91dffeb7c8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt @@ -9,30 +9,55 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import chat.simplex.common.views.helpers.ProfileImage import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable -fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) { +fun ShareListNavLinkView( + chat: Chat, + chatModel: ChatModel, + isMediaOrFileAttachment: Boolean, + isVoice: Boolean, + hasSimplexLink: Boolean +) { val stopped = chatModel.chatRunning.value == false when (chat.chatInfo) { - is ChatInfo.Direct -> + is ChatInfo.Direct -> { + val voiceProhibited = isVoice && !chat.chatInfo.featureEnabled(ChatFeature.Voice) ShareListNavLinkLayout( - chatLinkPreview = { SharePreviewView(chat) }, - click = { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) }, + chatLinkPreview = { SharePreviewView(chat, disabled = voiceProhibited) }, + click = { + if (voiceProhibited) { + showForwardProhibitedByPrefAlert() + } else { + directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) + } + }, stopped ) - is ChatInfo.Group -> + } + is ChatInfo.Group -> { + val simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks) + val fileProhibited = isMediaOrFileAttachment && !chat.groupFeatureEnabled(GroupFeature.Files) + val voiceProhibited = isVoice && !chat.chatInfo.featureEnabled(ChatFeature.Voice) + val prohibitedByPref = simplexLinkProhibited || fileProhibited || voiceProhibited ShareListNavLinkLayout( - chatLinkPreview = { SharePreviewView(chat) }, - click = { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) }, + chatLinkPreview = { SharePreviewView(chat, disabled = prohibitedByPref) }, + click = { + if (prohibitedByPref) { + showForwardProhibitedByPrefAlert() + } else { + groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) + } + }, stopped ) + } is ChatInfo.Local -> ShareListNavLinkLayout( - chatLinkPreview = { SharePreviewView(chat) }, + chatLinkPreview = { SharePreviewView(chat, disabled = false) }, click = { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) }, stopped ) @@ -40,6 +65,13 @@ fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) { } } +private fun showForwardProhibitedByPrefAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.cannot_share_message_alert_title), + text = generalGetString(MR.strings.cannot_share_message_alert_text), + ) +} + @Composable private fun ShareListNavLinkLayout( chatLinkPreview: @Composable () -> Unit, @@ -53,7 +85,7 @@ private fun ShareListNavLinkLayout( } @Composable -private fun SharePreviewView(chat: Chat) { +private fun SharePreviewView(chat: Chat, disabled: Boolean) { Row( Modifier.fillMaxSize(), horizontalArrangement = Arrangement.SpaceBetween, @@ -70,7 +102,7 @@ private fun SharePreviewView(chat: Chat) { } Text( chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = if (chat.chatInfo.incognito) Indigo else Color.Unspecified + color = if (disabled) MaterialTheme.colors.secondary else if (chat.chatInfo.incognito) Indigo else Color.Unspecified ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index a36930f5ce..69382d9fde 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -31,13 +31,44 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe scaffoldState = scaffoldState, topBar = { Column { ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } } }, ) { + val sharedContent = chatModel.sharedContent.value + var isMediaOrFileAttachment = false + var isVoice = false + var hasSimplexLink = false + when (sharedContent) { + is SharedContent.Text -> + hasSimplexLink = hasSimplexLink(sharedContent.text) + is SharedContent.Media -> { + isMediaOrFileAttachment = true + hasSimplexLink = hasSimplexLink(sharedContent.text) + } + is SharedContent.File -> { + isMediaOrFileAttachment = true + hasSimplexLink = hasSimplexLink(sharedContent.text) + } + is SharedContent.Forward -> { + val mc = sharedContent.chatItem.content.msgContent + if (mc != null) { + isMediaOrFileAttachment = mc.isMediaOrFileAttachment + isVoice = mc.isVoice + hasSimplexLink = hasSimplexLink(mc.text) + } + } + null -> {} + } Box(Modifier.padding(it)) { Column( modifier = Modifier .fillMaxSize() ) { if (chatModel.chats.isNotEmpty()) { - ShareList(chatModel, search = searchInList) + ShareList( + chatModel, + search = searchInList, + isMediaOrFileAttachment = isMediaOrFileAttachment, + isVoice = isVoice, + hasSimplexLink = hasSimplexLink + ) } else { EmptyList() } @@ -54,6 +85,11 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe } } +private fun hasSimplexLink(msg: String): Boolean { + val parsedMsg = parseToMarkdown(msg) ?: return false + return parsedMsg.any { ft -> ft.format is Format.SimplexLink } +} + @Composable private fun EmptyList() { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { @@ -141,7 +177,13 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState } @Composable -private fun ShareList(chatModel: ChatModel, search: String) { +private fun ShareList( + chatModel: ChatModel, + search: String, + isMediaOrFileAttachment: Boolean, + isVoice: Boolean, + hasSimplexLink: Boolean +) { val chats by remember(search) { derivedStateOf { val sorted = chatModel.chats.toList().sortedByDescending { it.chatInfo is ChatInfo.Local } @@ -156,7 +198,13 @@ private fun ShareList(chatModel: ChatModel, search: String) { modifier = Modifier.fillMaxWidth() ) { items(chats) { chat -> - ShareListNavLinkView(chat, chatModel) + ShareListNavLinkView( + chat, + chatModel, + isMediaOrFileAttachment = isMediaOrFileAttachment, + isVoice = isVoice, + hasSimplexLink = hasSimplexLink + ) } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index b896c4e980..996ecb11da 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -358,6 +358,8 @@ Share media… Share file… Forward message… + Cannot send message + Selected chat preferences prohibit this message. Attach diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 66178aa557..ecf038c8a5 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=5.8 -android.version_code=219 +android.version_name=5.8.1 +android.version_code=221 -desktop.version_name=5.8 -desktop.version_code=53 +desktop.version_name=5.8.1 +desktop.version_code=54 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 diff --git a/scripts/android/compress-and-sign-apk.sh b/scripts/android/compress-and-sign-apk.sh index e46b8a54f1..74d59203c4 100755 --- a/scripts/android/compress-and-sign-apk.sh +++ b/scripts/android/compress-and-sign-apk.sh @@ -47,7 +47,7 @@ for ORIG_NAME in "${ORIG_NAMES[@]}"; do #(cd apk && 7z a -r -mx=0 -tzip ../$ORIG_NAME resources.arsc) 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 diff --git a/website/.eleventy.js b/website/.eleventy.js index a3b94643e8..a0a35f3366 100644 --- a/website/.eleventy.js +++ b/website/.eleventy.js @@ -404,7 +404,6 @@ module.exports = function (ty) { // this condition works if the link is a valid website file const fileContent = fs.readFileSync(linkFile, 'utf8') parsed.path = (matter(fileContent).data?.permalink || parsed.path).replace(/\.md$/, ".html").toLowerCase() - return parsed.path } else if (!fs.existsSync(linkFile)) { linkFile = linkFile.replace('/website/src', '') if (fs.existsSync(linkFile)) { @@ -414,12 +413,13 @@ module.exports = function (ty) { index = linkFile.indexOf(keyword) linkFile = linkFile.substring(index + keyword.length) parsed.path = `${githubUrl}${linkFile}` - return parsed.path } else { // if the link is not a valid website file or project file throw new Error(`Broken link: ${parsed.path} in ${hostFile}`) } } + + return uri.serialize(parsed) } }).use(markdownItAnchor, { slugify: (str) => diff --git a/website/src/_includes/layouts/article.html b/website/src/_includes/layouts/article.html index 4550aa96d7..783b6ece7c 100644 --- a/website/src/_includes/layouts/article.html +++ b/website/src/_includes/layouts/article.html @@ -38,11 +38,10 @@
-
{{ content | applyGlossary | safe }}
+
{{ content | safe }}
{% include "footer.html" %} - diff --git a/website/src/_includes/layouts/doc.html b/website/src/_includes/layouts/doc.html index 66ec7f022b..091f98bdc7 100644 --- a/website/src/_includes/layouts/doc.html +++ b/website/src/_includes/layouts/doc.html @@ -108,7 +108,7 @@ -
{{ content | applyGlossary | safe }}
+
{{ content | safe }}
@@ -117,7 +117,6 @@ {% include "footer.html" %} - diff --git a/website/src/_includes/layouts/privacy.html b/website/src/_includes/layouts/privacy.html index fcaa9964e3..7527852694 100644 --- a/website/src/_includes/layouts/privacy.html +++ b/website/src/_includes/layouts/privacy.html @@ -33,11 +33,10 @@ {% include "navbar.html" %}
-
{{ content | applyGlossary | safe }}
+
{{ content | safe }}
{% include "footer.html" %} - diff --git a/website/src/css/blog.css b/website/src/css/blog.css index a5d1ae0a1a..897fbafb10 100644 --- a/website/src/css/blog.css +++ b/website/src/css/blog.css @@ -217,10 +217,6 @@ h6 { float: right; margin-left: 3rem; } - - #article .float-right{ - margin-left: 3rem; - } } @media (max-width:768px) { diff --git a/website/src/css/style.css b/website/src/css/style.css index a33c13d9c5..475eddc404 100644 --- a/website/src/css/style.css +++ b/website/src/css/style.css @@ -764,15 +764,14 @@ p a{ } .tooltip-title{ - margin: 0 !important; - margin-bottom: 0.5rem !important; - color: #0197FF !important; - font-weight: 600 !important; - font-size: 1.1rem !important; + margin-bottom: 0.5rem; + color: #0197FF; + font-weight: 600; + font-size: 1.1rem; } .dark .tooltip-title{ - color: #70F0F9 !important; + color: #70F0F9; } .glossary-tooltip .read-more-btn{ @@ -855,18 +854,13 @@ p a{ } .glossary-overlay .overlay-card .overlay-title{ - font-size: 1.875rem !important; - line-height: 2.25rem !important; - font-weight: 700 !important; - margin: 0 !important; - padding: 0 !important; - margin-bottom: 1.5rem !important; - --tw-text-opacity: 1 !important; - color: rgb(1 151 255 / var(--tw-text-opacity)) !important; -} - -.glossary-overlay .overlay-card .overlay-title::after{ - background-color: transparent !important; + font-size: 1.875rem; + line-height: 2.25rem; + font-weight: 700; + margin-bottom: 1rem; + margin-bottom: 1.5rem; + --tw-text-opacity: 1; + color: rgb(1 151 255 / var(--tw-text-opacity)); } .glossary-overlay .overlay-card .overlay-content{ @@ -882,10 +876,6 @@ p a{ color: #fff; } -.glossary-overlay .overlay-card .overlay-content p{ - margin: 0 !important; -} - .close-overlay-btn{ fill: #3F484B; position: fixed; diff --git a/website/src/js/script.js b/website/src/js/script.js index abf7779ad3..5f863f48ee 100644 --- a/website/src/js/script.js +++ b/website/src/js/script.js @@ -1,5 +1,3 @@ -const uniqueSwiperContainer = document.querySelector('.unique-swiper') -if (uniqueSwiperContainer) { const uniqueSwiper = new Swiper('.unique-swiper', { slidesPerView: 1, spaceBetween: 80, @@ -25,15 +23,12 @@ const uniqueSwiper = new Swiper('.unique-swiper', { prevEl: '.unique-swiper-button-prev', }, }); -} const isMobile = { Android: () => navigator.userAgent.match(/Android/i), iOS: () => navigator.userAgent.match(/iPhone|iPad|iPod/i) }; -const privateSwiperContainer = document.querySelector('.private-swiper') -if (privateSwiperContainer) { const privateSwiper = new Swiper('.private-swiper', { slidesPerView: 1, slidesPerGroup: 1, @@ -84,10 +79,7 @@ const privateSwiper = new Swiper('.private-swiper', { } } }); -} -const simplexExplainedSwiperContainer = document.querySelector('.simplex-explained-swiper') -if (simplexExplainedSwiperContainer){ const simplexExplainedSwiper = new Swiper(".simplex-explained-swiper", { slidesPerView: 1, spaceBetween: 80, @@ -120,7 +112,6 @@ const simplexExplainedSwiper = new Swiper(".simplex-explained-swiper", { } } }); -} function closeOverlay(e) { e.target.closest('.overlay').classList.remove('flex');