SettingsItem.swift (10912B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 10 struct SettingsImage: View { 11 let imageName: String? 12 13 @ScaledMetric var imgSize: CGFloat = 32 // sys image relative to fontSize 14 15 var hasImage: Bool { 16 if let imageName { 17 return UIImage(named: imageName) != nil 18 } 19 return false 20 } 21 22 var hasSysImage: Bool { 23 if let imageName { 24 return UIImage(systemName: imageName) != nil 25 } 26 return false 27 } 28 29 var body: some View { 30 if hasImage { 31 let imageSize = imgSize * 5 / 4 // Logos have space around them, thus need to be bigger 32 Image(imageName!) 33 .resizable() 34 .scaledToFit() 35 .frame(width: imageSize, height: imageSize) 36 } else if hasSysImage { 37 Image(systemName: imageName!) 38 .resizable() 39 .scaledToFit() 40 .frame(width: imgSize, height: imgSize) 41 .padding(.horizontal, 2) 42 } else { 43 EmptyView() 44 } 45 } 46 } 47 48 struct SettingsDescription: View { 49 let id1: String? 50 let description: String? 51 52 @AppStorage("minimalistic") var minimalistic: Bool = false 53 54 var body: some View { 55 if !minimalistic { 56 if let desc = description { 57 Text(desc) 58 .id(id1 == nil ? nil : id1! + "_T") 59 .frame(maxWidth: .infinity, alignment: .leading) 60 .talerFont(.caption) 61 } 62 } 63 } 64 } 65 // MARK: - 66 struct SettingsBase<Content: View>: View { 67 let id1: String? 68 let description: String? 69 let content: () -> Content 70 71 init(id1: String?, description: String? = nil, @ViewBuilder content: @escaping () -> Content) { 72 self.id1 = id1 73 self.description = description 74 self.content = content 75 } 76 77 var body: some View { 78 let isWeb = id1?.hasPrefix("web") ?? false 79 let foreColor = isWeb ? WalletColors().primaryAccent 80 : .primary 81 VStack { 82 content() 83 .id(id1) 84 .frame(maxWidth: .infinity, alignment: .leading) 85 .foregroundColor(foreColor) 86 .talerFont(.title3) 87 .padding([.bottom], 0.01) 88 SettingsDescription(id1: id1, description: description) 89 }.id(id1 == nil ? nil : id1! + "_V") 90 } 91 } 92 // MARK: - 93 struct SettingsItem<Content: View>: View { 94 let name: String 95 let id1: String? 96 let imageName: String? 97 let description: String? 98 let content: () -> Content 99 100 init(name: String, id1: String, imageName: String? = nil, description: String? = nil, 101 @ViewBuilder content: @escaping () -> Content 102 ) { 103 self.name = name 104 self.id1 = id1 105 self.imageName = imageName 106 self.description = description 107 self.content = content 108 } 109 110 var body: some View { 111 HStack { 112 SettingsImage(imageName: imageName) 113 114 SettingsBase(id1: id1, description: description) { 115 Text(name) 116 } 117 content() 118 .talerFont(.body) 119 }.id(id1 == nil ? nil : id1! + "_H") 120 .accessibilityElement(children: .combine) 121 .padding([.bottom], 4) 122 } 123 } 124 // MARK: - 125 struct SettingsToggle: View { 126 let name: String 127 @Binding var value: Bool 128 let id1: String? 129 let imageName: String? 130 let description: String? 131 let action: (_ newValue: Bool) -> Void 132 133 init(name: String, value: Binding<Bool>, id1: String? = nil, 134 imageName: String? = nil, 135 description: String? = nil, 136 action: @escaping (_ newValue: Bool) -> Void = {_ in} 137 ) { 138 self.name = name 139 self._value = value 140 self.id1 = id1 141 self.imageName = imageName 142 self.description = description 143 self.action = action 144 } 145 146 var body: some View { 147 let accLabel: String = if let description { 148 name + ", " + description 149 } else { 150 name 151 } 152 HStack { 153 SettingsImage(imageName: imageName) 154 155 SettingsBase(id1: id1, description: description) { 156 Toggle(name, isOn: $value.animation()) 157 .accessibility(sortPriority: 1) 158 .onChange(of: value) { value in 159 action(value) 160 } 161 } 162 }.id(id1 == nil ? nil : id1! + "_H") 163 .accessibilityElement(children: .combine) 164 .accessibilityLabel(accLabel) 165 .padding([.bottom], 4) 166 } 167 } 168 // MARK: - 169 struct SettingsFont: View { 170 let title: String 171 let value: Int 172 let action: (Int) -> Void 173 174 @State private var selectedFont = 0 175 let fonts = [String(localized: "Standard iOS Font"), "Atkinson-Hyperlegible", "Nunito"] 176 177 var body: some View { 178 Picker(title, selection: $selectedFont, content: { 179 ForEach(0..<fonts.count, id: \.self, content: { index in 180 Text(fonts[index]).tag(index) 181 }) 182 }) 183 .talerFont(.title2) 184 .pickerStyle(.menu) 185 .onAppear() { 186 withAnimation { selectedFont = value } 187 } 188 .onChange(of: selectedFont) { selectedF in 189 action(selectedF) 190 } 191 } 192 } 193 // MARK: - 194 struct SettingsStyle: View { 195 let title: String 196 @Binding var myListStyle: MyListStyle 197 198 var body: some View { 199 HStack { 200 Text(title) 201 .talerFont(.title2) 202 Spacer() 203 Picker(selection: $myListStyle) { 204 ForEach(MyListStyle.allCases, id: \.self) { 205 Text($0.displayName.capitalized).tag($0) 206 .talerFont(.title2) 207 } 208 } label: {} 209 .pickerStyle(.menu) 210 // .frame(alignment: .trailing) 211 // .background(WalletColors().buttonBackColor(pressed: false, disabled: false)) TODO: RoundRect 212 } 213 .accessibilityElement(children: .combine) 214 } 215 } 216 // MARK: - 217 struct SettingsTriState: View { 218 var name: String 219 @Binding var value: Int 220 let id1: String? 221 var description: String? 222 var action: (_ value: Int) -> Void = {value in } 223 224 func imageName(_ value: Int) -> (String, String) { 225 if id1 == "observe" { 226 return (value == 0) ? ("eye.slash", "Off") 227 : (value == 1) ? ("eye", "Type only") 228 : ("eye.fill", "Type and JSON") 229 } else if id1 == "delay" { 230 return (value == 0) ? ("figure.run", "none") // 231 : (value == 1) ? ("figure.walk", "3 seconds") // 232 : ("figure.hiking","6 seconds") // 233 } else { 234 return (value == 0) ? ("square.slash", "none") // 235 : (value == 1) ? ("2.square", "2 failures") // 236 : ("4.square", "4 failures") // 237 } 238 } 239 var body: some View { 240 let image = imageName(value) 241 242 VStack { 243 HStack { 244 Text(name) 245 .talerFont(.title2) 246 Spacer() 247 Text(verbatim: " ") 248 .talerFont(.largeTitle) 249 Button { 250 if value > 0 { 251 value = -1 252 action(value) 253 Controller.shared.playSound(1) 254 } else { 255 value = value + 1 256 action(value) 257 Controller.shared.playSound(value) 258 } 259 } label: { 260 Image(systemName: image.0) 261 .talerFont(.largeTitle) 262 } 263 } 264 265 SettingsDescription(id1: id1, description: description) 266 } 267 .accessibilityElement(children: .combine) 268 .accessibilityLabel(name) 269 .accessibility(value: Text(image.1)) 270 .accessibilityHint(description ?? EMPTYSTRING) 271 .padding([.bottom], 4) 272 } 273 } 274 // MARK: - 275 struct SettingsQuadState: View { 276 var name: String 277 @Binding var value: Int 278 let id1: String? 279 var action: (_ value: Int) -> Void = {value in } 280 281 func imageName(_ value: Int) -> (String, String) { 282 return (value == 0) ? ("iphone", "System") // or apple.logo 283 : (value == 1) ? (LIGHT_OFF, "Light") // 284 : (value == 2) ? (LIGHT_ON, "Dark") // 285 : ("swirl.circle.righthalf.filled", "Liquid dark") // 286 } 287 288 var body: some View { 289 let image = imageName(value) 290 291 VStack { 292 HStack { 293 Text(name) 294 .talerFont(.title2) 295 Spacer() 296 Text(verbatim: " ") 297 .talerFont(.largeTitle) 298 Button { 299 var maxValue = if #available(iOS 17.0, *) { 3 } else { 2 } 300 if value >= maxValue { 301 value = 0 302 action(value) 303 } else { 304 value = value + 1 305 action(value) 306 } 307 } label: { 308 Image(systemName: image.0) 309 .talerFont(.largeTitle) 310 } 311 } 312 313 SettingsDescription(id1: id1, description: image.1) 314 } 315 .accessibilityElement(children: .combine) 316 .accessibilityLabel(name) 317 .accessibility(value: Text(image.1)) 318 .padding([.bottom], 4) 319 } 320 } 321 // MARK: - 322 #if DEBUG 323 struct SettingsItemPreview : View { 324 @State var developerMode: Bool = false 325 @State var observe: Int = 0 326 327 var body: some View { 328 VStack { 329 SettingsToggle(name: "Developer Preview", value: $developerMode, id1: "dev1", 330 description: "More information intended for debugging") 331 SettingsTriState(name: "Observe walletCore", value: $observe, 332 id1: "observe", 333 description: "on LocalConsole") 334 } 335 } 336 } 337 338 struct SettingsItem_Previews: PreviewProvider { 339 static var previews: some View { 340 List { 341 SettingsItem(name: "Exchanges", id1: "list", 342 description: "Manage list of exchanges known to this wallet") {} 343 SettingsItemPreview() 344 SettingsItem(name: "Save Logfile", id1: "save", 345 description: "Help debugging wallet-core") { 346 Button("Save") { } 347 .buttonStyle(.bordered) 348 .disabled(true) 349 } 350 } 351 } 352 } 353 #endif