Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
173
iphone/Maps/Model/Profile.swift
Normal file
173
iphone/Maps/Model/Profile.swift
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import Network
|
||||
import OSMEditor
|
||||
|
||||
/// The OpenStreetMap profile
|
||||
@objc class Profile: NSObject {
|
||||
// MARK: Properties
|
||||
|
||||
/// Key for storing the current authorization token in the user defaults
|
||||
static private let userDefaultsKeyAuthorizationToken = "OSMAuthToken"
|
||||
|
||||
|
||||
/// Key for storing the name of the OpenStreetMap profile in the user defaults
|
||||
static private let userDefaultsKeyName = "UDOsmUserName"
|
||||
|
||||
|
||||
/// Key for storing the number of edits of the OpenStreetMap profile in the user defaults
|
||||
static private let userDefaultsKeyNumberOfEdits = "OSMUserChangesetsCount"
|
||||
|
||||
|
||||
/// Key for storing iff the OpenStreetMap profile needs to be reauthorized in the user defaults
|
||||
static private let userDefaultsNeedsReauthorization = "AuthNeedCheck"
|
||||
|
||||
|
||||
/// The URL for registering an OpenStreetMap profile
|
||||
static var registrationUrl: URL {
|
||||
return URL(string: String(describing: osm.OsmOAuth.ServerAuth().GetRegistrationURL()))!
|
||||
}
|
||||
|
||||
|
||||
/// The URL for letting an OpenStreetMap profile authorize this app
|
||||
static var authorizationUrl: URL {
|
||||
return URL(string: String(describing: osm.OsmOAuth.ServerAuth().BuildOAuth2Url()))!
|
||||
}
|
||||
|
||||
|
||||
/// The optional current authorization token (can be empty)
|
||||
@objc static var authorizationToken: String? {
|
||||
if let authorizationToken = UserDefaults.standard.string(forKey: userDefaultsKeyAuthorizationToken), !authorizationToken.isEmpty {
|
||||
return authorizationToken
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
/// If the OpenStreetMap profile needs to be reauthorized
|
||||
@objc static var needsReauthorization: Bool {
|
||||
return UserDefaults.standard.bool(forKey: userDefaultsNeedsReauthorization)
|
||||
}
|
||||
|
||||
|
||||
/// If there is an OpenStreetMap profile existing in the app
|
||||
@objc static var isExisting: Bool {
|
||||
return authorizationToken != nil
|
||||
}
|
||||
|
||||
|
||||
/// The optional name of the OpenStreetMap profile
|
||||
@objc static var name: String? {
|
||||
if isExisting {
|
||||
return UserDefaults.standard.string(forKey: userDefaultsKeyName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
/// The optional number of edits of the OpenStreetMap profile
|
||||
static var numberOfEdits: Int? {
|
||||
if isExisting {
|
||||
return UserDefaults.standard.integer(forKey: userDefaultsKeyNumberOfEdits)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
/// The optional URL for the edit history of the OpenStreetMap profile
|
||||
static var editHistoryUrl: URL? {
|
||||
if let name, let url = URL(string: String(describing: osm.OsmOAuth.ServerAuth().GetHistoryURL(std.string(name)))) {
|
||||
return url
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
/// The optional URL for the map notes of the OpenStreetMap profile
|
||||
static var notesUrl: URL? {
|
||||
if let name, let url = URL(string: String(describing: osm.OsmOAuth.ServerAuth().GetNotesURL(std.string(name)))) {
|
||||
return url
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
/// The URL for deleting an OpenStreetMap profile
|
||||
static var deleteUrl: URL {
|
||||
return URL(string: String(describing: osm.OsmOAuth.ServerAuth().GetDeleteURL()))!
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Save the authorization token based on a code during the Oauth process
|
||||
/// - Parameter authorizationCode: The code
|
||||
static func saveAuthorizationToken(from authorizationCode: String) async {
|
||||
var serverAuth = osm.OsmOAuth.ServerAuth()
|
||||
|
||||
let authorizationToken = String(describing: serverAuth.FinishAuthorization(std.string(authorizationCode)))
|
||||
|
||||
serverAuth.SetAuthToken(std.string(authorizationToken))
|
||||
|
||||
let userDefaults = UserDefaults.standard
|
||||
userDefaults.set(authorizationToken, forKey: userDefaultsKeyAuthorizationToken)
|
||||
if let userPreferences = await reloadUserPreferences() {
|
||||
userDefaults.set(String(describing: userPreferences.m_displayName), forKey: userDefaultsKeyName)
|
||||
userDefaults.set(Int(userPreferences.m_changesets), forKey: userDefaultsKeyNumberOfEdits)
|
||||
userDefaults.set(false, forKey: userDefaultsNeedsReauthorization)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Reload the OpenStreetMap profile data
|
||||
/// - Returns: Optional profile data
|
||||
static private func reloadUserPreferences() async -> osm.UserPreferences? {
|
||||
var userPreferences: osm.UserPreferences? = nil
|
||||
userPreferences = osm.ServerApi06(osm.OsmOAuth.ServerAuth(std.string(authorizationToken ?? ""))).GetUserPreferences()
|
||||
if let userPreferences, userPreferences.m_id == 0 {
|
||||
return nil
|
||||
}
|
||||
return userPreferences
|
||||
}
|
||||
|
||||
|
||||
/// Reload the OpenStreetMap profile
|
||||
static func reload() async {
|
||||
if isExisting {
|
||||
// Could be done in nicer way, but that would require iOS 17+
|
||||
await withCheckedContinuation { continuation in
|
||||
let networkPathMonitor: NWPathMonitor = NWPathMonitor()
|
||||
networkPathMonitor.pathUpdateHandler = { path in
|
||||
Task {
|
||||
if path.status != .unsatisfied {
|
||||
let userDefaults = UserDefaults.standard
|
||||
if let userPreferences = await reloadUserPreferences() {
|
||||
userDefaults.set(String(describing: userPreferences.m_displayName), forKey: userDefaultsKeyName)
|
||||
userDefaults.set(Int(userPreferences.m_changesets), forKey: userDefaultsKeyNumberOfEdits)
|
||||
} else if path.status == .satisfied {
|
||||
userDefaults.set(true, forKey: userDefaultsNeedsReauthorization)
|
||||
}
|
||||
}
|
||||
networkPathMonitor.cancel()
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
networkPathMonitor.start(queue: .main)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Logout of the OpenStreetMap profile
|
||||
static func logout() {
|
||||
let userDefaults = UserDefaults.standard
|
||||
userDefaults.removeObject(forKey: userDefaultsKeyAuthorizationToken)
|
||||
userDefaults.removeObject(forKey: userDefaultsKeyName)
|
||||
userDefaults.removeObject(forKey: userDefaultsKeyNumberOfEdits)
|
||||
userDefaults.removeObject(forKey: userDefaultsNeedsReauthorization)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
extension Settings {
|
||||
/// The announcing of speed traps for voice guidance during routing
|
||||
@objc enum AnnouncingSpeedTrapsWhileVoiceRouting: Int, Codable, CaseIterable, Identifiable {
|
||||
case always = 1
|
||||
case onlyWhenTooFast = 2
|
||||
case never = 0
|
||||
|
||||
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
var id: Self { self }
|
||||
|
||||
|
||||
/// The description text
|
||||
var description: String {
|
||||
switch self {
|
||||
case .always:
|
||||
return String(localized: "pref_tts_speedcams_always")
|
||||
case .onlyWhenTooFast:
|
||||
return String(localized: "pref_tts_speedcams_auto")
|
||||
case .never:
|
||||
return String(localized: "pref_tts_speedcams_never")
|
||||
default:
|
||||
return String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
iphone/Maps/Model/Settings Types/Appearance.swift
Normal file
30
iphone/Maps/Model/Settings Types/Appearance.swift
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
extension Settings {
|
||||
/// The visual appeareance
|
||||
@objc enum Appearance: Int, Codable, CaseIterable, Identifiable {
|
||||
case auto = 1
|
||||
case light = 2
|
||||
case dark = 3
|
||||
|
||||
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
var id: Self { self }
|
||||
|
||||
|
||||
/// The description text
|
||||
var description: String {
|
||||
switch self {
|
||||
case .auto:
|
||||
return String(localized: "auto")
|
||||
case .light:
|
||||
return String(localized: "pref_appearance_light")
|
||||
case .dark:
|
||||
return String(localized: "pref_appearance_dark")
|
||||
default:
|
||||
return String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
iphone/Maps/Model/Settings Types/DistanceUnit.swift
Normal file
27
iphone/Maps/Model/Settings Types/DistanceUnit.swift
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
extension Settings {
|
||||
/// The unit system used for distances
|
||||
@objc enum DistanceUnit: Int, Codable, CaseIterable, Identifiable {
|
||||
case metric = 0
|
||||
case imperial = 1
|
||||
|
||||
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
var id: Self { self }
|
||||
|
||||
|
||||
/// The description text
|
||||
var description: String {
|
||||
switch self {
|
||||
case .metric:
|
||||
return String(localized: "kilometres")
|
||||
case .imperial:
|
||||
return String(localized: "miles")
|
||||
default:
|
||||
return String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
iphone/Maps/Model/Settings Types/Language.swift
Normal file
31
iphone/Maps/Model/Settings Types/Language.swift
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
extension Settings {
|
||||
/// A language
|
||||
protocol Language: Codable, Identifiable, Equatable, Comparable {
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
var id: String { get }
|
||||
|
||||
|
||||
/// The localized name
|
||||
var localizedName: String { get }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Comparable
|
||||
extension Settings.Language {
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Comparable
|
||||
extension Settings.Language {
|
||||
static func < (lhs: Self, rhs: Self) -> Bool {
|
||||
return lhs.localizedName.localizedCaseInsensitiveCompare(rhs.localizedName) == .orderedAscending
|
||||
}
|
||||
}
|
||||
52
iphone/Maps/Model/Settings Types/LeftButtonType.swift
Normal file
52
iphone/Maps/Model/Settings Types/LeftButtonType.swift
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
extension Settings {
|
||||
/// The type of the left bottom bar button
|
||||
enum LeftButtonType: String, Codable, CaseIterable, Identifiable {
|
||||
case hidden = "Hidden"
|
||||
case addPlace = "AddPlace"
|
||||
case recordTrack = "RecordTrack"
|
||||
case settings = "Settings"
|
||||
case help = "Help"
|
||||
|
||||
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
var id: Self { self }
|
||||
|
||||
|
||||
/// The description text
|
||||
var description: String {
|
||||
switch self {
|
||||
case .hidden:
|
||||
return String(localized: "disabled")
|
||||
case .addPlace:
|
||||
return String(localized: "placepage_add_place_button")
|
||||
case .recordTrack:
|
||||
return String(localized: "start_track_recording")
|
||||
case .settings:
|
||||
return String(localized: "settings")
|
||||
case .help:
|
||||
return String(localized: "help")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The image
|
||||
var image: UIImage {
|
||||
let configuration = UIImage.SymbolConfiguration(pointSize: 24, weight: .semibold)
|
||||
switch self {
|
||||
case .addPlace:
|
||||
return UIImage(systemName: "plus", withConfiguration: configuration)!
|
||||
case .recordTrack:
|
||||
return UIImage(named: "track", in: nil, with: configuration)!
|
||||
case .settings:
|
||||
return UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .semibold))!
|
||||
case .help:
|
||||
return UIImage(systemName: "info.circle", withConfiguration: configuration)!
|
||||
default:
|
||||
return UIImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
iphone/Maps/Model/Settings Types/MapLanguage.swift
Normal file
13
iphone/Maps/Model/Settings Types/MapLanguage.swift
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
extension Settings {
|
||||
/// A language used for the map
|
||||
struct MapLanguage: Language {
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
let id: String
|
||||
|
||||
|
||||
/// The localized name
|
||||
let localizedName: String
|
||||
}
|
||||
}
|
||||
30
iphone/Maps/Model/Settings Types/MobileDataPolicy.swift
Normal file
30
iphone/Maps/Model/Settings Types/MobileDataPolicy.swift
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
extension Settings {
|
||||
/// The mobile data policy
|
||||
@objc enum MobileDataPolicy: Int, Codable, CaseIterable, Identifiable {
|
||||
case always = 1
|
||||
case ask = 2
|
||||
case never = 0
|
||||
|
||||
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
var id: Self { self }
|
||||
|
||||
|
||||
/// The description text
|
||||
var description: String {
|
||||
switch self {
|
||||
case .always:
|
||||
return String(localized: "mobile_data_option_always")
|
||||
case .ask:
|
||||
return String(localized: "mobile_data_option_ask")
|
||||
case .never:
|
||||
return String(localized: "mobile_data_option_never")
|
||||
default:
|
||||
return String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
iphone/Maps/Model/Settings Types/PowerSavingMode.swift
Normal file
30
iphone/Maps/Model/Settings Types/PowerSavingMode.swift
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
extension Settings {
|
||||
/// The power saving mode
|
||||
@objc enum PowerSavingMode: Int, Codable, CaseIterable, Identifiable {
|
||||
case auto = 1
|
||||
case maximum = 2
|
||||
case never = 0
|
||||
|
||||
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
var id: Self { self }
|
||||
|
||||
|
||||
/// The description text
|
||||
var description: String {
|
||||
switch self {
|
||||
case .auto:
|
||||
return String(localized: "power_managment_setting_auto")
|
||||
case .maximum:
|
||||
return String(localized: "power_managment_setting_manual_max")
|
||||
case .never:
|
||||
return String(localized: "power_managment_setting_never")
|
||||
default:
|
||||
return String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
iphone/Maps/Model/Settings Types/VoiceRoutingLanguage.swift
Normal file
13
iphone/Maps/Model/Settings Types/VoiceRoutingLanguage.swift
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
extension Settings {
|
||||
/// A language used for voice guidance during routing
|
||||
struct VoiceRoutingLanguage: Language {
|
||||
// MARK: Properties
|
||||
|
||||
/// The id
|
||||
let id: String
|
||||
|
||||
|
||||
/// The localized name
|
||||
let localizedName: String
|
||||
}
|
||||
}
|
||||
485
iphone/Maps/Model/Settings.swift
Normal file
485
iphone/Maps/Model/Settings.swift
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
import Combine
|
||||
import AVFoundation
|
||||
|
||||
/// The settings
|
||||
@objc class Settings: NSObject {
|
||||
// MARK: Properties
|
||||
|
||||
// The notification name for changed routing options
|
||||
static let routingOptionsChangedNotificationName: Notification.Name = Notification.Name(rawValue: "RoutingOptionsChanged")
|
||||
|
||||
|
||||
/// Key for storing if the sync beta alert has been shown in the user defaults
|
||||
static private let userDefaultsKeyHasShownSyncBetaAlert = "kUDDidShowICloudSynchronizationEnablingAlert"
|
||||
|
||||
|
||||
/// Key for storing the type of action used for the bottom left main interface button in the user defaults
|
||||
static private let userDefaultsKeyLeftButtonType = "LeftButtonType"
|
||||
|
||||
|
||||
/// Key for storing the map appearance in the user defaults
|
||||
static private let userDefaultsKeyMapAppearance = "MapAppearance"
|
||||
|
||||
|
||||
/// The current distance unit
|
||||
static var distanceUnit: DistanceUnit {
|
||||
get {
|
||||
if SettingsBridge.measurementUnits() == .imperial {
|
||||
return .imperial
|
||||
} else {
|
||||
return .metric
|
||||
}
|
||||
}
|
||||
set {
|
||||
if newValue == .imperial {
|
||||
SettingsBridge.setMeasurementUnits(.imperial)
|
||||
} else {
|
||||
SettingsBridge.setMeasurementUnits(.metric)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If zoom buttons should be displayed
|
||||
@objc static var hasZoomButtons: Bool {
|
||||
get {
|
||||
return SettingsBridge.zoomButtonsEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setZoomButtonsEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The type of action used for the bottom left main interface button
|
||||
static var leftButtonType: LeftButtonType {
|
||||
get {
|
||||
if let leftButtonTypeRawValue = UserDefaults.standard.string(forKey: userDefaultsKeyLeftButtonType), let leftButtonType = LeftButtonType(rawValue: leftButtonTypeRawValue) {
|
||||
return leftButtonType
|
||||
}
|
||||
|
||||
return .help
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: userDefaultsKeyLeftButtonType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If 3D buildings should be displayed
|
||||
@objc static var has3dBuildings: Bool {
|
||||
get {
|
||||
return SettingsBridge.buildings3dViewEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setBuildings3dViewEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If automatic map downloads should be enabled
|
||||
@objc static var hasAutomaticDownload: Bool {
|
||||
get {
|
||||
return SettingsBridge.autoDownloadEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setAutoDownloadEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The current mobile data policy
|
||||
@objc static var mobileDataPolicy: MobileDataPolicy {
|
||||
get {
|
||||
let networkPolicyPermission = NetworkPolicy.shared().permission
|
||||
if networkPolicyPermission == .always {
|
||||
return .always
|
||||
} else if networkPolicyPermission == .never {
|
||||
return .never
|
||||
} else {
|
||||
return .ask
|
||||
}
|
||||
}
|
||||
set {
|
||||
if newValue == .always {
|
||||
NetworkPolicy.shared().permission = .always
|
||||
} else if newValue == .never {
|
||||
NetworkPolicy.shared().permission = .never
|
||||
} else {
|
||||
NetworkPolicy.shared().permission = .ask
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The current power saving mode
|
||||
@objc static var powerSavingMode: PowerSavingMode {
|
||||
get {
|
||||
return PowerSavingMode(rawValue: SettingsBridge.powerManagement()) ?? .never
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setPowerManagement(newValue.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If an increased font size should be used for map labels
|
||||
@objc static var hasIncreasedFontsize: Bool {
|
||||
get {
|
||||
return SettingsBridge.largeFontSize()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setLargeFontSize(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If names should be transliterated to Latin
|
||||
@objc static var shouldTransliterateToLatin: Bool {
|
||||
get {
|
||||
return SettingsBridge.transliteration()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setTransliteration(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The available languages for the map
|
||||
static var availableLanguagesForMap: [MapLanguage] {
|
||||
var languages = SettingsBridge.availableMapLanguages().map { language in
|
||||
return MapLanguage(id: language.key, localizedName: language.value)
|
||||
}.sorted()
|
||||
languages.insert(MapLanguage(id: "default", localizedName: String(localized: "pref_maplanguage_local")), at: 0)
|
||||
languages.insert(MapLanguage(id: "auto", localizedName: String(localized: "auto")), at: 0)
|
||||
return languages
|
||||
}
|
||||
|
||||
|
||||
/// The current language for the map
|
||||
static var languageForMap: MapLanguage.ID {
|
||||
get {
|
||||
return SettingsBridge.mapLanguageCode()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setMapLanguageCode(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If the compass should be calibrated
|
||||
@objc static var shouldCalibrateCompass: Bool {
|
||||
get {
|
||||
return SettingsBridge.compassCalibrationEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setCompassCalibrationEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The current map appearance
|
||||
@objc static var mapAppearance: Appearance {
|
||||
get {
|
||||
let mapAppearanceRawValue = UserDefaults.standard.integer(forKey: userDefaultsKeyMapAppearance)
|
||||
if mapAppearanceRawValue != 0, let mapAppearance = Appearance(rawValue: mapAppearanceRawValue) {
|
||||
return mapAppearance
|
||||
}
|
||||
|
||||
return .auto
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: userDefaultsKeyMapAppearance)
|
||||
ThemeManager.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The current appearance
|
||||
@objc static var appearance: Appearance {
|
||||
get {
|
||||
let theme = SettingsBridge.theme()
|
||||
if theme == MWMTheme.day {
|
||||
return .light
|
||||
} else if theme == MWMTheme.night {
|
||||
return .dark
|
||||
} else {
|
||||
return .auto
|
||||
}
|
||||
}
|
||||
set {
|
||||
if newValue == .light {
|
||||
SettingsBridge.setTheme(MWMTheme.day)
|
||||
} else if newValue == .dark {
|
||||
SettingsBridge.setTheme(MWMTheme.night)
|
||||
} else {
|
||||
SettingsBridge.setTheme(MWMTheme.auto)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If the bookmarks should be synced via iCloud
|
||||
@objc static var shouldSync: Bool {
|
||||
get {
|
||||
return SettingsBridge.iCLoudSynchronizationEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setICLoudSynchronizationEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If the sync beta alert has been shown
|
||||
@objc static var hasShownSyncBetaAlert: Bool {
|
||||
get {
|
||||
return UserDefaults.standard.bool(forKey: userDefaultsKeyHasShownSyncBetaAlert)
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: userDefaultsKeyHasShownSyncBetaAlert)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The publisher for state changes of the iCloud sync
|
||||
static var syncStatePublisher: AnyPublisher<SynchronizationManagerState, Never> {
|
||||
iCloudSynchronizaionManager.shared.notifyObservers()
|
||||
return iCloudSynchronizaionManager.shared.statePublisher
|
||||
}
|
||||
|
||||
|
||||
/// If our custom logging is enabled
|
||||
@objc static var isLogging: Bool {
|
||||
get {
|
||||
return SettingsBridge.isFileLoggingEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setFileLoggingEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The formatter for the size of the log file
|
||||
@objc static var logSizeFormatter: MeasurementFormatter {
|
||||
let measurementFormatter = MeasurementFormatter()
|
||||
measurementFormatter.unitStyle = .medium
|
||||
measurementFormatter.unitOptions = .naturalScale
|
||||
return measurementFormatter
|
||||
}
|
||||
|
||||
|
||||
/// The size of the log file in bytes
|
||||
@objc static var logSize: Measurement<UnitInformationStorage> {
|
||||
return Measurement<UnitInformationStorage>(value: Double(SettingsBridge.logFileSize()), unit: .bytes)
|
||||
}
|
||||
|
||||
|
||||
/// If the perspective view should be used during routing
|
||||
@objc static var hasPerspectiveViewWhileRouting: Bool {
|
||||
get {
|
||||
return SettingsBridge.perspectiveViewEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setPerspectiveViewEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If auto zoom should be used during routing
|
||||
@objc static var hasAutoZoomWhileRouting: Bool {
|
||||
get {
|
||||
return SettingsBridge.autoZoomEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setAutoZoomEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If voice guidance should be provided during routing
|
||||
@objc static var shouldProvideVoiceRouting: Bool {
|
||||
get {
|
||||
return MWMTextToSpeech.isTTSEnabled()
|
||||
}
|
||||
set {
|
||||
MWMTextToSpeech.setTTSEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The available languages for voice guidance during routing
|
||||
static var availableLanguagesForVoiceRouting: [VoiceRoutingLanguage] {
|
||||
return MWMTextToSpeech.availableLanguages().map { language in
|
||||
return VoiceRoutingLanguage(id: language.key, localizedName: language.value)
|
||||
}.sorted()
|
||||
}
|
||||
|
||||
|
||||
/// The current language for voice guidance during routing
|
||||
@objc static var languageForVoiceRouting: VoiceRoutingLanguage.ID {
|
||||
get {
|
||||
return MWMTextToSpeech.selectedLanguage()
|
||||
}
|
||||
set {
|
||||
MWMTextToSpeech.tts().setNotificationsLocale(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The voice used for voice guidance during routing
|
||||
@objc static var voiceForVoiceRouting: String? {
|
||||
if let voice = MWMTextToSpeech.tts().voice() {
|
||||
return voice.name
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
/// If street names should be announced in the voice guidance during routing
|
||||
@objc static var shouldAnnounceStreetnamesWhileVoiceRouting: Bool {
|
||||
get {
|
||||
return MWMTextToSpeech.isStreetNamesTTSEnabled()
|
||||
}
|
||||
set {
|
||||
MWMTextToSpeech.setStreetNamesTTSEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The current announcement of speed traps in the voice guidance during routing
|
||||
@objc static var announcingSpeedTrapsWhileVoiceRouting: AnnouncingSpeedTrapsWhileVoiceRouting {
|
||||
get {
|
||||
return AnnouncingSpeedTrapsWhileVoiceRouting(rawValue: MWMTextToSpeech.speedCameraMode()) ?? .never
|
||||
}
|
||||
set {
|
||||
MWMTextToSpeech.setSpeedCameraMode(newValue.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If toll roads should be avoided during routing
|
||||
@objc static var shouldAvoidTollRoadsWhileRouting: Bool {
|
||||
get {
|
||||
return RoutingOptions().avoidToll
|
||||
}
|
||||
set {
|
||||
let routingOptions = RoutingOptions()
|
||||
routingOptions.avoidToll = newValue
|
||||
routingOptions.save()
|
||||
|
||||
NotificationCenter.default.post(name: routingOptionsChangedNotificationName, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If unpaved roads should be avoided during routing
|
||||
@objc static var shouldAvoidUnpavedRoadsWhileRouting: Bool {
|
||||
get {
|
||||
return RoutingOptions().avoidDirty
|
||||
}
|
||||
set {
|
||||
let routingOptions = RoutingOptions()
|
||||
routingOptions.avoidDirty = newValue
|
||||
routingOptions.save()
|
||||
|
||||
NotificationCenter.default.post(name: routingOptionsChangedNotificationName, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If paved roads should be avoided during routing
|
||||
@objc static var shouldAvoidPavedRoadsWhileRouting: Bool {
|
||||
get {
|
||||
return RoutingOptions().avoidPaved
|
||||
}
|
||||
set {
|
||||
let routingOptions = RoutingOptions()
|
||||
routingOptions.avoidPaved = newValue
|
||||
routingOptions.save()
|
||||
|
||||
NotificationCenter.default.post(name: routingOptionsChangedNotificationName, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If ferries should be avoided during routing
|
||||
@objc static var shouldAvoidFerriesWhileRouting: Bool {
|
||||
get {
|
||||
return RoutingOptions().avoidFerry
|
||||
}
|
||||
set {
|
||||
let routingOptions = RoutingOptions()
|
||||
routingOptions.avoidFerry = newValue
|
||||
routingOptions.save()
|
||||
|
||||
NotificationCenter.default.post(name: routingOptionsChangedNotificationName, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If motorways should be avoided during routing
|
||||
@objc static var shouldAvoidMotorwaysWhileRouting: Bool {
|
||||
get {
|
||||
return RoutingOptions().avoidMotorway
|
||||
}
|
||||
set {
|
||||
let routingOptions = RoutingOptions()
|
||||
routingOptions.avoidMotorway = newValue
|
||||
routingOptions.save()
|
||||
|
||||
NotificationCenter.default.post(name: routingOptionsChangedNotificationName, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If steps should be avoided during routing
|
||||
@objc static var shouldAvoidStepsWhileRouting: Bool {
|
||||
get {
|
||||
return RoutingOptions().avoidSteps
|
||||
}
|
||||
set {
|
||||
let routingOptions = RoutingOptions()
|
||||
routingOptions.avoidSteps = newValue
|
||||
routingOptions.save()
|
||||
|
||||
NotificationCenter.default.post(name: routingOptionsChangedNotificationName, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Create a bookmarks backup before enabling the sync beta
|
||||
/// - Parameter completionHandler: A compeltion handler, which returns if a backup has been created
|
||||
static func createBookmarksBackupBecauseOfSyncBeta(completionHandler: ((_ hasCreatedBackup: Bool) -> Void)?) {
|
||||
BookmarksManager.shared().shareAllCategories { status, url in
|
||||
switch status {
|
||||
case .success:
|
||||
let window = (UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive }.first(where: { $0 is UIWindowScene }) as? UIWindowScene)?.keyWindow
|
||||
if let viewController = window?.rootViewController?.presentedViewController {
|
||||
let shareController = ActivityViewController.share(for: url, message: String(localized: "share_bookmarks_email_body")) { _, _, _, _ in
|
||||
completionHandler?(true)
|
||||
}
|
||||
shareController.present(inParentViewController: viewController, anchorView: nil)
|
||||
}
|
||||
case .emptyCategory:
|
||||
Toast.show(withText: String(localized: "bookmarks_error_title_share_empty"))
|
||||
completionHandler?(false)
|
||||
case .archiveError:
|
||||
Toast.show(withText: String(localized: "dialog_routing_system_error"))
|
||||
completionHandler?(false)
|
||||
case .fileError:
|
||||
Toast.show(withText: String(localized: "dialog_routing_system_error"))
|
||||
completionHandler?(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Play a test audio snippet for voice guidance during routing
|
||||
@objc static func playVoiceRoutingTest() {
|
||||
MWMTextToSpeech.playTest()
|
||||
}
|
||||
}
|
||||
85
iphone/Maps/Model/Social Media.swift
Normal file
85
iphone/Maps/Model/Social Media.swift
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import SwiftUI
|
||||
|
||||
enum SocialMedia: CaseIterable, Identifiable {
|
||||
case codeberg
|
||||
case mastodon
|
||||
case matrix
|
||||
case lemmy
|
||||
case bluesky
|
||||
case pixelfed
|
||||
case email
|
||||
|
||||
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The e-mail address
|
||||
static let emailAddress: String = "ios@comaps.app"
|
||||
|
||||
|
||||
//// The id
|
||||
var id: Self { self }
|
||||
|
||||
|
||||
/// The description text
|
||||
var description: String {
|
||||
switch self {
|
||||
case .codeberg:
|
||||
return String(localized: "social_codeberg")
|
||||
case .mastodon:
|
||||
return String(localized: "social_mastodon")
|
||||
case .matrix:
|
||||
return String(localized: "social_matrix")
|
||||
case .lemmy:
|
||||
return String(localized: "social_lemmy")
|
||||
case .bluesky:
|
||||
return String(localized: "social_bluesky")
|
||||
case .pixelfed:
|
||||
return String(localized: "social_pixelfed")
|
||||
case .email:
|
||||
return String(localized: "social_email")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The url
|
||||
var url: URL {
|
||||
switch self {
|
||||
case .codeberg:
|
||||
return URL(string: "https://codeberg.org/comaps/")!
|
||||
case .mastodon:
|
||||
return URL(string: "https://floss.social/@CoMaps")!
|
||||
case .matrix:
|
||||
return URL(string: "https://matrix.to/#/#comaps:matrix.org")!
|
||||
case .lemmy:
|
||||
return URL(string: "https://sopuli.xyz/c/CoMaps")!
|
||||
case .bluesky:
|
||||
return URL(string: "https://bsky.app/profile/comaps.app")!
|
||||
case .pixelfed:
|
||||
return URL(string: "https://pixelfed.social/CoMaps")!
|
||||
case .email:
|
||||
return URL(string: "mailto:\(SocialMedia.emailAddress)")!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The image text
|
||||
var image: Image {
|
||||
switch self {
|
||||
case .codeberg:
|
||||
return Image(.SocialMedia.codeberg)
|
||||
case .mastodon:
|
||||
return Image(.SocialMedia.mastodon)
|
||||
case .matrix:
|
||||
return Image(.SocialMedia.matrix)
|
||||
case .lemmy:
|
||||
return Image(.SocialMedia.lemmy)
|
||||
case .bluesky:
|
||||
return Image(.SocialMedia.bluesky)
|
||||
case .pixelfed:
|
||||
return Image(.SocialMedia.pixelfed)
|
||||
case .email:
|
||||
return Image(systemName: "envelope.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue