From cbc86cd81e43ccdce258baae150cdd1d812628e3 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 1 Aug 2024 09:48:17 +0300 Subject: [PATCH] ios: disable chats in share-sheet based on preferences (#4549) * claenup * cleanup * remove groupFeatureEnabled from Chat --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 18 ------- .../Views/Chat/ChatItemForwardingView.swift | 35 ++----------- .../Chat/ComposeMessage/ComposeView.swift | 4 -- apps/ios/SimpleX SE/ShareModel.swift | 17 ++++++- apps/ios/SimpleX SE/ShareView.swift | 24 +++++++-- apps/ios/SimpleXChat/ChatUtils.swift | 51 +++++++++++++++++++ 6 files changed, 92 insertions(+), 57 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index c9772090e3..51c9f102b7 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -871,24 +871,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike { var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } } - func groupFeatureEnabled(_ feature: GroupFeature) -> Bool { - if case let .group(groupInfo) = self.chatInfo { - let p = groupInfo.fullGroupPreferences - return switch feature { - case .timedMessages: p.timedMessages.on - case .directMessages: p.directMessages.on(for: groupInfo.membership) - case .fullDelete: p.fullDelete.on - case .reactions: p.reactions.on - case .voice: p.voice.on(for: groupInfo.membership) - case .files: p.files.on(for: groupInfo.membership) - case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership) - case .history: p.history.on - } - } else { - return true - } - } - public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 0b7de32a88..7c405b6346 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -21,7 +21,6 @@ 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(chats: ChatModel.shared.chats) var body: some View { @@ -67,34 +66,6 @@ struct ChatItemForwardingView: View { } } - 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(theme.colors.secondary) @@ -102,7 +73,11 @@ struct ChatItemForwardingView: View { } @ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View { - let prohibited = prohibitedByPref(chat) + let prohibited = chat.prohibitedByPref( + hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text), + isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false, + isVoice: ci.content.msgContent?.isVoice ?? false + ) Button { if prohibited { alert = SomeAlert( diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index e19672c883..1364958172 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -1120,10 +1120,6 @@ 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/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 49c48afc5c..bed9806bf1 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -27,6 +27,7 @@ class ShareModel: ObservableObject { @Published var isLoaded = false @Published var bottomBar: BottomBar = .loadingSpinner @Published var errorAlert: ErrorAlert? + @Published var hasSimplexLink = false @Published var alertRequiresPassword = false enum BottomBar { @@ -49,7 +50,13 @@ class ShareModel: ObservableObject { private var itemProvider: NSItemProvider? - var isSendDisbled: Bool { sharedContent == nil || selected == nil } + var isSendDisbled: Bool { sharedContent == nil || selected == nil || isProhibited(selected) } + + func isProhibited(_ chat: ChatData?) -> Bool { + if let chat, let sharedContent { + sharedContent.prohibited(in: chat, hasSimplexLink: hasSimplexLink) + } else { false } + } var filteredChats: Array { search.isEmpty @@ -379,6 +386,14 @@ enum SharedContent { case .data: .file(comment) } } + + func prohibited(in chatData: ChatData, hasSimplexLink: Bool) -> Bool { + chatData.prohibitedByPref( + hasSimplexLink: hasSimplexLink, + isMediaOrFileAttachment: cryptoFile != nil, + isVoice: false + ) + } } extension NSItemProvider { diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift index a213a4ab23..dcdeae2b82 100644 --- a/apps/ios/SimpleX SE/ShareView.swift +++ b/apps/ios/SimpleX SE/ShareView.swift @@ -20,18 +20,31 @@ struct ShareView: View { ZStack(alignment: .bottom) { if model.isLoaded { List(model.filteredChats) { chat in + let isProhibited = model.isProhibited(chat) + let isSelected = model.selected == chat HStack { profileImage( chatInfoId: chat.chatInfo.id, iconName: chatIconName(chat.chatInfo), size: 30 ) - Text(chat.chatInfo.displayName) + Text(chat.chatInfo.displayName).foregroundStyle( + isProhibited ? .secondary : .primary + ) Spacer() - radioButton(selected: chat == model.selected) + radioButton(selected: isSelected && !isProhibited) } .contentShape(Rectangle()) - .onTapGesture { model.selected = model.selected == chat ? nil : chat } + .onTapGesture { + if isProhibited { + model.errorAlert = ErrorAlert( + title: "Cannot forward message", + message: "Selected chat preferences prohibit this message." + ) { Button("Ok", role: .cancel) { } } + } else { + model.selected = isSelected ? nil : chat + } + } .tag(chat) } } else { @@ -66,6 +79,9 @@ struct ShareView: View { Button("Ok") { model.completion() } } } + .onChange(of: model.comment) { + model.hasSimplexLink = hasSimplexLink($0) + } } private func compose(isLoading: Bool) -> some View { @@ -77,7 +93,7 @@ struct ShareView: View { HStack { Group { if #available(iOSApplicationExtension 16.0, *) { - TextField("Comment", text: $model.comment, axis: .vertical) + TextField("Comment", text: $model.comment, axis: .vertical).lineLimit(6) } else { TextField("Comment", text: $model.comment) } diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift index a37b6babf7..5f56180918 100644 --- a/apps/ios/SimpleXChat/ChatUtils.swift +++ b/apps/ios/SimpleXChat/ChatUtils.swift @@ -14,6 +14,45 @@ public protocol ChatLike { var chatStats: ChatStats { get } } +extension ChatLike { + public func groupFeatureEnabled(_ feature: GroupFeature) -> Bool { + if case let .group(groupInfo) = self.chatInfo { + let p = groupInfo.fullGroupPreferences + return switch feature { + case .timedMessages: p.timedMessages.on + case .directMessages: p.directMessages.on(for: groupInfo.membership) + case .fullDelete: p.fullDelete.on + case .reactions: p.reactions.on + case .voice: p.voice.on(for: groupInfo.membership) + case .files: p.files.on(for: groupInfo.membership) + case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership) + case .history: p.history.on + } + } else { + return true + } + } + + public func prohibitedByPref( + hasSimplexLink: Bool, + isMediaOrFileAttachment: Bool, + isVoice: Bool + ) -> Bool { + // preference checks should match checks in compose view + let simplexLinkProhibited = hasSimplexLink && !groupFeatureEnabled(.simplexLinks) + let fileProhibited = isMediaOrFileAttachment && !groupFeatureEnabled(.files) + let voiceProhibited = isVoice && !chatInfo.featureEnabled(.voice) + return switch chatInfo { + case .direct: voiceProhibited + case .group: simplexLinkProhibited || fileProhibited || voiceProhibited + case .local: false + case .contactRequest: false + case .contactConnection: false + case .invalidJSON: false + } + } +} + public func filterChatsToForwardTo(chats: [C]) -> [C] { var filteredChats = chats.filter { c in c.chatInfo.chatType != .local && canForwardToChat(c.chatInfo) @@ -60,3 +99,15 @@ public func chatIconName(_ cInfo: ChatInfo) -> String { default: "circle.fill" } } + +public func hasSimplexLink(_ text: String?) -> Bool { + if let text, let parsedMsg = parseSimpleXMarkdown(text) { + parsedMsgHasSimplexLink(parsedMsg) + } else { + false + } +} + +public func parsedMsgHasSimplexLink(_ parsedMsg: [FormattedText]) -> Bool { + parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false }) +}