taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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