SettingsItem.swift (8814B)
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 ? Color.accentColor 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 var body: some View { 230 let image = imageName(value) 231 232 VStack { 233 HStack { 234 Text(name) 235 .talerFont(.title2) 236 Spacer() 237 Text(verbatim: " ") 238 .talerFont(.largeTitle) 239 Button { 240 if value > 0 { 241 value = -1 242 action(value) 243 Controller.shared.playSound(1) 244 } else { 245 value = value + 1 246 action(value) 247 Controller.shared.playSound(value) 248 } 249 } label: { 250 Image(systemName: image.0) 251 .talerFont(.largeTitle) 252 } 253 } 254 255 SettingsDescription(id1: id1, description: description) 256 } 257 .accessibilityElement(children: .combine) 258 .accessibilityLabel(name) 259 .accessibility(value: Text(image.1)) 260 .accessibilityHint(description ?? EMPTYSTRING) 261 .padding([.bottom], 4) 262 } 263 } 264 // MARK: - 265 #if DEBUG 266 struct SettingsItemPreview : View { 267 @State var developerMode: Bool = false 268 @State var observe: Int = 0 269 270 var body: some View { 271 VStack { 272 SettingsToggle(name: "Developer Preview", value: $developerMode, id1: "dev1", 273 description: "More information intended for debugging") 274 SettingsTriState(name: "Observe walletCore", value: $observe, 275 id1: "observe", 276 description: "on LocalConsole") 277 } 278 } 279 } 280 281 struct SettingsItem_Previews: PreviewProvider { 282 static var previews: some View { 283 List { 284 SettingsItem(name: "Exchanges", id1: "list", 285 description: "Manage list of exchanges known to this wallet") {} 286 SettingsItemPreview() 287 SettingsItem(name: "Save Logfile", id1: "save", 288 description: "Help debugging wallet-core") { 289 Button("Save") { } 290 .buttonStyle(.bordered) 291 .disabled(true) 292 } 293 } 294 } 295 } 296 #endif