taler-ios

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

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