CopyShare.swift (7390B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import UniformTypeIdentifiers 9 import SwiftUI 10 import SymLog 11 12 @MainActor 13 struct FeedbackButton: View { 14 private let symLog = SymLogV(0) 15 let title: String? 16 let image: UIImage? 17 let isDisabled: Bool 18 let action: () -> Void 19 20 @EnvironmentObject private var controller: Controller 21 @State private var scale: CGFloat = 1.0 22 23 public init(_ title: String? = nil, 24 image: UIImage? = nil, 25 disabled: Bool = false, 26 action: @escaping @MainActor () -> Void) { 27 self.title = title 28 self.image = image 29 self.isDisabled = disabled 30 self.action = action 31 } 32 33 private func triggerPulse() { 34 // First scale down quickly 35 withAnimation(.easeIn(duration: 0.1)) { 36 scale = 0.8 37 } 38 // Then bounce back bigger 39 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 40 withAnimation(.easeOut(duration: 0.25)) { 41 scale = 1.15 42 } 43 } 44 // Finally settle to normal 45 DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { 46 withAnimation(.easeInOut(duration: 0.25)) { 47 scale = 1.0 48 } 49 } 50 } 51 52 var body: some View { 53 Button(title ?? EMPTYSTRING) { 54 symLog.log(title ?? EMPTYSTRING) 55 controller.hapticFeedback(.medium) 56 action() 57 triggerPulse() 58 } 59 .buttonStyle(TalerButtonStyle(type: .bordered, disabled: isDisabled)) 60 } 61 } 62 // MARK: - 63 @MainActor 64 struct CopyButton: View { 65 private let symLog = SymLogV(0) 66 let textToCopy: String 67 @Binding var isCopied: Bool 68 let image: UIImage? 69 let vertical: Bool 70 let title: String? 71 72 @Environment(\.isEnabled) private var isEnabled: Bool 73 @EnvironmentObject private var controller: Controller 74 75 @State private var scale: CGFloat = 1.0 76 77 init(textToCopy: String, isCopied: Binding<Bool>, vertical: Bool, image: UIImage? = nil) { 78 self.textToCopy = textToCopy 79 self._isCopied = isCopied 80 self.image = image 81 self.vertical = vertical 82 self.title = nil 83 } 84 85 init(textToCopy: String, isCopied: Binding<Bool>, title: String, image: UIImage? = nil) { 86 self.textToCopy = textToCopy 87 self._isCopied = isCopied 88 self.image = image 89 self.vertical = false 90 self.title = title 91 } 92 93 func copyAction() -> Void { 94 symLog.log(textToCopy) 95 triggerPulse() 96 controller.hapticFeedback(.medium) 97 let strings = [UTType.plainText.identifier : textToCopy] 98 var items: [[String : Any]] = [strings] 99 if let image { 100 let images = [UTType.image.identifier : image] 101 items.append(images) 102 } 103 UIPasteboard.general.setItems(items) 104 isCopied = true 105 } 106 107 private func triggerPulse() { 108 // First scale down quickly 109 withAnimation(.easeIn(duration: 0.1)) { 110 scale = 0.8 111 } 112 // Then bounce back bigger 113 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 114 withAnimation(.easeOut(duration: 0.25)) { 115 scale = 1.15 116 } 117 } 118 // Finally settle to normal 119 DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { 120 withAnimation(.easeInOut(duration: 0.25)) { 121 scale = 1.0 122 } 123 } 124 } 125 126 var body: some View { 127 Button(action: copyAction) { 128 let image = Image(systemName: COPY1) // 129 .accessibility(hidden: true) 130 131 if vertical { 132 VStack { 133 let shortCopy = String(localized: "Copy.short", defaultValue: "Copy", comment: "5 letters max, else abbreviate") 134 image 135 Text(shortCopy) 136 } 137 } else { 138 let longCopy = String(localized: "Copy.long", defaultValue: "Copy", comment: "may be a bit longer") 139 HStack { 140 image 141 Text(title ?? longCopy) 142 } 143 } 144 } 145 .tint(.accentColor) 146 .talerFont(.body) 147 .scaleEffect(scale) 148 .disabled(!isEnabled) 149 } 150 } 151 // MARK: - 152 struct ShareType: Hashable { 153 let textToShare: String 154 let image: UIImage? 155 } 156 // MARK: - 157 @MainActor 158 struct ShareButton: View { 159 private let symLog = SymLogV(0) 160 let textToShare: String 161 let image: UIImage? 162 let title: String 163 164 @State private var scale: CGFloat = 1.0 165 166 init(textToShare: String, image: UIImage? = nil) { 167 self.textToShare = textToShare 168 self.image = image 169 self.title = String(localized: "Share") 170 } 171 init(textToShare: String, title: String, image: UIImage? = nil) { 172 self.textToShare = textToShare 173 self.image = image 174 self.title = title 175 } 176 177 @Environment(\.isEnabled) private var isEnabled: Bool 178 @EnvironmentObject private var controller: Controller 179 180 @MainActor 181 func dismissAndPost() { 182 dismissTop() 183 let shareType = ShareType(textToShare: textToShare, image: image) 184 let userInfo = [NOTIFICATIONSHARE: shareType] 185 NotificationCenter.default.post(name: .ShareAction, object: nil, userInfo: userInfo) // will trigger NavigationLink 186 } 187 188 func shareAction() -> Void { 189 symLog.log(textToShare) 190 controller.hapticFeedback(.soft) 191 Task { 192 // First scale down quickly 193 withAnimation(.easeIn(duration: 0.1)) { 194 scale = 0.8 195 } 196 // Then bounce back bigger 197 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 198 withAnimation(.easeOut(duration: 0.25)) { 199 scale = 1.15 200 } 201 // ShareSheet.shareSheet(textToShare: textToShare, image: image) 202 dismissAndPost() 203 } 204 // Finally settle to normal 205 DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { 206 withAnimation(.easeInOut(duration: 0.25)) { 207 scale = 1.0 208 } 209 } 210 } 211 } 212 213 var body: some View { 214 Button(action: shareAction) { 215 HStack { 216 Image(systemName: SHARE) // 217 .accessibility(hidden: true) 218 Text(title) 219 } 220 } 221 .tint(.accentColor) 222 .talerFont(.body) 223 .scaleEffect(scale) 224 .disabled(!isEnabled) 225 } 226 } 227 // MARK: - 228 struct CopyShare: View { 229 @Environment(\.isEnabled) private var isEnabled: Bool 230 @State var isCopied: Bool = false 231 232 let textToCopy: String 233 let image: UIImage? 234 235 var body: some View { 236 HStack { 237 CopyButton(textToCopy: textToCopy, isCopied: $isCopied, vertical: false, image: image) 238 .buttonStyle(TalerButtonStyle(type: .bordered)) 239 ShareButton(textToShare: textToCopy, image: image) 240 .buttonStyle(TalerButtonStyle(type: .bordered)) 241 } // two buttons 242 } 243 } 244 // MARK: - 245 struct CopyShare_Previews: PreviewProvider { 246 static var previews: some View { 247 CopyShare(textToCopy: "Hallö", image: nil) 248 } 249 }