From 5d7abf31cebfff9363f1f085015e2eea840a3a3a Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 4 Jul 2024 19:37:03 +0200 Subject: [PATCH] ios: make message corners rounded when selecting context menus (#4401) Co-authored-by: Evgeny Poberezkin --- .../Chat/ChatItem/CIGroupInvitationView.swift | 1 - .../Chat/ChatItem/CIInvalidJSONView.swift | 1 - .../Chat/ChatItem/CIRcvDecryptionError.swift | 2 - .../Views/Chat/ChatItem/DeletedItemView.swift | 1 - .../Views/Chat/ChatItem/FramedItemView.swift | 1 - .../ChatItem/IntegrityErrorItemView.swift | 1 - .../Chat/ChatItem/MarkedDeletedItemView.swift | 1 - .../Shared/Views/Chat/ChatItemInfoView.swift | 4 +- apps/ios/Shared/Views/Chat/ChatView.swift | 1 + apps/ios/Shared/Views/Chat/ReverseList.swift | 1 + .../Views/Helpers/ChatItemClipShape.swift | 63 +++++++++++++++++++ apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++ 12 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index 62e9852853..ef0fec5dfe 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -70,7 +70,6 @@ struct CIGroupInvitationView: View { .padding(.horizontal, 12) .padding(.vertical, 6) .background(chatItemFrameColor(chatItem, theme)) - .cornerRadius(18) .textSelection(.disabled) .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } .onChange(of: inProgress) { inProgress in diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift index f9802089df..18fd682646 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift @@ -22,7 +22,6 @@ struct CIInvalidJSONView: View { .padding(.horizontal, 12) .padding(.vertical, 6) .background(Color(uiColor: .tertiarySystemGroupedBackground)) - .cornerRadius(18) .textSelection(.disabled) .onTapGesture { showJSON = true } .appSheet(isPresented: $showJSON) { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index e127ca86cf..7023449e9f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -132,7 +132,6 @@ struct CIRcvDecryptionError: View { .onTapGesture(perform: { onClick() }) .padding(.vertical, 6) .background(Color(uiColor: .tertiarySystemGroupedBackground)) - .cornerRadius(18) .textSelection(.disabled) } @@ -152,7 +151,6 @@ struct CIRcvDecryptionError: View { .onTapGesture(perform: { onClick() }) .padding(.vertical, 6) .background(Color(uiColor: .tertiarySystemGroupedBackground)) - .cornerRadius(18) .textSelection(.disabled) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift index 54356c5a6e..ed2340b6c4 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift @@ -25,7 +25,6 @@ struct DeletedItemView: View { .padding(.leading, 12) .padding(.vertical, 6) .background(chatItemFrameColor(chatItem, theme)) - .cornerRadius(18) .textSelection(.disabled) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index db1a74e213..5d6327139a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -76,7 +76,6 @@ struct FramedItemView: View { } } .background(chatItemFrameColorMaybeImageOrVideo(chatItem, theme)) - .cornerRadius(18) .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } if let (title, text) = chatItem.meta.itemStatus.statusInfo { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift index de71b1fa08..822dda4d06 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift @@ -70,7 +70,6 @@ struct CIMsgError: View { .padding(.leading, 12) .padding(.vertical, 6) .background(Color(uiColor: .tertiarySystemGroupedBackground)) - .cornerRadius(18) .textSelection(.disabled) .onTapGesture(perform: onTap) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift index b81f113d22..f8bd9156da 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift @@ -23,7 +23,6 @@ struct MarkedDeletedItemView: View { .padding(.horizontal, 12) .padding(.vertical, 6) .background(chatItemFrameColor(chatItem, theme)) - .cornerRadius(18) .textSelection(.disabled) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index a55b07495a..3312833ccc 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -232,7 +232,7 @@ struct ChatItemInfoView: View { .padding(.horizontal, 12) .padding(.vertical, 6) .background(chatItemFrameColor(ci, theme)) - .cornerRadius(18) + .modifier(ChatItemClipped()) .contextMenu { if itemVersion.msgContent.text != "" { Button { @@ -302,7 +302,7 @@ struct ChatItemInfoView: View { .padding(.horizontal, 12) .padding(.vertical, 6) .background(quotedMsgFrameColor(qi, theme)) - .cornerRadius(18) + .modifier(ChatItemClipped()) .contextMenu { if qi.text != "" { Button { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 34ad68f031..40c97202b4 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -754,6 +754,7 @@ struct ChatView: View { playbackState: $playbackState, playbackTime: $playbackTime ) + .modifier(ChatItemClipped(ci)) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } .accessibilityLabel("") if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 { diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 512aa9196a..d8a362e847 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -55,6 +55,7 @@ struct ReverseList: UIV // 1. Style tableView.separatorStyle = .none tableView.transform = .verticalFlip + tableView.backgroundColor = .clear // 2. Register cells if #available(iOS 16.0, *) { diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift new file mode 100644 index 0000000000..3e5200f281 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -0,0 +1,63 @@ +// +// ChatItemClipShape.swift +// SimpleX (iOS) +// +// Created by Levitating Pineapple on 04/07/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +/// Modifier, which provides clipping mask for ``ChatItemWithMenu`` view +/// and it's previews: (drag interaction, context menu, etc.) +/// Supports [Dynamic Type](https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically) +/// by retaining pill shape, even when ``ChatItem``'s height is less that twice its corner radius +struct ChatItemClipped: ViewModifier { + struct ClipShape: Shape { + let maxCornerRadius: Double + + func path(in rect: CGRect) -> Path { + Path( + roundedRect: rect, + cornerRadius: min((rect.height / 2), maxCornerRadius), + style: .circular + ) + } + } + + init() { + clipShape = ClipShape( + maxCornerRadius: 18 + ) + } + + init(_ chatItem: ChatItem) { + clipShape = ClipShape( + maxCornerRadius: { + switch chatItem.content { + case + .sndMsgContent, + .rcvMsgContent, + .sndDeleted, + .rcvDeleted, + .sndModerated, + .rcvModerated, + .rcvBlocked: 18 + default: 8 + } + }() + ) + } + + private let clipShape: ClipShape + + func body(content: Content) -> some View { + content + .contentShape(.dragPreview, clipShape) + .contentShape(.contextMenuPreview, clipShape) + .clipShape(clipShape) + } +} + + diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index f7ff222d28..1c5c032e11 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -195,6 +195,7 @@ 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */; }; 8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; }; 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; + CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; CEEA861D2C2ABCB50084E1EA /* ReverseList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEA861C2C2ABCB50084E1EA /* ReverseList.swift */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; }; @@ -501,6 +502,7 @@ 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeModeEditor.swift; sourceTree = ""; }; 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = ""; }; 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = ""; }; + CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = ""; }; CEEA861C2C2ABCB50084E1EA /* ReverseList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseList.swift; sourceTree = ""; }; D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = ""; }; 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; }; @@ -683,6 +685,7 @@ 8C7F8F0D2C19C0C100D16888 /* ViewModifiers.swift */, 8C74C3ED2C1B942300039E77 /* ChatWallpaper.swift */, 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */, + CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, ); path = Helpers; sourceTree = ""; @@ -1225,6 +1228,7 @@ 5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */, 5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */, D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */, + CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */, 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */, 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */, 5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */,