Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,165 @@
import SwiftUI
/// View for the OpenStreetMapp profile
struct ExistingProfileView: View {
// MARK: Properties
/// The open url action of the environment
@Environment(\.openURL) private var openUrl
/// The date the profile information was last updated (this is necessary for automatically refreshing the view)
@Binding var lastUpdated: Date
/// If the login form should be shown in the Safari view
@State private var showLogin: Bool = false
/// If the edit history should be shown in the Safari view
@State private var showEditHistory: Bool = false
/// If the map notes should be shown in the Safari view
@State private var showNotes: Bool = false
/// The publisher to know when to stop showing the Safari view for the login form
private let stopShowingLoginPublisher = NotificationCenter.default.publisher(for: SafariView.dismissNotificationName)
/// If the profile is being presented as an alert
var isPresentedAsAlert: Bool = false
/// The actual view
var body: some View {
VStack(alignment: .leading) {
if Profile.needsReauthorization {
List {
Text("osm_profile_reauthorize_promt")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
}
} else if !isPresentedAsAlert {
List {
Section {
VStack {
Text(Profile.numberOfEdits ?? 0, format: .number)
.font(.largeTitle)
.bold()
.frame(maxWidth: .infinity)
Text("osm_profile_verfied_changes")
}
.padding([.top, .bottom])
.frame(maxWidth: .infinity)
} footer: {
if let editHistoryUrl = Profile.editHistoryUrl {
Button {
showEditHistory = true
} label: {
Text("osm_profile_view_edit_history")
.lineLimit(1)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedProminentButtonStyle())
.controlSize(.large)
.font(.headline)
.sheet(isPresented: $showEditHistory) {
SafariView(url: editHistoryUrl, dismissButton: .close)
}
.padding([.top, .bottom])
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
if let notesUrl = Profile.notesUrl {
Section {
Button {
showNotes = true
} label: {
Text("osm_profile_view_notes")
.lineLimit(1)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedProminentButtonStyle())
.buttonBorderShape(.roundedRectangle(radius: 0))
.controlSize(.large)
.font(.headline)
.sheet(isPresented: $showNotes) {
SafariView(url: notesUrl, dismissButton: .close)
}
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}
.refreshable {
await Profile.reload()
withAnimation {
lastUpdated = Date.now
}
}
}
Spacer(minLength: 0)
VStack {
if Profile.needsReauthorization {
Button {
showLogin = true
} label: {
Text("osm_profile_reauthorize")
.lineLimit(1)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedProminentButtonStyle())
.controlSize(.large)
.font(.headline)
.sheet(isPresented: $showLogin) {
SafariView(url: Profile.authorizationUrl, dismissButton: .cancel)
}
Divider()
.padding([.top, .bottom])
VStack(alignment: .leading) {
Text("osm_profile_remove_promt")
Button {
Profile.logout()
lastUpdated = Date.now
} label: {
Text("osm_profile_remove")
.lineLimit(1)
.foregroundStyle(.alternativeAccent)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedButtonStyle())
.controlSize(.large)
.font(.headline)
}
} else if !isPresentedAsAlert {
Button {
if let wikiUrl = URL(string: String(localized: "osm_more_about_url")) {
openUrl(wikiUrl)
}
} label: {
Text("osm_more_about")
.lineLimit(1)
.foregroundStyle(.alternativeAccent)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedButtonStyle())
.controlSize(.large)
.font(.headline)
}
}
.padding([.bottom, .leading, .trailing])
.background(Color(uiColor: .systemGroupedBackground))
}
.onReceive(stopShowingLoginPublisher) { _ in
showLogin = false
}
}
}

View file

@ -0,0 +1,86 @@
import SwiftUI
/// View for the OpenStreetMapp profile
struct NoExistingProfileView: View {
// MARK: Properties
/// The open url action of the environment
@Environment(\.openURL) private var openUrl
/// If the login form should be shown in the Safari view
@State private var showLogin: Bool = false
/// The publisher to know when to stop showing the Safari view for the login form
private let stopShowingLoginPublisher = NotificationCenter.default.publisher(for: SafariView.dismissNotificationName)
/// The actual view
var body: some View {
VStack(alignment: .leading) {
List {
VStack(alignment: .leading) {
Text("osm_profile_promt")
.font(.headline)
HStack(alignment: .top) {
Image(.openStreetMapLogo)
.resizable()
.aspectRatio(1, contentMode: .fit)
.frame(maxWidth: 50)
.padding(.top, 6)
Text("osm_profile_explanation")
.tint(.alternativeAccent)
}
}
.padding(.bottom, 8)
}
Spacer(minLength: 0)
VStack {
VStack {
Button {
showLogin = true
} label: {
Text("osm_profile_login")
.lineLimit(1)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedProminentButtonStyle())
.controlSize(.large)
.font(.headline)
.sheet(isPresented: $showLogin) {
SafariView(url: Profile.authorizationUrl, dismissButton: .cancel)
}
}
Divider()
.padding([.top, .bottom])
VStack(alignment: .leading) {
Text("osm_profile_register_promt")
Button {
openUrl(Profile.registrationUrl)
} label: {
Text("osm_profile_register")
.lineLimit(1)
.foregroundStyle(.alternativeAccent)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedButtonStyle())
.controlSize(.large)
.font(.headline)
}
}
.padding([.bottom, .leading, .trailing])
.background(Color(uiColor: .systemGroupedBackground))
}
.onReceive(stopShowingLoginPublisher) { _ in
showLogin = false
}
}
}

View file

@ -0,0 +1,104 @@
import SwiftUI
/// View for the OpenStreetMap profile
struct ProfileView: View {
// MARK: Properties
/// The dismiss action of the environment
@Environment(\.dismiss) private var dismiss
/// The open url action of the environment
@Environment(\.openURL) private var openUrl
/// The date the profile information was last updated (this is necessary for automatically refreshing the view)
@State private var lastUpdated: Date = Date.now
/// If the profile is being presented as an alert
var isPresentedAsAlert: Bool = false
/// The publisher to know when to stop showing the Safari view for the login form
private let stopShowingLoginPublisher = NotificationCenter.default.publisher(for: SafariView.dismissNotificationName)
/// The actual view
var body: some View {
VStack(spacing: 0) {
if isPresentedAsAlert {
Button {
dismiss()
} label: {
Label {
Text("close")
} icon: {
Image(systemName: "xmark.circle.fill")
.font(.title)
}
}
.labelStyle(.iconOnly)
.buttonStyle(PlainButtonStyle())
.foregroundStyle(.primary)
.padding([.top, .leading, .trailing])
.frame(maxWidth: .infinity, alignment: .trailing)
.background(Color(uiColor: .systemGroupedBackground))
}
if Profile.isExisting {
ExistingProfileView(lastUpdated: $lastUpdated, isPresentedAsAlert: isPresentedAsAlert)
} else {
NoExistingProfileView()
}
}
.accentColor(.accent)
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle(Profile.name ?? String(localized: "osm_profile"))
.toolbar {
ToolbarItem(placement: .destructiveAction) {
if !isPresentedAsAlert, Profile.isExisting, !Profile.needsReauthorization {
Menu {
Button {
Profile.logout()
lastUpdated = Date.now
} label: {
if #available(iOS 16, *) {
Label("osm_profile_logout", systemImage: "rectangle.portrait.and.arrow.forward")
} else {
Label("osm_profile_logout", systemImage: "power")
}
}
Button(role: .destructive) {
openUrl(Profile.deleteUrl)
} label: {
Label("osm_profile_delete", systemImage: "trash")
}
} label: {
if #available(iOS 16, *) {
Label("osm_profile_logout", systemImage: "rectangle.portrait.and.arrow.forward")
} else {
Label("osm_profile_logout", systemImage: "power")
}
} primaryAction: {
Profile.logout()
lastUpdated = Date.now
}
}
}
}
.task {
await Profile.reload()
withAnimation {
lastUpdated = Date.now
}
}
.tag(lastUpdated)
.onReceive(stopShowingLoginPublisher) { _ in
if isPresentedAsAlert {
dismiss()
}
}
}
}

View file

@ -0,0 +1,281 @@
import SwiftUI
/// View for the navigation settings
struct SettingsNavigationView: View {
// MARK: Properties
/// The scene phase of the environment
@Environment(\.scenePhase) private var scenePhase
/// If the perspective view should be used during routing
@State var hasPerspectiveViewWhileRouting: Bool = true
/// If auto zoom should be used during routing
@State var hasAutoZoomWhileRouting: Bool = true
/// If voice guidance should be provided during routing
@State var shouldProvideVoiceRouting: Bool = true
/// The selected language for voice guidance during routing
@State var selectedLanguageForVoiceRouting: Settings.VoiceRoutingLanguage.ID? = nil
/// If street names should be announced in the voice guidance during routing
@State var shouldAnnounceStreetnamesWhileVoiceRouting: Bool = false
/// The selected announcement of speed traps in the voice guidance during routing
@State var selectedAnnouncingSpeedTrapsWhileVoiceRouting: Settings.AnnouncingSpeedTrapsWhileVoiceRouting = .never
/// If toll roads should be avoided during routing
@State var shouldAvoidTollRoadsWhileRouting: Bool = false
/// If unpaved roads should be avoided during routing
@State var shouldAvoidUnpavedRoadsWhileRouting: Bool = false
/// If paved roads should be avoided during routing
@State var shouldAvoidPavedRoadsWhileRouting: Bool = false
/// If ferries should be avoided during routing
@State var shouldAvoidFerriesWhileRouting: Bool = false
/// If motorways should be avoided during routing
@State var shouldAvoidMotorwaysWhileRouting: Bool = false
/// If steps should be avoided during routing
@State var shouldAvoidStepsWhileRouting: Bool = false
/// A date for forcing a refresh of the view
@State var forceRefreshDate: Date = Date.now
/// The actual view
var body: some View {
List {
Section {
Toggle("pref_map_3d_title", isOn: $hasPerspectiveViewWhileRouting)
.tint(.accent)
Toggle("pref_map_auto_zoom", isOn: $hasAutoZoomWhileRouting)
.tint(.accent)
}
Section {
Toggle("pref_tts_enable_title", isOn: $shouldProvideVoiceRouting)
.tint(.accent)
if shouldProvideVoiceRouting {
Picker(selection: $selectedLanguageForVoiceRouting) {
ForEach(Settings.availableLanguagesForVoiceRouting) { languageForVoiceRouting in
Text(languageForVoiceRouting.localizedName)
.tag(languageForVoiceRouting.id)
}
} label: {
Text("pref_tts_language_title")
}
HStack {
VStack(alignment: .leading) {
Text("voice")
if #available(iOS 26, *) {
Text("voice_explanation")
.font(.footnote)
.foregroundStyle(.secondary)
} else {
Text("voice_explanation_before_version26")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
Spacer()
Text(Settings.voiceForVoiceRouting ?? "unknown")
.foregroundStyle(.secondary)
.id(UUID())
}
Toggle(isOn: $shouldAnnounceStreetnamesWhileVoiceRouting) {
VStack(alignment: .leading) {
Text("pref_tts_street_names_title")
Text("pref_tts_street_names_description")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
.tint(.accent)
Picker(selection: $selectedAnnouncingSpeedTrapsWhileVoiceRouting) {
ForEach(Settings.AnnouncingSpeedTrapsWhileVoiceRouting.allCases) { announcingSpeedTrapsWhileVoiceRouting in
Text(announcingSpeedTrapsWhileVoiceRouting.description)
}
} label: {
Text("speedcams_alert_title")
}
}
} header: {
Text("pref_tts_title")
} footer: {
if shouldProvideVoiceRouting {
Button {
Settings.playVoiceRoutingTest()
} label: {
Text("pref_tts_test_voice_title")
.bold()
.lineLimit(1)
.padding(4)
.frame(maxWidth: .infinity)
}
.buttonStyle(BorderedButtonStyle())
.foregroundStyle(.alternativeAccent)
.padding([.top, .bottom])
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
.id(forceRefreshDate)
Section {
Toggle(isOn: $shouldAvoidTollRoadsWhileRouting) {
Label {
Text("avoid_tolls")
} icon: {
Image(shouldAvoidTollRoadsWhileRouting ? "tolls.slash" : "tolls")
.foregroundStyle(.secondary)
}
}
.tint(.accent)
Toggle(isOn: $shouldAvoidUnpavedRoadsWhileRouting) {
Label {
Text("avoid_unpaved")
} icon: {
Image(shouldAvoidUnpavedRoadsWhileRouting ? "unpaved.slash" : "unpaved")
.foregroundStyle(.secondary)
}
}
.tint(.accent)
.disabled(shouldAvoidPavedRoadsWhileRouting)
Toggle(isOn: $shouldAvoidPavedRoadsWhileRouting) {
Label {
Text("avoid_paved")
} icon: {
Image(shouldAvoidPavedRoadsWhileRouting ? "paved.slash" : "paved")
.foregroundStyle(.secondary)
}
}
.tint(.accent)
.disabled(shouldAvoidUnpavedRoadsWhileRouting)
Toggle(isOn: $shouldAvoidMotorwaysWhileRouting) {
Label {
Text("avoid_motorways")
} icon: {
Image(shouldAvoidMotorwaysWhileRouting ? "motorways.slash" : "motorways")
.foregroundStyle(.secondary)
}
}
.tint(.accent)
Toggle(isOn: $shouldAvoidFerriesWhileRouting) {
Label {
Text("avoid_ferry")
} icon: {
Image(shouldAvoidFerriesWhileRouting ? "ferries.slash" : "ferries")
.foregroundStyle(.secondary)
}
}
.tint(.accent)
Toggle(isOn: $shouldAvoidStepsWhileRouting) {
Label {
Text("avoid_steps")
} icon: {
Image(shouldAvoidStepsWhileRouting ? "steps.slash" : "steps")
.foregroundStyle(.secondary)
}
}
.tint(.accent)
} header: {
Text("driving_options_title")
}
}
.accentColor(.accent)
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle("prefs_group_route")
.onAppear {
hasPerspectiveViewWhileRouting = Settings.hasPerspectiveViewWhileRouting
hasAutoZoomWhileRouting = Settings.hasAutoZoomWhileRouting
shouldProvideVoiceRouting = Settings.shouldProvideVoiceRouting
selectedLanguageForVoiceRouting = Settings.languageForVoiceRouting
shouldAnnounceStreetnamesWhileVoiceRouting = Settings.shouldAnnounceStreetnamesWhileVoiceRouting
selectedAnnouncingSpeedTrapsWhileVoiceRouting = Settings.announcingSpeedTrapsWhileVoiceRouting
shouldAvoidTollRoadsWhileRouting = Settings.shouldAvoidTollRoadsWhileRouting
shouldAvoidUnpavedRoadsWhileRouting = Settings.shouldAvoidUnpavedRoadsWhileRouting
shouldAvoidPavedRoadsWhileRouting = Settings.shouldAvoidPavedRoadsWhileRouting
shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
}
.onChange(of: scenePhase) { _ in
forceRefreshDate = Date.now
}
.onChange(of: hasPerspectiveViewWhileRouting) { changedHasPerspectiveViewWhileRouting in
Settings.hasPerspectiveViewWhileRouting = changedHasPerspectiveViewWhileRouting
}
.onChange(of: hasAutoZoomWhileRouting) { changedHasAutoZoomWhileRouting in
Settings.hasAutoZoomWhileRouting = changedHasAutoZoomWhileRouting
}
.onChange(of: shouldProvideVoiceRouting) { changedShouldProvideVoiceRouting in
Settings.shouldProvideVoiceRouting = changedShouldProvideVoiceRouting
}
.onChange(of: selectedLanguageForVoiceRouting) { changedSelectedLanguageForVoiceRouting in
if let changedSelectedLanguageForVoiceRouting {
Settings.languageForVoiceRouting = changedSelectedLanguageForVoiceRouting
}
}
.onChange(of: shouldAnnounceStreetnamesWhileVoiceRouting) { changedShouldAnnounceStreetnamesWhileVoiceRouting in
Settings.shouldAnnounceStreetnamesWhileVoiceRouting = changedShouldAnnounceStreetnamesWhileVoiceRouting
}
.onChange(of: selectedAnnouncingSpeedTrapsWhileVoiceRouting) { changedSelectedAnnouncingSpeedTrapsWhileVoiceRouting in
Settings.announcingSpeedTrapsWhileVoiceRouting = changedSelectedAnnouncingSpeedTrapsWhileVoiceRouting
}
.onChange(of: shouldAvoidTollRoadsWhileRouting) { changedShouldAvoidTollRoadsWhileRouting in
Settings.shouldAvoidTollRoadsWhileRouting = changedShouldAvoidTollRoadsWhileRouting
}
.onChange(of: shouldAvoidUnpavedRoadsWhileRouting) { changedShouldAvoidUnpavedRoadsWhileRouting in
Settings.shouldAvoidUnpavedRoadsWhileRouting = changedShouldAvoidUnpavedRoadsWhileRouting
if changedShouldAvoidUnpavedRoadsWhileRouting {
shouldAvoidPavedRoadsWhileRouting = false
}
}
.onChange(of: shouldAvoidPavedRoadsWhileRouting) { changedShouldAvoidPavedRoadsWhileRouting in
Settings.shouldAvoidPavedRoadsWhileRouting = changedShouldAvoidPavedRoadsWhileRouting
if changedShouldAvoidPavedRoadsWhileRouting {
shouldAvoidUnpavedRoadsWhileRouting = false
}
}
.onChange(of: shouldAvoidFerriesWhileRouting) { changedShouldAvoidFerriesWhileRouting in
Settings.shouldAvoidFerriesWhileRouting = changedShouldAvoidFerriesWhileRouting
}
.onChange(of: shouldAvoidMotorwaysWhileRouting) { changedShouldAvoidMotorwaysWhileRouting in
Settings.shouldAvoidMotorwaysWhileRouting = changedShouldAvoidMotorwaysWhileRouting
}
.onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in
Settings.shouldAvoidStepsWhileRouting = changedShouldAvoidStepsWhileRouting
}
}
}

View file

@ -0,0 +1,365 @@
import SwiftUI
/// View for the settings
struct SettingsView: View {
// MARK: Properties
/// The dismiss action of the environment
@Environment(\.dismiss) private var dismiss
/// The selected distance unit
@State private var selectedDistanceUnit: Settings.DistanceUnit = .metric
/// If zoom buttons should be displayed
@State private var hasZoomButtons: Bool = true
/// The selected left button type
@State private var selectedLeftButtonType: Settings.LeftButtonType = .help
/// If 3D buildings should be displayed
@State private var has3dBuildings: Bool = true
/// If automatic map downloads should be enabled
@State private var hasAutomaticDownload: Bool = true
/// If an increased font size should be used for map labels
@State private var hasIncreasedFontsize: Bool = false
/// The selected language for the map
@State var selectedLanguageForMap: Settings.MapLanguage.ID? = nil
/// If names should be transliterated to Latin
@State private var shouldTransliterateToLatin: Bool = true
/// The selected map appearance
@State private var selectedMapAppearance: Settings.Appearance = .auto
/// The selected appearance
@State private var selectedAppearance: Settings.Appearance = .auto
/// If the bookmarks should be synced via iCloud
@State private var shouldSync: Bool = false
/// If the sync beta alert should be shown
@State private var showSyncBetaAlert: Bool = false
/// If the sync is possible
@State private var isSyncPossible: Bool = true
/// If the compass should be calibrated
@State private var shouldCalibrateCompass: Bool = true
/// The selected power saving mode
@State private var selectedPowerSavingMode: Settings.PowerSavingMode = .never
/// The selected mobile data policy
@State private var selectedMobileDataPolicy: Settings.MobileDataPolicy = .always
/// If our custom logging is enabled
@State private var isLogging: Bool = false
/// The actual view
var body: some View {
NavigationView {
List {
Section {
NavigationLink {
ProfileView()
} label: {
HStack {
Text("osm_profile")
.lineLimit(1)
.layoutPriority(2)
Spacer(minLength: 0)
.layoutPriority(0)
Text(Profile.name ?? String())
.lineLimit(1)
.foregroundStyle(.secondary)
.layoutPriority(1)
}
}
}
Section {
Picker(selection: $selectedDistanceUnit) {
ForEach(Settings.DistanceUnit.allCases) { distanceUnit in
Text(distanceUnit.description)
}
} label: {
Text("measurement_units")
}
Toggle("pref_zoom_title", isOn: $hasZoomButtons)
.tint(.accent)
Picker(selection: $selectedLeftButtonType) {
ForEach(Settings.LeftButtonType.allCases) { leftButtonType in
Text(leftButtonType.description)
}
} label: {
Text("pref_left_button_type")
}
Toggle(isOn: $has3dBuildings) {
VStack(alignment: .leading) {
Text("pref_map_3d_buildings_title")
if selectedPowerSavingMode == .maximum {
Text("pref_map_3d_buildings_disabled_summary")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
.tint(.accent)
.disabled(selectedPowerSavingMode == .maximum)
Toggle("autodownload", isOn: $hasAutomaticDownload)
.tint(.accent)
Toggle("big_font", isOn: $hasIncreasedFontsize)
.tint(.accent)
Picker(selection: $selectedLanguageForMap) {
ForEach(Settings.availableLanguagesForMap) { languageForMap in
Text(languageForMap.localizedName)
.tag(languageForMap.id)
if languageForMap.id == "auto" || languageForMap.id == "default" {
Divider()
}
}
} label: {
Text("pref_maplanguage_title")
}
Toggle(isOn: $shouldTransliterateToLatin) {
VStack(alignment: .leading) {
Text("transliteration_title")
if selectedLanguageForMap == "default" {
Text("transliteration_title_disabled_summary")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
.tint(.accent)
.disabled(selectedLanguageForMap == "default")
Picker(selection: $selectedMapAppearance) {
ForEach(Settings.Appearance.allCases) { mapAppearance in
Text(mapAppearance.description)
}
} label: {
Text("pref_mapappearance_title")
}
}
NavigationLink("prefs_group_route") {
SettingsNavigationView()
}
Section {
Toggle(isOn: $shouldSync) {
VStack(alignment: .leading) {
Text("icloud_sync")
if !isSyncPossible {
Text("icloud_disabled_message")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
.tint(.accent)
.disabled(!isSyncPossible)
.alert("enable_icloud_synchronization_title", isPresented: $showSyncBetaAlert) {
Button {
Settings.hasShownSyncBetaAlert = true
Settings.shouldSync = true
shouldSync = true
} label: {
Text("enable")
}
Button {
Settings.createBookmarksBackupBecauseOfSyncBeta { hasCreatedBackup in
if hasCreatedBackup {
Settings.hasShownSyncBetaAlert = true
Settings.shouldSync = true
shouldSync = true
} else {
Settings.shouldSync = false
shouldSync = false
}
}
} label: {
Text("backup")
}
Button(role: .cancel) {
// Do nothing
} label: {
Text("cancel")
}
} message: {
Text("enable_icloud_synchronization_message")
}
}
Section {
Picker(selection: $selectedAppearance) {
ForEach(Settings.Appearance.allCases) { appearance in
Text(appearance.description)
}
} label: {
Text("pref_appearance_title")
}
Toggle("pref_calibration_title", isOn: $shouldCalibrateCompass)
.tint(.accent)
Picker(selection: $selectedPowerSavingMode) {
ForEach(Settings.PowerSavingMode.allCases) { powerSavingMode in
Text(powerSavingMode.description)
}
} label: {
Text("power_managment_title")
}
Picker(selection: $selectedMobileDataPolicy) {
ForEach(Settings.MobileDataPolicy.allCases) { mobileDataPolicy in
Text(mobileDataPolicy.description)
}
} label: {
Text("mobile_data")
}
}
Section {
Toggle(isOn: $isLogging) {
VStack(alignment: .leading) {
Text("enable_logging")
if isLogging {
Text(Settings.logSize, formatter: Settings.logSizeFormatter)
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
.tint(.accent)
} footer: {
Text("enable_logging_warning_message")
}
}
.navigationTitle("settings")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button {
dismiss()
} label: {
Text("close")
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
.onAppear {
selectedDistanceUnit = Settings.distanceUnit
hasZoomButtons = Settings.hasZoomButtons
selectedLeftButtonType = Settings.leftButtonType
has3dBuildings = Settings.has3dBuildings
hasAutomaticDownload = Settings.hasAutomaticDownload
hasIncreasedFontsize = Settings.hasIncreasedFontsize
selectedLanguageForMap = Settings.languageForMap
shouldTransliterateToLatin = Settings.shouldTransliterateToLatin
selectedMapAppearance = Settings.mapAppearance
selectedAppearance = Settings.appearance
shouldSync = Settings.shouldSync
shouldCalibrateCompass = Settings.shouldCalibrateCompass
selectedPowerSavingMode = Settings.powerSavingMode
selectedMobileDataPolicy = Settings.mobileDataPolicy
isLogging = Settings.isLogging
}
.onChange(of: selectedDistanceUnit) { changedSelectedDistanceUnit in
Settings.distanceUnit = changedSelectedDistanceUnit
}
.onChange(of: hasZoomButtons) { changedHasZoomButtons in
Settings.hasZoomButtons = changedHasZoomButtons
}
.onChange(of: selectedLeftButtonType) { changedSelectedLeftButtonType in
Settings.leftButtonType = changedSelectedLeftButtonType
}
.onChange(of: has3dBuildings) { changedHas3dBuildings in
Settings.has3dBuildings = changedHas3dBuildings
}
.onChange(of: hasAutomaticDownload) { changedHasAutomaticDownload in
Settings.hasAutomaticDownload = changedHasAutomaticDownload
}
.onChange(of: hasIncreasedFontsize) { changedHasIncreasedFontsize in
Settings.hasIncreasedFontsize = changedHasIncreasedFontsize
}
.onChange(of: selectedLanguageForMap) { changedSelectedLanguageForMap in
if let changedSelectedLanguageForMap {
Settings.languageForMap = changedSelectedLanguageForMap
}
}
.onChange(of: shouldTransliterateToLatin) { changedShouldTransliterateToLatin in
Settings.shouldTransliterateToLatin = changedShouldTransliterateToLatin
}
.onChange(of: selectedMapAppearance) { changedSelectedMapAppearance in
Settings.mapAppearance = changedSelectedMapAppearance
}
.onChange(of: selectedAppearance) { changedSelectedAppearance in
Settings.appearance = changedSelectedAppearance
}
.onChange(of: shouldSync) { changedShouldSync in
if changedShouldSync, !Settings.hasShownSyncBetaAlert {
showSyncBetaAlert = true
shouldSync = false
} else {
Settings.shouldSync = changedShouldSync
}
}
.onChange(of: shouldCalibrateCompass) { changedShouldCalibrateCompass in
Settings.shouldCalibrateCompass = changedShouldCalibrateCompass
}
.onChange(of: selectedPowerSavingMode) { changedSelectedPowerSavingMode in
Settings.powerSavingMode = changedSelectedPowerSavingMode
}
.onChange(of: selectedMobileDataPolicy) { changedSelectedMobileDataPolicy in
Settings.mobileDataPolicy = changedSelectedMobileDataPolicy
}
.onChange(of: isLogging) { changedIsLogging in
Settings.isLogging = changedIsLogging
}
.onReceive(Settings.syncStatePublisher) { syncState in
isSyncPossible = syncState.isAvailable
}
.accentColor(.toolbarAccent)
}
}