SettingsItem.swift (10374B)
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 return (value == 0) ? ("eye.slash", "Off") 226 : (value == 1) ? ("eye", "Type only") 227 : ("eye.fill", "Type and JSON") 228 } 229 230 var body: some View { 231 let image = imageName(value) 232 233 VStack { 234 HStack { 235 Text(name) 236 .talerFont(.title2) 237 Spacer() 238 Text(verbatim: " ") 239 .talerFont(.largeTitle) 240 Button { 241 if value > 0 { 242 value = -1 243 action(value) 244 Controller.shared.playSound(1) 245 } else { 246 value = value + 1 247 action(value) 248 Controller.shared.playSound(value) 249 } 250 } label: { 251 Image(systemName: image.0) 252 .talerFont(.largeTitle) 253 } 254 } 255 256 SettingsDescription(id1: id1, description: description) 257 } 258 .accessibilityElement(children: .combine) 259 .accessibilityLabel(name) 260 .accessibility(value: Text(image.1)) 261 .accessibilityHint(description ?? EMPTYSTRING) 262 .padding([.bottom], 4) 263 } 264 } 265 // MARK: - 266 struct SettingsQuadState: View { 267 var name: String 268 @Binding var value: Int 269 let id1: String? 270 var action: (_ value: Int) -> Void = {value in } 271 272 func imageName(_ value: Int) -> (String, String) { 273 return (value == 0) ? ("iphone", "System") // or apple.logo 274 : (value == 1) ? (LIGHT_OFF, "Light") // 275 : (value == 2) ? (LIGHT_ON, "Dark") // 276 : ("swirl.circle.righthalf.filled", "Liquid dark") // 277 } 278 279 var body: some View { 280 let image = imageName(value) 281 282 VStack { 283 HStack { 284 Text(name) 285 .talerFont(.title2) 286 Spacer() 287 Text(verbatim: " ") 288 .talerFont(.largeTitle) 289 Button { 290 var maxValue = if #available(iOS 17.0, *) { 3 } else { 2 } 291 if value >= maxValue { 292 value = 0 293 action(value) 294 } else { 295 value = value + 1 296 action(value) 297 } 298 } label: { 299 Image(systemName: image.0) 300 .talerFont(.largeTitle) 301 } 302 } 303 304 SettingsDescription(id1: id1, description: image.1) 305 } 306 .accessibilityElement(children: .combine) 307 .accessibilityLabel(name) 308 .accessibility(value: Text(image.1)) 309 .padding([.bottom], 4) 310 } 311 } 312 // MARK: - 313 #if DEBUG 314 struct SettingsItemPreview : View { 315 @State var developerMode: Bool = false 316 @State var observe: Int = 0 317 318 var body: some View { 319 VStack { 320 SettingsToggle(name: "Developer Preview", value: $developerMode, id1: "dev1", 321 description: "More information intended for debugging") 322 SettingsTriState(name: "Observe walletCore", value: $observe, 323 id1: "observe", 324 description: "on LocalConsole") 325 } 326 } 327 } 328 329 struct SettingsItem_Previews: PreviewProvider { 330 static var previews: some View { 331 List { 332 SettingsItem(name: "Exchanges", id1: "list", 333 description: "Manage list of exchanges known to this wallet") {} 334 SettingsItemPreview() 335 SettingsItem(name: "Save Logfile", id1: "save", 336 description: "Help debugging wallet-core") { 337 Button("Save") { } 338 .buttonStyle(.bordered) 339 .disabled(true) 340 } 341 } 342 } 343 } 344 #endif