SettingsItem.swift (8595B)
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 var hasImage: Bool { 14 if let imageName { 15 return UIImage(named: imageName) != nil 16 } 17 return false 18 } 19 20 var hasSysImage: Bool { 21 if let imageName { 22 return UIImage(systemName: imageName) != nil 23 } 24 return false 25 } 26 27 var body: some View { 28 if hasImage { 29 Image(imageName!) 30 .resizable() 31 .scaledToFit() 32 .frame(width: 44, height: 44) 33 } else if hasSysImage { 34 Image(systemName: imageName!) 35 .resizable() 36 .scaledToFit() 37 .frame(width: 36, height: 36) 38 .padding(.horizontal, 2) 39 } else { 40 EmptyView() 41 } 42 } 43 } 44 45 struct SettingsDescription: View { 46 let id1: String? 47 let description: String? 48 49 @AppStorage("minimalistic") var minimalistic: Bool = false 50 51 var body: some View { 52 if !minimalistic { 53 if let desc = description { 54 Text(desc) 55 .id(id1 == nil ? nil : id1! + "_T") 56 .frame(maxWidth: .infinity, alignment: .leading) 57 .talerFont(.caption) 58 } 59 } 60 } 61 } 62 // MARK: - 63 struct SettingsBase<Content: View>: View { 64 let id1: String? 65 let description: String? 66 let content: () -> Content 67 68 init(id1: String?, description: String? = nil, @ViewBuilder content: @escaping () -> Content) { 69 self.id1 = id1 70 self.description = description 71 self.content = content 72 } 73 74 var body: some View { 75 let isWeb = id1?.hasPrefix("web") ?? false 76 let foreColor = isWeb ? Color.accentColor 77 : .primary 78 VStack { 79 content() 80 .id(id1) 81 .frame(maxWidth: .infinity, alignment: .leading) 82 .foregroundColor(foreColor) 83 .talerFont(.title2) 84 .padding([.bottom], 0.01) 85 SettingsDescription(id1: id1, description: description) 86 }.id(id1 == nil ? nil : id1! + "_V") 87 } 88 } 89 // MARK: - 90 struct SettingsItem<Content: View>: View { 91 let name: String 92 let id1: String? 93 let imageName: String? 94 let description: String? 95 let content: () -> Content 96 97 init(name: String, id1: String, imageName: String? = nil, description: String? = nil, 98 @ViewBuilder content: @escaping () -> Content 99 ) { 100 self.name = name 101 self.id1 = id1 102 self.imageName = imageName 103 self.description = description 104 self.content = content 105 } 106 107 var body: some View { 108 HStack { 109 SettingsImage(imageName: imageName) 110 111 SettingsBase(id1: id1, description: description) { 112 Text(name) 113 } 114 content() 115 .talerFont(.body) 116 }.id(id1 == nil ? nil : id1! + "_H") 117 .accessibilityElement(children: .combine) 118 .padding([.bottom], 4) 119 } 120 } 121 // MARK: - 122 struct SettingsToggle: View { 123 let name: String 124 @Binding var value: Bool 125 let id1: String? 126 let imageName: String? 127 let description: String? 128 let action: (_ newValue: Bool) -> Void 129 130 init(name: String, value: Binding<Bool>, id1: String? = nil, 131 imageName: String? = nil, 132 description: String? = nil, 133 action: @escaping (_ newValue: Bool) -> Void = {_ in} 134 ) { 135 self.name = name 136 self._value = value 137 self.id1 = id1 138 self.imageName = imageName 139 self.description = description 140 self.action = action 141 } 142 143 var body: some View { 144 let accLabel: String = if let description { 145 name + ", " + description 146 } else { 147 name 148 } 149 HStack { 150 SettingsImage(imageName: imageName) 151 152 SettingsBase(id1: id1, description: description) { 153 Toggle(name, isOn: $value.animation()) 154 .accessibility(sortPriority: 1) 155 .onChange(of: value) { value in 156 action(value) 157 } 158 } 159 }.id(id1 == nil ? nil : id1! + "_H") 160 .accessibilityElement(children: .combine) 161 .accessibilityLabel(accLabel) 162 .padding([.bottom], 4) 163 } 164 } 165 // MARK: - 166 struct SettingsFont: View { 167 let title: String 168 let value: Int 169 let action: (Int) -> Void 170 171 @State private var selectedFont = 0 172 let fonts = [String(localized: "Standard iOS Font"), "Atkinson-Hyperlegible", "Nunito"] 173 174 var body: some View { 175 Picker(title, selection: $selectedFont, content: { 176 ForEach(0..<fonts.count, id: \.self, content: { index in 177 Text(fonts[index]).tag(index) 178 }) 179 }) 180 .talerFont(.title2) 181 .pickerStyle(.menu) 182 .onAppear() { 183 withAnimation { selectedFont = value } 184 } 185 .onChange(of: selectedFont) { selectedF in 186 action(selectedF) 187 } 188 } 189 } 190 // MARK: - 191 struct SettingsStyle: View { 192 let title: String 193 @Binding var myListStyle: MyListStyle 194 195 var body: some View { 196 HStack { 197 Text(title) 198 .talerFont(.title2) 199 Spacer() 200 Picker(selection: $myListStyle) { 201 ForEach(MyListStyle.allCases, id: \.self) { 202 Text($0.displayName.capitalized).tag($0) 203 .talerFont(.title2) 204 } 205 } label: {} 206 .pickerStyle(.menu) 207 // .frame(alignment: .trailing) 208 // .background(WalletColors().buttonBackColor(pressed: false, disabled: false)) TODO: RoundRect 209 } 210 .accessibilityElement(children: .combine) 211 } 212 } 213 // MARK: - 214 struct SettingsTriState: View { 215 var name: String 216 @Binding var value: Int 217 let id1: String? 218 var description: String? 219 var action: (_ value: Int) -> Void = {value in } 220 221 func imageName(_ value: Int) -> (String, String) { 222 return (value == 0) ? ("eye.slash", "Off") 223 : (value == 1) ? ("eye", "Type only") 224 : ("eye.fill", "Type and JSON") 225 } 226 var body: some View { 227 let image = imageName(value) 228 229 VStack { 230 HStack { 231 Text(name) 232 .talerFont(.title2) 233 Spacer() 234 Text(verbatim: " ") 235 .talerFont(.largeTitle) 236 Button { 237 if value > 0 { 238 value = -1 239 action(value) 240 Controller.shared.playSound(1) 241 } else { 242 value = value + 1 243 action(value) 244 Controller.shared.playSound(value) 245 } 246 } label: { 247 Image(systemName: image.0) 248 .talerFont(.largeTitle) 249 } 250 } 251 252 SettingsDescription(id1: id1, description: description) 253 } 254 .accessibilityElement(children: .combine) 255 .accessibilityLabel(name) 256 .accessibility(value: Text(image.1)) 257 .accessibilityHint(description ?? EMPTYSTRING) 258 .padding([.bottom], 4) 259 } 260 } 261 // MARK: - 262 #if DEBUG 263 struct SettingsItemPreview : View { 264 @State var developerMode: Bool = false 265 @State var observe: Int = 0 266 267 var body: some View { 268 VStack { 269 SettingsToggle(name: "Developer Preview", value: $developerMode, id1: "dev1", 270 description: "More information intended for debugging") 271 SettingsTriState(name: "Observe walletCore", value: $observe, 272 id1: "observe", 273 description: "on LocalConsole") 274 } 275 } 276 } 277 278 struct SettingsItem_Previews: PreviewProvider { 279 static var previews: some View { 280 List { 281 SettingsItem(name: "Exchanges", id1: "list", 282 description: "Manage list of exchanges known to this wallet") {} 283 SettingsItemPreview() 284 SettingsItem(name: "Save Logfile", id1: "save", 285 description: "Help debugging wallet-core") { 286 Button("Save") { } 287 .buttonStyle(.bordered) 288 .disabled(true) 289 } 290 } 291 } 292 } 293 #endif