taler-ios

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

SettingsView.swift (6167B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2026 Bohdan, Volodymyr Potuzhnyi
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 import SwiftUI
     18 
     19 
     20 private struct LanguageOption: Identifiable {
     21     let tag: String
     22     let label: String
     23     var id: String { tag }
     24 }
     25 
     26 private let supportedLanguages: [LanguageOption] = [
     27     LanguageOption(tag: "", label: S.settingsLanguageSystemDefault),
     28     LanguageOption(tag: "en", label: "English"),
     29     LanguageOption(tag: "de", label: "Deutsch"),
     30     LanguageOption(tag: "es", label: "Español"),
     31     LanguageOption(tag: "fi", label: "Suomi"),
     32     LanguageOption(tag: "fr", label: "Français"),
     33     LanguageOption(tag: "it", label: "Italiano"),
     34     LanguageOption(tag: "ru", label: "Русский"),
     35     LanguageOption(tag: "sv", label: "Svenska"),
     36     LanguageOption(tag: "tr", label: "Türkçe"),
     37     LanguageOption(tag: "uk", label: "Українська"),
     38     LanguageOption(tag: "he", label: "עברית"),
     39 ]
     40 
     41 struct SettingsView: View {
     42     @EnvironmentObject var configManager: ConfigManager
     43     @ObservedObject private var languageManager = LanguageManager.shared
     44     let onNavigate: (PosDestination) -> Void
     45     var openDrawer: (() -> Void)?
     46 
     47     private var username: String? {
     48         guard !configManager.merchantUrl.isEmpty,
     49               let url = URL(string: configManager.merchantUrl) else { return nil }
     50         return url.pathComponents.last(where: { !$0.isEmpty && $0 != "/" })
     51     }
     52 
     53     var body: some View {
     54         VStack(spacing: 0) {
     55             PosTopBar(title: S.menuSettings, onMenuTap: { openDrawer?() })
     56             List {
     57             Section {
     58                 Picker(S.settingsInitialOrderLabel, selection: Binding(
     59                     get: { configManager.initialOrderScreen },
     60                     set: { configManager.setInitialOrderScreen($0) }
     61                 )) {
     62                     Text(S.menuAmountEntry).tag(InitialOrderScreen.amountEntry)
     63                     Text(S.menuOrder).tag(InitialOrderScreen.inventory)
     64                 }
     65 
     66                 Picker(S.settingsLanguageLabel, selection: Binding(
     67                     get: { languageManager.selectedLanguage },
     68                     set: { languageManager.setLanguage($0) }
     69                 )) {
     70                     ForEach(supportedLanguages) { lang in
     71                         if lang.tag.isEmpty, let resolved = languageManager.resolvedLanguageLabel {
     72                             Text("\(lang.label) (\(resolved))").tag(lang.tag)
     73                         } else {
     74                             Text(lang.label).tag(lang.tag)
     75                         }
     76                     }
     77                 }
     78             } header: {
     79                 Text(S.settingsAppTitle)
     80                     .foregroundColor(.posOnSurface)
     81             }
     82 
     83             Section {
     84                 VStack(alignment: .leading, spacing: 12) {
     85                     Text(S.settingsInstanceDescription)
     86                         .font(.subheadline)
     87                         .foregroundColor(.posOnSurface)
     88 
     89                     Button {
     90                         onNavigate(.config)
     91                     } label: {
     92                         HStack {
     93                             Image(systemName: "gearshape.2")
     94                             Text(S.settingsInstanceButton)
     95                         }
     96                         .frame(maxWidth: .infinity)
     97                         .padding(.vertical, 8)
     98                         .background(Color.posPrimaryContainer)
     99                         .foregroundColor(.posOnPrimaryContainer)
    100                         .cornerRadius(8)
    101                     }
    102                     .buttonStyle(.plain)
    103 
    104                     Button {
    105                         configManager.logout()
    106                     } label: {
    107                         Text(S.settingsLogoutButton)
    108                             .frame(maxWidth: .infinity)
    109                             .padding(.vertical, 8)
    110                             .background(Color.posErrorContainer)
    111                             .foregroundColor(.posOnErrorContainer)
    112                             .cornerRadius(8)
    113                     }
    114                     .buttonStyle(.plain)
    115                 }
    116             } header: {
    117                 Text(S.settingsInstanceTitle)
    118                     .foregroundColor(.posOnSurface)
    119             }
    120 
    121             Section {
    122                 aboutRow(label: S.appName, value: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "")
    123                 if let username {
    124                     aboutRow(label: S.configUsername, value: username)
    125                 }
    126                 if !configManager.merchantUrl.isEmpty {
    127                     let suffix = configManager.backendVersion.map { " (\($0))" } ?? ""
    128                     aboutRow(label: S.configMerchantUrl, value: "\(configManager.merchantUrl)\(suffix)")
    129                 }
    130             } header: {
    131                 Text(S.settingsAboutTitle)
    132                     .foregroundColor(.posOnSurface)
    133             }
    134 
    135         }
    136         .scrollContentBackground(.hidden)
    137         .background(Color.posSurface)
    138         }
    139         .background(Color.posSurface)
    140         .navigationBarHidden(true)
    141         .toast(message: Binding(
    142             get: { languageManager.languageChangedToast },
    143             set: { languageManager.languageChangedToast = $0 }
    144         ), duration: 4)
    145     }
    146 
    147     private func aboutRow(label: String, value: String) -> some View {
    148         HStack(spacing: 4) {
    149             Text(label + ":")
    150                 .fontWeight(.bold)
    151             Text(value)
    152         }
    153         .font(.subheadline)
    154         .foregroundColor(.posOnSurfaceVariant)
    155     }
    156 }