taler-ios

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

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