CopyShare.swift (6692B)
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 let image: UIImage? 68 let vertical: Bool 69 let title: String? 70 71 @Environment(\.isEnabled) private var isEnabled: Bool 72 @EnvironmentObject private var controller: Controller 73 74 @State private var scale: CGFloat = 1.0 75 76 init(textToCopy: String, vertical: Bool, image: UIImage? = nil) { 77 self.textToCopy = textToCopy 78 self.image = image 79 self.vertical = vertical 80 self.title = nil 81 } 82 83 init(textToCopy: String, title: String, image: UIImage? = nil) { 84 self.textToCopy = textToCopy 85 self.image = image 86 self.vertical = false 87 self.title = title 88 } 89 90 func copyAction() -> Void { 91 symLog.log(textToCopy) 92 triggerPulse() 93 controller.hapticFeedback(.medium) 94 let strings = [UTType.plainText.identifier : textToCopy] 95 var items: [[String : Any]] = [strings] 96 if let image { 97 let images = [UTType.image.identifier : image] 98 items.append(images) 99 } 100 UIPasteboard.general.setItems(items) 101 } 102 103 private func triggerPulse() { 104 // First scale down quickly 105 withAnimation(.easeIn(duration: 0.1)) { 106 scale = 0.8 107 } 108 // Then bounce back bigger 109 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 110 withAnimation(.easeOut(duration: 0.25)) { 111 scale = 1.15 112 } 113 } 114 // Finally settle to normal 115 DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { 116 withAnimation(.easeInOut(duration: 0.25)) { 117 scale = 1.0 118 } 119 } 120 } 121 122 var body: some View { 123 Button(action: copyAction) { 124 let image = Image(systemName: COPY1) // 125 .accessibility(hidden: true) 126 127 if vertical { 128 VStack { 129 let shortCopy = String(localized: "Copy.short", defaultValue: "Copy", comment: "5 letters max, else abbreviate") 130 image 131 Text(shortCopy) 132 } 133 } else { 134 let longCopy = String(localized: "Copy.long", defaultValue: "Copy", comment: "may be a bit longer") 135 HStack { 136 image 137 Text(title ?? longCopy) 138 } 139 } 140 } 141 .tint(.accentColor) 142 .talerFont(.body) 143 .scaleEffect(scale) 144 .disabled(!isEnabled) 145 } 146 } 147 // MARK: - 148 @MainActor 149 struct ShareButton: View { 150 private let symLog = SymLogV(0) 151 let textToShare: String 152 let image: UIImage? 153 let title: String 154 155 @State private var scale: CGFloat = 1.0 156 157 init(textToShare: String, image: UIImage? = nil) { 158 self.textToShare = textToShare 159 self.image = image 160 self.title = String(localized: "Share") 161 } 162 init(textToShare: String, title: String, image: UIImage? = nil) { 163 self.textToShare = textToShare 164 self.image = image 165 self.title = title 166 } 167 168 @Environment(\.isEnabled) private var isEnabled: Bool 169 @EnvironmentObject private var controller: Controller 170 171 func shareAction() -> Void { 172 symLog.log(textToShare) 173 controller.hapticFeedback(.soft) 174 Task { 175 // First scale down quickly 176 withAnimation(.easeIn(duration: 0.1)) { 177 scale = 0.8 178 } 179 // Then bounce back bigger 180 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 181 withAnimation(.easeOut(duration: 0.25)) { 182 scale = 1.15 183 } 184 ShareSheet.shareSheet(textToShare: textToShare, image: image) 185 } 186 // Finally settle to normal 187 DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { 188 withAnimation(.easeInOut(duration: 0.25)) { 189 scale = 1.0 190 } 191 } 192 } 193 } 194 195 var body: some View { 196 Button(action: shareAction) { 197 HStack { 198 Image(systemName: SHARE) // 199 .accessibility(hidden: true) 200 Text(title) 201 } 202 } 203 .tint(.accentColor) 204 .talerFont(.body) 205 .scaleEffect(scale) 206 .disabled(!isEnabled) 207 } 208 } 209 // MARK: - 210 struct CopyShare: View { 211 @Environment(\.isEnabled) private var isEnabled: Bool 212 213 let textToCopy: String 214 let image: UIImage? 215 216 var body: some View { 217 HStack { 218 CopyButton(textToCopy: textToCopy, vertical: false, image: image) 219 .buttonStyle(TalerButtonStyle(type: .bordered)) 220 ShareButton(textToShare: textToCopy, image: image) 221 .buttonStyle(TalerButtonStyle(type: .bordered)) 222 } // two buttons 223 } 224 } 225 // MARK: - 226 struct CopyShare_Previews: PreviewProvider { 227 static var previews: some View { 228 CopyShare(textToCopy: "Hallö", image: nil) 229 } 230 }