From e3ed7f68a2d9efabe2688206e803e488f6f34527 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 6 Aug 2024 16:50:33 +0300 Subject: [PATCH] ios: invert swipe actions in oneHandUI mode (#4596) * add swipe label * minor * adjust font * dynamic type * limit use to oneHandUI * icon size * fix offset * change font style --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../Views/ChatList/ChatListNavLink.swift | 54 ++++++++----- .../ios/Shared/Views/Helpers/SwipeLabel.swift | 80 +++++++++++++++++++ apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + 3 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/SwipeLabel.swift diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 0668d64441..b7ae298bc7 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -56,6 +56,8 @@ struct ChatListNavLink: View { @State private var inProgress = false @State private var progressByTimeout = false + @AppStorage(DEFAULT_ONE_HAND_UI) private var oneHandUI = false + var dynamicRowHeight: CGFloat { dynamicSizes[userFont]?.rowHeight ?? 80 } var body: some View { @@ -102,7 +104,7 @@ struct ChatListNavLink: View { showSheetContent: { sheet = $0 } ) } label: { - Label("Delete", systemImage: "trash") + deleteLabel } .tint(.red) } @@ -120,7 +122,7 @@ struct ChatListNavLink: View { .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() - ToggleNtfsButton(chat: chat) + toggleNtfsButton(chat: chat) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { if !chat.chatItems.isEmpty { @@ -136,7 +138,7 @@ struct ChatListNavLink: View { showSheetContent: { sheet = $0 } ) } label: { - Label("Delete", systemImage: "trash") + deleteLabel } .tint(.red) } @@ -202,7 +204,7 @@ struct ChatListNavLink: View { .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() - ToggleNtfsButton(chat: chat) + toggleNtfsButton(chat: chat) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { if !chat.chatItems.isEmpty { @@ -243,7 +245,7 @@ struct ChatListNavLink: View { await MainActor.run { inProgress = false } } } label: { - Label("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward") + SwipeLabel("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward", inverted: oneHandUI) } .tint(chat.chatInfo.incognito ? .indigo : theme.colors.primary) } @@ -253,14 +255,14 @@ struct ChatListNavLink: View { Button { Task { await markChatRead(chat) } } label: { - Label("Read", systemImage: "checkmark") + SwipeLabel("Read", systemImage: "checkmark", inverted: oneHandUI) } .tint(theme.colors.primary) } else { Button { Task { await markChatUnread(chat) } } label: { - Label("Unread", systemImage: "circlebadge.fill") + SwipeLabel("Unread", systemImage: "circlebadge.fill", inverted: oneHandUI) } .tint(theme.colors.primary) } @@ -272,24 +274,36 @@ struct ChatListNavLink: View { Button { toggleChatFavorite(chat, favorite: false) } label: { - Label("Unfav.", systemImage: "star.slash") + SwipeLabel("Unfav.", systemImage: "star.slash.fill", inverted: oneHandUI) } .tint(.green) } else { Button { toggleChatFavorite(chat, favorite: true) } label: { - Label("Favorite", systemImage: "star.fill") + SwipeLabel("Favorite", systemImage: "star.fill", inverted: oneHandUI) } .tint(.green) } } + @ViewBuilder private func toggleNtfsButton(chat: Chat) -> some View { + Button { + toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) + } label: { + if chat.chatInfo.ntfsEnabled { + SwipeLabel("Mute", systemImage: "speaker.slash.fill", inverted: oneHandUI) + } else { + SwipeLabel("Unmute", systemImage: "speaker.wave.2.fill", inverted: oneHandUI) + } + } + } + private func clearChatButton() -> some View { Button { AlertManager.shared.showAlert(clearChatAlert()) } label: { - Label("Clear", systemImage: "gobackward") + SwipeLabel("Clear", systemImage: "gobackward", inverted: oneHandUI) } .tint(Color.orange) } @@ -298,7 +312,7 @@ struct ChatListNavLink: View { Button { AlertManager.shared.showAlert(clearNoteFolderAlert()) } label: { - Label("Clear", systemImage: "gobackward") + SwipeLabel("Clear", systemImage: "gobackward", inverted: oneHandUI) } .tint(Color.orange) } @@ -307,7 +321,7 @@ struct ChatListNavLink: View { Button { AlertManager.shared.showAlert(leaveGroupAlert(groupInfo)) } label: { - Label("Leave", systemImage: "rectangle.portrait.and.arrow.right") + SwipeLabel("Leave", systemImage: "rectangle.portrait.and.arrow.right.fill", inverted: oneHandUI) } .tint(Color.yellow) } @@ -316,7 +330,7 @@ struct ChatListNavLink: View { Button { AlertManager.shared.showAlert(deleteGroupAlert(groupInfo)) } label: { - Label("Delete", systemImage: "trash") + deleteLabel } .tint(.red) } @@ -326,18 +340,18 @@ struct ChatListNavLink: View { .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } - } label: { Label("Accept", systemImage: "checkmark") } + } label: { SwipeLabel("Accept", systemImage: "checkmark", inverted: oneHandUI) } .tint(theme.colors.primary) Button { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } } label: { - Label("Accept incognito", systemImage: "theatermasks") + SwipeLabel("Accept incognito", systemImage: "theatermasks.fill", inverted: oneHandUI) } .tint(.indigo) Button { AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest)) } label: { - Label("Reject", systemImage: "multiply") + SwipeLabel("Reject", systemImage: "multiply.fill", inverted: oneHandUI) } .tint(.red) } @@ -358,14 +372,14 @@ struct ChatListNavLink: View { AlertManager.shared.showAlertMsg(title: a.title, message: a.message) }) } label: { - Label("Delete", systemImage: "trash") + deleteLabel } .tint(.red) Button { showContactConnectionInfo = true } label: { - Label("Name", systemImage: "pencil") + SwipeLabel("Name", systemImage: "pencil", inverted: oneHandUI) } .tint(theme.colors.primary) } @@ -384,6 +398,10 @@ struct ChatListNavLink: View { } } + private var deleteLabel: some View { + SwipeLabel("Delete", systemImage: "trash.fill", inverted: oneHandUI) + } + private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert { Alert( title: Text("Delete group?"), diff --git a/apps/ios/Shared/Views/Helpers/SwipeLabel.swift b/apps/ios/Shared/Views/Helpers/SwipeLabel.swift new file mode 100644 index 0000000000..27a8c6737d --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/SwipeLabel.swift @@ -0,0 +1,80 @@ +// +// SwipeLabel.swift +// SimpleX (iOS) +// +// Created by Levitating Pineapple on 06/08/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct SwipeLabel: View { + private let text: String + private let systemImage: String + private let inverted: Bool + + init(_ text: String, systemImage: String, inverted: Bool) { + self.text = text + self.systemImage = systemImage + self.inverted = inverted + } + + var body: some View { + if inverted { + Image( + uiImage: SwipeActionView( + systemName: systemImage, + text: text + ).snapshot(inverted: inverted) + ) + } else { + Label(text, systemImage: systemImage) + } + } + + private class SwipeActionView: UIView { + private let imageView = UIImageView() + private let label = UILabel() + private let fontSize: CGFloat + + init(systemName: String, text: String) { + fontSize = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline).pointSize + super.init(frame: CGRect(x: 0, y: 0, width: 64, height: 32 + fontSize)) + imageView.image = UIImage(systemName: systemName) + imageView.contentMode = .scaleAspectFit + label.text = NSLocalizedString(text, comment: "swipe action") + label.textAlignment = .center + label.font = UIFont.systemFont(ofSize: fontSize, weight: .medium) + addSubview(imageView) + addSubview(label) + } + + override func layoutSubviews() { + imageView.frame = CGRect( + x: 20, + y: 0, + width: 24, + height: 24 + ) + label.frame = CGRect( + x: 0, + y: 32, + width: 64, + height: fontSize + ) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("not implemented") } + + func snapshot(inverted: Bool) -> UIImage { + UIGraphicsImageRenderer(bounds: bounds).image { context in + if inverted { + context.cgContext.scaleBy(x: 1, y: -1) + context.cgContext.translateBy(x: 0, y: -bounds.height) + } + layer.render(in: context.cgContext) + }.withRenderingMode(.alwaysTemplate) + } + } +} diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 6c9e114e53..1c99e3a573 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -200,6 +200,7 @@ CE3097FB2C4C0C9F00180898 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */; }; CE38A29A2C3FCA54005ED185 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; }; CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; + CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; }; CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; }; CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; }; @@ -536,6 +537,7 @@ CE1EB0E32C459A660099D896 /* ShareAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAPI.swift; sourceTree = ""; }; CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatUtils.swift; sourceTree = ""; }; CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; + CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = ""; }; CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = ""; }; CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = ""; }; CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -789,6 +791,7 @@ 8C74C3ED2C1B942300039E77 /* ChatWallpaper.swift */, 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */, CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, + CE7548092C622630009579B7 /* SwipeLabel.swift */, ); path = Helpers; sourceTree = ""; @@ -1443,6 +1446,7 @@ 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */, 8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */, 64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */, + CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */, 5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */, 5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */, 6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */,