diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json new file mode 100644 index 0000000000..cb29f09fe1 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "vertical_logo_x1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "vertical_logo_x2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "vertical_logo_x3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png new file mode 100644 index 0000000000..f916e43ea9 Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png differ diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png new file mode 100644 index 0000000000..bb35878f0c Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png differ diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x3.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x3.png new file mode 100644 index 0000000000..c55f481b36 Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x3.png differ diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index e8b494724a..2ad8d546f2 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -74,7 +74,7 @@ struct ContentView: View { } } - @ViewBuilder func allViews() -> some View { + func allViews() -> some View { ZStack { let showCallArea = chatModel.activeCall != nil && chatModel.activeCall?.callState != .waitCapabilities && chatModel.activeCall?.callState != .invitationAccepted // contentView() has to be in a single branch, so that enabling authentication doesn't trigger re-rendering and close settings. @@ -209,7 +209,7 @@ struct ContentView: View { } } - @ViewBuilder private func activeCallInteractiveArea(_ call: Call) -> some View { + private func activeCallInteractiveArea(_ call: Call) -> some View { HStack { Text(call.contact.displayName).font(.body).foregroundColor(.white) Spacer() diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 7c8996a99b..ab7a47b944 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -467,7 +467,7 @@ struct ActiveCallOverlay: View { .disabled(call.initialCallType == .audio && client.activeCall?.peerHasOldVersion == true) } - @ViewBuilder private func flipCameraButton() -> some View { + private func flipCameraButton() -> some View { controlButton(call, "arrow.triangle.2.circlepath", padding: 12) { Task { if await WebRTCClient.isAuthorized(for: .video) { @@ -477,11 +477,11 @@ struct ActiveCallOverlay: View { } } - @ViewBuilder private func controlButton(_ call: Call, _ imageName: String, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { + private func controlButton(_ call: Call, _ imageName: String, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { callButton(imageName, call.peerMediaSources.hasVideo ? Color.black.opacity(0.2) : Color.white.opacity(0.2), padding: padding, perform) } - @ViewBuilder private func audioDevicePickerButton() -> some View { + private func audioDevicePickerButton() -> some View { AudioDevicePicker() .opacity(0.8) .scaleEffect(2) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift index 024aeed96a..0283e9c07e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift @@ -50,7 +50,7 @@ struct CICallItemView: View { Image(systemName: "phone.connection").foregroundColor(.green) } - @ViewBuilder private func endedCallIcon(_ sent: Bool) -> some View { + private func endedCallIcon(_ sent: Bool) -> some View { HStack { Image(systemName: "phone.down") Text(durationText(duration)).foregroundColor(theme.colors.secondary) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 4f879db426..273c9de408 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -46,7 +46,7 @@ struct CILinkView: View { func openBrowserAlert(uri: URL) { showAlert( - NSLocalizedString("Open in browser?", comment: "alert title"), + NSLocalizedString("Open link?", comment: "alert title"), message: uri.absoluteString, actions: {[ UIAlertAction( diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index e26fb62a71..4e5713c263 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -68,7 +68,7 @@ struct CIRcvDecryptionError: View { } } - @ViewBuilder private func viewBody() -> some View { + private func viewBody() -> some View { Group { if case let .direct(contact) = chat.chatInfo, let contactStats = contact.activeConn?.connectionStats { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 632d4196c2..b27d266d8a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -87,7 +87,7 @@ struct FramedItemView: View { .overlay(DetermineWidth()) .accessibilityLabel("") } - } + } .background { chatItemFrameColorMaybeImageOrVideo(chatItem, theme).modifier(ChatTailPadding()) } .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } @@ -201,6 +201,7 @@ struct FramedItemView: View { } @ViewBuilder private func ciQuoteView(_ qi: CIQuote) -> some View { + let backgroundColor = chatItemFrameContextColor(chatItem, theme) let v = ZStack(alignment: .topTrailing) { switch (qi.content) { case let .image(_, image): @@ -242,7 +243,8 @@ struct FramedItemView: View { // if enable this always, size of the framed voice message item will be incorrect after end of playback .overlay { if case .voice = chatItem.content.msgContent {} else { DetermineWidth() } } .frame(minWidth: msgWidth, alignment: .leading) - .background(chatItemFrameContextColor(chatItem, theme)) + .background(backgroundColor) + .environment(\.containerBackground, UIColor(backgroundColor)) if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth { v.frame(maxWidth: mediaWidth, alignment: .leading) } else { @@ -308,6 +310,7 @@ struct FramedItemView: View { rightToLeft: rtl, prefix: txtPrefix ) + .environment(\.containerBackground, UIColor(chatItemFrameColor(ci, theme))) .multilineTextAlignment(rtl ? .trailing : .leading) .padding(.vertical, 6) .padding(.horizontal, 12) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index d8dbd673f4..aab4177cbf 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -26,6 +26,7 @@ private func typing(_ theme: AppTheme, _ descr: UIFontDescriptor, _ ws: [UIFont. struct MsgContentView: View { @ObservedObject var chat: Chat @Environment(\.showTimestamp) var showTimestamp: Bool + @Environment(\.containerBackground) var containerBackground: UIColor @EnvironmentObject var theme: AppTheme var text: String var formattedText: [FormattedText]? = nil @@ -92,7 +93,8 @@ struct MsgContentView: View { @inline(__always) private func msgContentView() -> some View { - let s = messageText(text, formattedText, textStyle: textStyle, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary, prefix: prefix) + let r = messageText(text, formattedText, textStyle: textStyle, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: containerBackground, prefix: prefix) + let s = r.string let t: Text if let mt = meta { if mt.isLive { @@ -102,7 +104,7 @@ struct MsgContentView: View { } else { t = Text(AttributedString(s)) } - return t.overlay(handleTextLinks(s, showSecrets: $showSecrets)) + return msgTextResultView(r, t, showSecrets: $showSecrets) } @inline(__always) @@ -118,7 +120,13 @@ struct MsgContentView: View { } } -func handleTextLinks(_ s: NSAttributedString, showSecrets: Binding>? = nil) -> some View { +func msgTextResultView(_ r: MsgTextResult, _ t: Text, showSecrets: Binding>? = nil) -> some View { + t.if(r.hasSecrets, transform: hiddenSecretsView) + .if(r.handleTaps) { $0.overlay(handleTextTaps(r.string, showSecrets: showSecrets)) } +} + +@inline(__always) +private func handleTextTaps(_ s: NSAttributedString, showSecrets: Binding>? = nil) -> some View { return GeometryReader { g in Rectangle() .fill(Color.clear) @@ -174,13 +182,43 @@ func handleTextLinks(_ s: NSAttributedString, showSecrets: Binding>? = } } +func hiddenSecretsView(_ v: V) -> some View { + v.overlay( + GeometryReader { g in + let size = (g.size.width + g.size.height) / 1.4142 + Image("vertical_logo") + .resizable(resizingMode: .tile) + .frame(width: size, height: size) + .rotationEffect(.degrees(45), anchor: .center) + .position(x: g.size.width / 2, y: g.size.height / 2) + .clipped() + .saturation(0.65) + .opacity(0.35) + } + .mask(v) + ) +} + private let linkAttrKey = NSAttributedString.Key("chat.simplex.app.link") private let webLinkAttrKey = NSAttributedString.Key("chat.simplex.app.webLink") private let secretAttrKey = NSAttributedString.Key("chat.simplex.app.secret") -func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: UIFont.TextStyle = .body, sender: String?, preview: Bool = false, mentions: [String: CIMention]?, userMemberId: String?, showSecrets: Set?, secondaryColor: Color, prefix: NSAttributedString? = nil) -> NSMutableAttributedString { +typealias MsgTextResult = (string: NSMutableAttributedString, hasSecrets: Bool, handleTaps: Bool) + +func messageText( + _ text: String, + _ formattedText: [FormattedText]?, + textStyle: UIFont.TextStyle = .body, + sender: String?, + preview: Bool = false, + mentions: [String: CIMention]?, + userMemberId: String?, + showSecrets: Set?, + backgroundColor: UIColor, + prefix: NSAttributedString? = nil +) -> MsgTextResult { let res = NSMutableAttributedString() let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle) let font = UIFont.preferredFont(forTextStyle: textStyle) @@ -188,7 +226,10 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U .font: font, .foregroundColor: UIColor.label ] + let secretColor = backgroundColor.withAlphaComponent(1) var link: [NSAttributedString.Key: Any]? + var hasSecrets = false + var handleTaps = false if let sender { if preview { @@ -230,14 +271,16 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U if let showSecrets { if !showSecrets.contains(secretIdx) { attrs[.foregroundColor] = UIColor.clear - attrs[.backgroundColor] = UIColor.secondarySystemFill // secretColor + attrs[.backgroundColor] = secretColor } attrs[secretAttrKey] = secretIdx secretIdx += 1 + handleTaps = true } else { attrs[.foregroundColor] = UIColor.clear - attrs[.backgroundColor] = UIColor.secondarySystemFill + attrs[.backgroundColor] = secretColor } + hasSecrets = true case let .colored(color): if let c = color.uiColor { attrs[.foregroundColor] = UIColor(c) @@ -247,11 +290,13 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U if !preview { attrs[linkAttrKey] = NSURL(string: ft.text) attrs[webLinkAttrKey] = true + handleTaps = true } case let .simplexLink(linkType, simplexUri, smpHosts): attrs = linkAttrs() if !preview { attrs[linkAttrKey] = NSURL(string: simplexUri) + handleTaps = true } if case .description = privacySimplexLinkModeDefault.get() { t = simplexLinkText(linkType, smpHosts) @@ -278,11 +323,13 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U attrs = linkAttrs() if !preview { attrs[linkAttrKey] = NSURL(string: "mailto:" + ft.text) + handleTaps = true } case .phone: attrs = linkAttrs() if !preview { attrs[linkAttrKey] = NSURL(string: "tel:" + t.replacingOccurrences(of: " ", with: "")) + handleTaps = true } case .none: () } @@ -292,7 +339,7 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U res.append(NSMutableAttributedString(string: text, attributes: plain)) } - return res + return (string: res, hasSecrets: hasSecrets, handleTaps: handleTaps) func linkAttrs() -> [NSAttributedString.Key: Any] { link = link ?? [ diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 587957cd5d..dfc620c402 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -41,7 +41,7 @@ struct ChatItemForwardingView: View { .alert(item: $alert) { $0.alert } } - @ViewBuilder private func forwardListView() -> some View { + private func forwardListView() -> some View { VStack(alignment: .leading) { if !chatsToForwardTo.isEmpty { List { diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index 1b840a1547..cd75d1b0cd 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -131,9 +131,9 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func details() -> some View { + private func details() -> some View { let meta = ci.meta - VStack(alignment: .leading, spacing: 16) { + return VStack(alignment: .leading, spacing: 16) { Text(title) .font(.largeTitle) .bold() @@ -197,7 +197,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func historyTab() -> some View { + private func historyTab() -> some View { GeometryReader { g in let maxWidth = (g.size.width - 32) * 0.84 ScrollView { @@ -227,12 +227,13 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { - VStack(alignment: .leading, spacing: 4) { - textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil) + private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { + let backgroundColor = chatItemFrameColor(ci, theme) + return VStack(alignment: .leading, spacing: 4) { + textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil, backgroundColor: UIColor(backgroundColor)) .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(ci, theme)) + .background(backgroundColor) .modifier(ChatItemClipped()) .contextMenu { if itemVersion.msgContent.text != "" { @@ -257,9 +258,9 @@ struct ChatItemInfoView: View { .frame(maxWidth: maxWidth, alignment: .leading) } - @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil) -> some View { + @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil, backgroundColor: UIColor) -> some View { if text != "" { - TextBubble(text: text, formattedText: formattedText, sender: sender, mentions: ci.mentions, userMemberId: userMemberId) + TextBubble(text: text, formattedText: formattedText, sender: sender, mentions: ci.mentions, userMemberId: userMemberId, backgroundColor: backgroundColor) } else { Text("no text") .italic() @@ -274,15 +275,16 @@ struct ChatItemInfoView: View { var sender: String? = nil var mentions: [String: CIMention]? var userMemberId: String? + var backgroundColor: UIColor @State private var showSecrets: Set = [] var body: some View { - let s = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary) - Text(AttributedString(s)).overlay(handleTextLinks(s, showSecrets: $showSecrets)) + let r = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: backgroundColor) + return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) } } - @ViewBuilder private func quoteTab(_ qi: CIQuote) -> some View { + private func quoteTab(_ qi: CIQuote) -> some View { GeometryReader { g in let maxWidth = (g.size.width - 32) * 0.84 ScrollView { @@ -300,9 +302,10 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { - VStack(alignment: .leading, spacing: 4) { - textBubble(qi.text, qi.formattedText, qi.getSender(nil)) + private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { + let backgroundColor = quotedMsgFrameColor(qi, theme) + return VStack(alignment: .leading, spacing: 4) { + textBubble(qi.text, qi.formattedText, qi.getSender(nil), backgroundColor: UIColor(backgroundColor)) .padding(.horizontal, 12) .padding(.vertical, 6) .background(quotedMsgFrameColor(qi, theme)) @@ -335,7 +338,7 @@ struct ChatItemInfoView: View { : theme.appColors.receivedMessage } - @ViewBuilder private func forwardedFromTab(_ forwardedFromItem: AChatItem) -> some View { + private func forwardedFromTab(_ forwardedFromItem: AChatItem) -> some View { ScrollView { VStack(alignment: .leading, spacing: 16) { details() @@ -373,7 +376,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func forwardedFromSender(_ forwardedFromItem: AChatItem) -> some View { + private func forwardedFromSender(_ forwardedFromItem: AChatItem) -> some View { HStack { ChatInfoImage(chat: Chat(chatInfo: forwardedFromItem.chatInfo), size: 48) .padding(.trailing, 6) @@ -404,7 +407,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func deliveryTab(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { + private func deliveryTab(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { ScrollView { VStack(alignment: .leading, spacing: 16) { details() @@ -419,7 +422,7 @@ struct ChatItemInfoView: View { .frame(maxHeight: .infinity, alignment: .top) } - @ViewBuilder private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { + private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { LazyVStack(alignment: .leading, spacing: 12) { let mss = membersStatuses(memberDeliveryStatuses) if !mss.isEmpty { diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 5d09e153d5..f5558bcd93 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -18,6 +18,10 @@ extension EnvironmentValues { static let defaultValue: Bool = true } + struct ContainerBackground: EnvironmentKey { + static let defaultValue: UIColor = .clear + } + var showTimestamp: Bool { get { self[ShowTimestamp.self] } set { self[ShowTimestamp.self] = newValue } @@ -27,6 +31,11 @@ extension EnvironmentValues { get { self[Revealed.self] } set { self[Revealed.self] = newValue } } + + var containerBackground: UIColor { + get { self[ContainerBackground.self] } + set { self[ContainerBackground.self] = newValue } + } } struct ChatItemView: View { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 1349996683..fc80eb6dec 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -71,10 +71,9 @@ struct ChatView: View { } } - @ViewBuilder private var viewBody: some View { let cInfo = chat.chatInfo - ZStack { + return ZStack { let wallpaperImage = theme.wallpaper.type.image let wallpaperType = theme.wallpaper.type let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background) @@ -1528,9 +1527,9 @@ struct ChatView: View { } } - @ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat, _ itemSeparation: ItemSeparation) -> some View { + func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat, _ itemSeparation: ItemSeparation) -> some View { let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading - VStack(alignment: alignment.horizontal, spacing: 3) { + return VStack(alignment: alignment.horizontal, spacing: 3) { HStack { if ci.chatDir.sent { goToItemButton(true) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index d45cc9abc4..845442c75f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -70,8 +70,10 @@ struct ContextItemView: View { .lineLimit(lines) } - private func contextMsgPreview(_ contextItem: ChatItem) -> Text { - return attachment() + Text(AttributedString(messageText(contextItem.text, contextItem.formattedText, sender: nil, preview: true, mentions: contextItem.mentions, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary))) + private func contextMsgPreview(_ contextItem: ChatItem) -> some View { + let r = messageText(contextItem.text, contextItem.formattedText, sender: nil, preview: true, mentions: contextItem.mentions, userMemberId: nil, showSecrets: nil, backgroundColor: UIColor(background)) + let t = attachment() + Text(AttributedString(r.string)) + return t.if(r.hasSecrets, transform: hiddenSecretsView) func attachment() -> Text { let isFileLoaded = if let fileSource = getLoadedFileSource(contextItem.file) { diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 66fe67a29e..7cd543af10 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -145,9 +145,9 @@ struct AddGroupMembersViewCommon: View { return dummy }() - @ViewBuilder private func inviteMembersButton() -> some View { + private func inviteMembersButton() -> some View { let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Invite to group" : "Invite to chat" - Button { + return Button { inviteMembers() } label: { HStack { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 9fa07bc391..96a4981be0 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -292,9 +292,9 @@ struct GroupChatInfoView: View { .disabled(!groupInfo.ready || chat.chatItems.isEmpty) } - @ViewBuilder private func addMembersActionButton(width: CGFloat) -> some View { - if chat.chatInfo.incognito { - ZStack { + private func addMembersActionButton(width: CGFloat) -> some View { + ZStack { + if chat.chatInfo.incognito { InfoViewButton(image: "link.badge.plus", title: "invite", width: width) { groupLinkNavLinkActive = true } @@ -306,10 +306,7 @@ struct GroupChatInfoView: View { } .frame(width: 1, height: 1) .hidden() - } - .disabled(!groupInfo.ready) - } else { - ZStack { + } else { InfoViewButton(image: "person.fill.badge.plus", title: "invite", width: width) { addMembersNavLinkActive = true } @@ -322,8 +319,8 @@ struct GroupChatInfoView: View { .frame(width: 1, height: 1) .hidden() } - .disabled(!groupInfo.ready) } + .disabled(!groupInfo.ready) } private func muteButton(width: CGFloat, nextNtfMode: MsgFilter) -> some View { @@ -569,9 +566,9 @@ struct GroupChatInfoView: View { } } - @ViewBuilder private func leaveGroupButton() -> some View { + private func leaveGroupButton() -> some View { let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group" : "Leave chat" - Button(role: .destructive) { + return Button(role: .destructive) { alert = .leaveGroupAlert } label: { Label(label, systemImage: "rectangle.portrait.and.arrow.right") diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 4dd2b9e683..97bff70efb 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -18,6 +18,7 @@ struct GroupWelcomeView: View { @State private var editMode = true @FocusState private var keyboardVisible: Bool @State private var showSaveDialog = false + @State private var showSecrets: Set = [] let maxByteCount = 1200 @@ -58,9 +59,8 @@ struct GroupWelcomeView: View { } private func textPreview() -> some View { - let s = messageText(welcomeText, parseSimpleXMarkdown(welcomeText), sender: nil, mentions: nil, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary) - return Text(AttributedString(s)) - .overlay(handleTextLinks(s)) + let r = messageText(welcomeText, parseSimpleXMarkdown(welcomeText), sender: nil, mentions: nil, userMemberId: nil, showSecrets: showSecrets, backgroundColor: UIColor(theme.colors.background)) + return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) .frame(minHeight: 130, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index f5234ed331..f9cf5e98e4 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -90,7 +90,7 @@ struct ChatListNavLink: View { .actionSheet(item: $actionSheet) { $0.actionSheet } } - @ViewBuilder private func contactNavLink(_ contact: Contact) -> some View { + private func contactNavLink(_ contact: Contact) -> some View { Group { if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) @@ -243,7 +243,7 @@ struct ChatListNavLink: View { } } - @ViewBuilder private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View { + private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View { NavLinkPlain( chatId: chat.chatInfo.id, selection: $chatModel.chatId, diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 93c18f28cc..5c491b6303 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -335,9 +335,9 @@ struct ChatListView: View { } } - @ViewBuilder private var chatList: some View { + private var chatList: some View { let cs = filteredChats() - ZStack { + return ZStack { ScrollViewReader { scrollProxy in List { if !chatModel.chats.isEmpty { @@ -804,7 +804,7 @@ struct TagsView: View { } } - @ViewBuilder private func expandedPresetTagsFiltersView() -> some View { + private func expandedPresetTagsFiltersView() -> some View { ForEach(PresetTag.allCases, id: \.id) { tag in if (chatTagsModel.presetTags[tag] ?? 0) > 0 { expandedTagFilterView(tag) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index c4b584061d..b8c8233e6e 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -187,13 +187,14 @@ struct ChatPreviewView: View { .kerning(-2) } - private func chatPreviewLayout(_ text: Text?, draft: Bool = false, _ hasFilePreview: Bool = false) -> some View { + private func chatPreviewLayout(_ text: Text?, draft: Bool = false, hasFilePreview: Bool = false, hasSecrets: Bool) -> some View { ZStack(alignment: .topTrailing) { let s = chat.chatStats let mentionWidth: CGFloat = if s.unreadMentions > 0 && s.unreadCount > 1 { dynamicSize(userFont).unreadCorner } else { 0 } let t = text .lineLimit(userFont <= .xxxLarge ? 2 : 1) .multilineTextAlignment(.leading) + .if(hasSecrets, transform: hiddenSecretsView) .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.leading, hasFilePreview ? 0 : 8) .padding(.trailing, mentionWidth + (hasFilePreview ? 38 : 36)) @@ -259,11 +260,13 @@ struct ChatPreviewView: View { } } - private func messageDraft(_ draft: ComposeState) -> Text { + private func messageDraft(_ draft: ComposeState) -> (Text, Bool) { let msg = draft.message - return image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) - + attachment() - + Text(AttributedString(messageText(msg, parseSimpleXMarkdown(msg), sender: nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary))) + let r = messageText(msg, parseSimpleXMarkdown(msg), sender: nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: nil, backgroundColor: UIColor(theme.colors.background)) + return (image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) + + attachment() + + Text(AttributedString(r.string)), + r.hasSecrets) func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text { Text(Image(systemName: s)).foregroundColor(color) + textSpace @@ -279,10 +282,11 @@ struct ChatPreviewView: View { } } - func chatItemPreview(_ cItem: ChatItem) -> Text { + func chatItemPreview(_ cItem: ChatItem) -> (Text, Bool) { let itemText = cItem.meta.itemDeleted == nil ? cItem.text : markedDeletedText() let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil - return Text(AttributedString(messageText(itemText, itemFormattedText, sender: cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, secondaryColor: theme.colors.secondary, prefix: prefix()))) + let r = messageText(itemText, itemFormattedText, sender: cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, backgroundColor: UIColor(theme.colors.background), prefix: prefix()) + return (Text(AttributedString(r.string)), r.hasSecrets) // same texts are in markedDeletedText in MarkedDeletedItemView, but it returns LocalizedStringKey; // can be refactored into a single function if functions calling these are changed to return same type @@ -319,9 +323,11 @@ struct ChatPreviewView: View { @ViewBuilder private func chatMessagePreview(_ cItem: ChatItem?, _ hasFilePreview: Bool = false) -> some View { if chatModel.draftChatId == chat.id, let draft = chatModel.draft { - chatPreviewLayout(messageDraft(draft), draft: true, hasFilePreview) + let (t, hasSecrets) = messageDraft(draft) + chatPreviewLayout(t, draft: true, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) } else if let cItem = cItem { - chatPreviewLayout(itemStatusMark(cItem) + chatItemPreview(cItem), hasFilePreview) + let (t, hasSecrets) = chatItemPreview(cItem) + chatPreviewLayout(itemStatusMark(cItem) + t, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) } else { switch (chat.chatInfo) { case let .direct(contact): @@ -399,7 +405,7 @@ struct ChatPreviewView: View { : chatPreviewInfoText("you are invited to group") } - @ViewBuilder private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View { + private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View { Text(text) .frame(maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading) .padding([.leading, .trailing], 8) @@ -479,7 +485,7 @@ struct ChatPreviewView: View { } } -@ViewBuilder func groupReportsIcon(size: CGFloat) -> some View { +func groupReportsIcon(size: CGFloat) -> some View { Image(systemName: "flag") .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 4dbdc81620..8b0a8af888 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -245,7 +245,7 @@ struct ServersSummaryView: View { } } - @ViewBuilder private func smpServersListView( + private func smpServersListView( _ servers: [SMPServerSummary], _ statsStartedAt: Date, _ header: LocalizedStringKey? = nil, @@ -256,7 +256,7 @@ struct ServersSummaryView: View { ? serverAddress($0.smpServer) < serverAddress($1.smpServer) : $0.hasSubs && !$1.hasSubs } - Section { + return Section { ForEach(sortedServers) { server in smpServerView(server, statsStartedAt) } @@ -318,14 +318,14 @@ struct ServersSummaryView: View { return onionHosts == .require ? .indigo : .accentColor } - @ViewBuilder private func xftpServersListView( + private func xftpServersListView( _ servers: [XFTPServerSummary], _ statsStartedAt: Date, _ header: LocalizedStringKey? = nil, _ footer: LocalizedStringKey? = nil ) -> some View { let sortedServers = servers.sorted { serverAddress($0.xftpServer) < serverAddress($1.xftpServer) } - Section { + return Section { ForEach(sortedServers) { server in xftpServerView(server, statsStartedAt) } diff --git a/apps/ios/Shared/Views/ChatList/TagListView.swift b/apps/ios/Shared/Views/ChatList/TagListView.swift index 74ed9534e0..2063fe15de 100644 --- a/apps/ios/Shared/Views/ChatList/TagListView.swift +++ b/apps/ios/Shared/Views/ChatList/TagListView.swift @@ -138,7 +138,7 @@ struct TagListView: View { } } - @ViewBuilder private func radioButton(selected: Bool) -> some View { + private func radioButton(selected: Bool) -> some View { Image(systemName: selected ? "checkmark.circle.fill" : "circle") .imageScale(.large) .foregroundStyle(selected ? Color.accentColor : Color(.tertiaryLabel)) diff --git a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift index fe840006cd..456c46d318 100644 --- a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift +++ b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift @@ -140,9 +140,9 @@ struct ContactListNavLink: View { } } - @ViewBuilder private func previewTitle(_ contact: Contact, titleColor: Color) -> some View { + private func previewTitle(_ contact: Contact, titleColor: Color) -> some View { let t = Text(chat.chatInfo.chatViewName).foregroundColor(titleColor) - ( + return ( contact.verified == true ? verifiedIcon + t : t diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift index 1ded0acc90..02a1b87826 100644 --- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift @@ -28,7 +28,7 @@ struct DatabaseErrorView: View { } } - @ViewBuilder private func databaseErrorView() -> some View { + private func databaseErrorView() -> some View { VStack(alignment: .center, spacing: 20) { switch status { case let .errorNotADatabase(dbFile): diff --git a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift index 609943bcb6..4a6f8e7549 100644 --- a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift +++ b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift @@ -28,7 +28,7 @@ struct PasscodeEntry: View { } } - @ViewBuilder private func passwordView() -> some View { + private func passwordView() -> some View { Text( password == "" ? " " diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index 39656c1534..e5263813fa 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -85,7 +85,7 @@ struct NewChatSheet: View { } } - @ViewBuilder private func viewBody(_ showArchive: Bool) -> some View { + private func viewBody(_ showArchive: Bool) -> some View { List { HStack { ContactsListSearchBar( @@ -258,7 +258,7 @@ struct ContactsList: View { } } - @ViewBuilder private func noResultSection(text: String) -> some View { + private func noResultSection(text: String) -> some View { Section { Text(text) .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 2524b5e682..110eda7882 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -506,7 +506,7 @@ private struct ActiveProfilePicker: View { } } - @ViewBuilder private func profilerPickerUserOption(_ user: User) -> some View { + private func profilerPickerUserOption(_ user: User) -> some View { Button { if selectedProfile == user && incognitoEnabled { incognitoEnabled = false diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 45ef186671..17e1735472 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -304,11 +304,11 @@ struct ChooseServerOperators: View { } } - @ViewBuilder private func operatorCheckView(_ serverOperator: ServerOperator) -> some View { + private func operatorCheckView(_ serverOperator: ServerOperator) -> some View { let checked = selectedOperatorIds.contains(serverOperator.operatorId) let icon = checked ? "checkmark.circle.fill" : "circle" let iconColor = checked ? theme.colors.primary : Color(uiColor: .tertiaryLabel).asAnotherColorFromSecondary(theme) - HStack(spacing: 10) { + return HStack(spacing: 10) { Image(serverOperator.largeLogo(colorScheme)) .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index 24da6a94a8..afbccc109c 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -38,9 +38,9 @@ struct OperatorView: View { .allowsHitTesting(!testing) } - @ViewBuilder private func operatorView() -> some View { + private func operatorView() -> some View { let duplicateHosts = findDuplicateHosts(serverErrors) - VStack { + return VStack { List { Section { infoViewLink() @@ -500,14 +500,14 @@ struct SingleOperatorUsageConditionsView: View { } } - @ViewBuilder private func acceptConditionsButton() -> some View { + private func acceptConditionsButton() -> some View { let operatorIds = ChatModel.shared.conditions.serverOperators .filter { $0.operatorId == userServers[operatorIndex].operator_.operatorId || // Opened operator ($0.enabled && !$0.conditionsAcceptance.conditionsAccepted) // Other enabled operators with conditions not accepted } .map { $0.operatorId } - Button { + return Button { acceptForOperators(operatorIds, operatorIndex) } label: { Text("Accept conditions") diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift index ed3c5c773c..b9737914ec 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift @@ -38,9 +38,9 @@ struct YourServersView: View { .allowsHitTesting(!testing) } - @ViewBuilder private func yourServersView() -> some View { + private func yourServersView() -> some View { let duplicateHosts = findDuplicateHosts(serverErrors) - List { + return List { if !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty { Section { ForEach($userServers[operatorIndex].smpServers) { srv in diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 3321b25793..e06b1c4dd3 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -280,159 +280,159 @@ struct SettingsView: View { } } - @ViewBuilder func settingsView() -> some View { - let user = chatModel.currentUser - List { - Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { - NavigationLink { - NotificationsView() - .navigationTitle("Notifications") - .modifier(ThemedBackground(grouped: true)) - } label: { - HStack { - notificationsIcon() - Text("Notifications") - } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - NetworkAndServers() - .navigationTitle("Network & servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - CallSettings() - .navigationTitle("Your calls") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - PrivacySettings() - .navigationTitle("Your privacy") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") } - } - .disabled(chatModel.chatRunning != true) - - if UIApplication.shared.supportsAlternateIcons { - NavigationLink { - AppearanceSettings() - .navigationTitle("Appearance") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") } - } - .disabled(chatModel.chatRunning != true) + func settingsView() -> some View { + List { + let user = chatModel.currentUser + Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { + NavigationLink { + NotificationsView() + .navigationTitle("Notifications") + .modifier(ThemedBackground(grouped: true)) + } label: { + HStack { + notificationsIcon() + Text("Notifications") } } + .disabled(chatModel.chatRunning != true) - Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) { - chatDatabaseRow() - NavigationLink { - MigrateFromDevice(showProgressOnSettings: $showProgress) - .toolbar { - // Redaction broken for `.navigationTitle` - using a toolbar item instead. - ToolbarItem(placement: .principal) { - Text("Migrate device").font(.headline) - } - } - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } - } + NavigationLink { + NetworkAndServers() + .navigationTitle("Network & servers") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } } - - Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { - if let user = user { - NavigationLink { - ChatHelp(dismissSettingsSheet: dismiss) - .navigationTitle("Welcome \(user.displayName)!") - .modifier(ThemedBackground()) - .frame(maxHeight: .infinity, alignment: .top) - } label: { - settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") } - } - } + .disabled(chatModel.chatRunning != true) + + NavigationLink { + CallSettings() + .navigationTitle("Your calls") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") } + } + .disabled(chatModel.chatRunning != true) + + NavigationLink { + PrivacySettings() + .navigationTitle("Your privacy") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") } + } + .disabled(chatModel.chatRunning != true) + + if UIApplication.shared.supportsAlternateIcons { NavigationLink { - WhatsNewView(viaSettings: true, updatedConditions: false) - .modifier(ThemedBackground()) - .navigationBarTitleDisplayMode(.inline) + AppearanceSettings() + .navigationTitle("Appearance") + .modifier(ThemedBackground(grouped: true)) } label: { - settingsRow("plus", color: theme.colors.secondary) { Text("What's new") } + settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") } } + .disabled(chatModel.chatRunning != true) + } + } + + Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) { + chatDatabaseRow() + NavigationLink { + MigrateFromDevice(showProgressOnSettings: $showProgress) + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Migrate device").font(.headline) + } + } + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } + } + } + + Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { + if let user = user { NavigationLink { - SimpleXInfo(onboarding: false) - .navigationBarTitle("", displayMode: .inline) + ChatHelp(dismissSettingsSheet: dismiss) + .navigationTitle("Welcome \(user.displayName)!") .modifier(ThemedBackground()) .frame(maxHeight: .infinity, alignment: .top) } label: { - settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") } + settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") } } - settingsRow("number", color: theme.colors.secondary) { - Button("Send questions and ideas") { - dismiss() - DispatchQueue.main.async { - UIApplication.shared.open(simplexTeamURL) - } + } + NavigationLink { + WhatsNewView(viaSettings: true, updatedConditions: false) + .modifier(ThemedBackground()) + .navigationBarTitleDisplayMode(.inline) + } label: { + settingsRow("plus", color: theme.colors.secondary) { Text("What's new") } + } + NavigationLink { + SimpleXInfo(onboarding: false) + .navigationBarTitle("", displayMode: .inline) + .modifier(ThemedBackground()) + .frame(maxHeight: .infinity, alignment: .top) + } label: { + settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") } + } + settingsRow("number", color: theme.colors.secondary) { + Button("Send questions and ideas") { + dismiss() + DispatchQueue.main.async { + UIApplication.shared.open(simplexTeamURL) } } - .disabled(chatModel.chatRunning != true) - settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") } } + .disabled(chatModel.chatRunning != true) + settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") } + } - Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) { - settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") } - settingsRow("star", color: theme.colors.secondary) { - Button("Rate the app") { - if let scene = sceneDelegate.windowScene { - SKStoreReviewController.requestReview(in: scene) - } + Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) { + settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") } + settingsRow("star", color: theme.colors.secondary) { + Button("Rate the app") { + if let scene = sceneDelegate.windowScene { + SKStoreReviewController.requestReview(in: scene) } } - ZStack(alignment: .leading) { - Image(colorScheme == .dark ? "github_light" : "github") - .resizable() - .frame(width: 24, height: 24) - .opacity(0.5) - .colorMultiply(theme.colors.secondary) - Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)") - .padding(.leading, indent) - } } + ZStack(alignment: .leading) { + Image(colorScheme == .dark ? "github_light" : "github") + .resizable() + .frame(width: 24, height: 24) + .opacity(0.5) + .colorMultiply(theme.colors.secondary) + Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)") + .padding(.leading, indent) + } + } - Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) { - NavigationLink { - DeveloperView() - .navigationTitle("Developer tools") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") } - } - NavigationLink { - VersionView() - .navigationBarTitle("App version") - .modifier(ThemedBackground()) - } label: { - Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") - } + Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) { + NavigationLink { + DeveloperView() + .navigationTitle("Developer tools") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") } + } + NavigationLink { + VersionView() + .navigationBarTitle("App version") + .modifier(ThemedBackground()) + } label: { + Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") } } - .navigationTitle("Your settings") - .modifier(ThemedBackground(grouped: true)) - .onDisappear { - chatModel.showingTerminal = false - chatModel.terminalItems = [] - } + } + .navigationTitle("Your settings") + .modifier(ThemedBackground(grouped: true)) + .onDisappear { + chatModel.showingTerminal = false + chatModel.terminalItems = [] + } } private func chatDatabaseRow() -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 8a70efbe82..9aa42930bf 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -133,7 +133,6 @@ struct UserProfile: View { .alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) } } - @ViewBuilder private func overlayButton( _ systemName: String, edge: Edge.Set, diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 781ea4bc34..887023b670 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -221,11 +221,11 @@ struct UserProfilesView: View { !user.hidden ? nil : trimmedSearchTextOrPassword } - @ViewBuilder private func profileActionView(_ action: UserProfileAction) -> some View { + private func profileActionView(_ action: UserProfileAction) -> some View { let passwordValid = actionPassword == actionPassword.trimmingCharacters(in: .whitespaces) let passwordField = PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid) let actionEnabled: (User) -> Bool = { user in actionPassword != "" && passwordValid && correctPassword(user, actionPassword) } - List { + return List { switch action { case let .deleteUser(user, delSMPQueues): actionHeader("Delete profile", user) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 926eb7e20f..56553b3283 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -5290,8 +5290,8 @@ Requires compatible VPN. Отвори група No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 32403dba44..21bf0aef60 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -5093,8 +5093,8 @@ Vyžaduje povolení sítě VPN. Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index b4751e1014..6e834157df 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -5572,8 +5572,8 @@ Dies erfordert die Aktivierung eines VPNs. Gruppe öffnen No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index a4a0c07410..641af86c2a 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -5573,9 +5573,9 @@ Requires compatible VPN. Open group No comment provided by engineer. - - Open in browser? - Open in browser? + + Open link? + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 849cff5ebc..f5226df190 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -5572,8 +5572,8 @@ Requiere activación de la VPN. Grupo abierto No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 7e493d5f73..5281fbc701 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -5069,8 +5069,8 @@ Edellyttää VPN:n sallimista. Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 80d5ae5f55..1e5da0b0ed 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -5537,8 +5537,8 @@ Nécessite l'activation d'un VPN. Ouvrir le groupe No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 3d3d8c6383..c8a29ede41 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -5572,8 +5572,8 @@ VPN engedélyezése szükséges. Csoport megnyitása No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index c672096082..1bfccb3b06 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -5572,8 +5572,8 @@ Richiede l'attivazione della VPN. Apri gruppo No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 4dd0d467a1..26f415dd13 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -5146,8 +5146,8 @@ VPN を有効にする必要があります。 Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 43d568f747..681502e255 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -5572,8 +5572,8 @@ Vereist het inschakelen van VPN. Open groep No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index f8a73a220d..01bc0b8508 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -5449,8 +5449,8 @@ Wymaga włączenia VPN. Grupa otwarta No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 2b4a426130..a7b63e38ba 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -5538,8 +5538,8 @@ Requires compatible VPN. Открыть группу No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 0398c37c8c..be68dc9780 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -5048,8 +5048,8 @@ Requires compatible VPN. Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index b1f65a1791..6eb1daf84b 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -5462,8 +5462,8 @@ VPN'nin etkinleştirilmesi gerekir. Grubu aç No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 784505ee62..7c8c6f4254 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -5479,8 +5479,8 @@ Requires compatible VPN. Відкрита група No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index a1ca7d430a..31a333085f 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -5373,8 +5373,8 @@ Requires compatible VPN. 打开群 No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift index f2b9de9f72..07180ffa1b 100644 --- a/apps/ios/SimpleX SE/ShareView.swift +++ b/apps/ios/SimpleX SE/ShareView.swift @@ -160,7 +160,7 @@ struct ShareView: View { } } - @ViewBuilder private func linkPreview(_ linkPreview: LinkPreview) -> some View { + private func linkPreview(_ linkPreview: LinkPreview) -> some View { previewArea { HStack(alignment: .center, spacing: 8) { if let uiImage = imageFromBase64(linkPreview.image) {