Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
165
iphone/Maps/UI/Settings/Profile/ExistingProfileView.swift
Normal file
165
iphone/Maps/UI/Settings/Profile/ExistingProfileView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
86
iphone/Maps/UI/Settings/Profile/NoExistingProfileView.swift
Normal file
86
iphone/Maps/UI/Settings/Profile/NoExistingProfileView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
104
iphone/Maps/UI/Settings/Profile/ProfileView.swift
Normal file
104
iphone/Maps/UI/Settings/Profile/ProfileView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
281
iphone/Maps/UI/Settings/SettingsNavigationView.swift
Normal file
281
iphone/Maps/UI/Settings/SettingsNavigationView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
365
iphone/Maps/UI/Settings/SettingsView.swift
Normal file
365
iphone/Maps/UI/Settings/SettingsView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue