From 302f244d51423f8d2daeaf2dfd9dbbc627de9262 Mon Sep 17 00:00:00 2001 From: Craig Reyenga <craig.reyenga@gmail.com> Date: Thu, 13 Mar 2025 11:26:08 -0400 Subject: [PATCH] Create and test PreferenceSetting. --- .../Testing/Unit/PreferenceSettingTests.swift | 75 +++ .../Unit/PreferenceSettingTestsSuccess.plist | 97 ++++ Sources/Helpers/PreferenceSetting.swift | 459 ++++++++++++++++++ VLC.xcodeproj/project.pbxproj | 20 +- 4 files changed, 648 insertions(+), 3 deletions(-) create mode 100644 Buildsystem/Testing/Unit/PreferenceSettingTests.swift create mode 100644 Buildsystem/Testing/Unit/PreferenceSettingTestsSuccess.plist create mode 100644 Sources/Helpers/PreferenceSetting.swift diff --git a/Buildsystem/Testing/Unit/PreferenceSettingTests.swift b/Buildsystem/Testing/Unit/PreferenceSettingTests.swift new file mode 100644 index 000000000..8d4c20859 --- /dev/null +++ b/Buildsystem/Testing/Unit/PreferenceSettingTests.swift @@ -0,0 +1,75 @@ +/***************************************************************************** + * PreferenceSettingTests.swift + * VLC for iOS + ***************************************************************************** + * Copyright (c) 2025 VideoLAN. All rights reserved. + * + * Authors: Craig Reyenga <craig.reyenga # gmail.com> + * + * Refer to the COPYING file of the official project for license. + *****************************************************************************/ + +import XCTest +@testable import VLC + +final class PreferenceSettingTests: XCTestCase { + + func testDecodeAll() { + let fileURL = Bundle(for: type(of: self)).url(forResource: "PreferenceSettingTestsSuccess", + withExtension: "plist")! + let data = try! Data(contentsOf: fileURL) + + let decoder = PropertyListDecoder() + let decoded = try! decoder.decode(PreferenceSettingRoot.self, from: data) + + // Sample data is intentionally meant to have nothing to do with VLC. + // This way, global string searches won't bring up this test as a result unnecessarily. + + let group = PreferenceSetting.Group(title: "First Section", footerText: "First") + + let titleChoices = PreferenceSetting.Choices + .number([.init(title: "Zero", value: .from(integer: 0)), + .init(title: "One", value: .from(integer: 1)), + .init(title: "Two", value: .from(integer: 2)), + .init(title: "Three", value: .from(integer: 3))], + defaultValue: .zero) + let title = PreferenceSetting.Title(key: "numberOfParkingTickets", + title: "Number of Parking Tickets", + choices: titleChoices) + + let multiChoices = PreferenceSetting.Choices + .string([.init(title: "Zero", value: "Zero"), + .init(title: "One", value: "One"), + .init(title: "Two", value: "Two"), + .init(title: "Three", value: "Three")], + defaultValue: "Zero") + let multi = PreferenceSetting.MultiValue(key: "numberOfSpeedingTickets", + title: "Number of Speeding Tickets", + choices: multiChoices) + + let textField = PreferenceSetting.TextField(key: "name", + title: "Name", + defaultValue: "") + + let toggle = PreferenceSetting.Toggle(key: "WashHandsBeforeEating", + title: "Wash Hands Before Eating", + defaultValue: true) + + let custom = PreferenceSetting.Custom.helloWorld(.init(title: "Greetings Earth", + population: 8_200_000_000)) + + let expected: [PreferenceSetting] = [ + .groupSpecifier(group), + .titleSpecifier(title), + .multiValueSpecifier(multi), + .textFieldSpecifier(textField), + .toggleSwitchSpecifier(toggle), + .custom(custom) + ] + + XCTAssertTrue(decoded.specifiers == expected) + } + + // TODO: create tests for failure cases. + +} diff --git a/Buildsystem/Testing/Unit/PreferenceSettingTestsSuccess.plist b/Buildsystem/Testing/Unit/PreferenceSettingTestsSuccess.plist new file mode 100644 index 000000000..598563423 --- /dev/null +++ b/Buildsystem/Testing/Unit/PreferenceSettingTestsSuccess.plist @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>PreferenceSpecifiers</key> + <array> + <dict> + <key>Type</key> + <string>PSGroupSpecifier</string> + <key>Title</key> + <string>First Section</string> + <key>FooterText</key> + <string>First</string> + </dict> + <dict> + <key>Type</key> + <string>PSTitleValueSpecifier</string> + <key>DefaultValue</key> + <integer>0</integer> + <key>Key</key> + <string>numberOfParkingTickets</string> + <key>Title</key> + <string>Number of Parking Tickets</string> + <key>Titles</key> + <array> + <string>Zero</string> + <string>One</string> + <string>Two</string> + <string>Three</string> + </array> + <key>Values</key> + <array> + <integer>0</integer> + <integer>1</integer> + <integer>2</integer> + <integer>3</integer> + </array> + </dict> + <dict> + <key>Type</key> + <string>PSMultiValueSpecifier</string> + <key>DefaultValue</key> + <string>Zero</string> + <key>Key</key> + <string>numberOfSpeedingTickets</string> + <key>Title</key> + <string>Number of Speeding Tickets</string> + <key>Titles</key> + <array> + <string>Zero</string> + <string>One</string> + <string>Two</string> + <string>Three</string> + </array> + <key>Values</key> + <array> + <string>Zero</string> + <string>One</string> + <string>Two</string> + <string>Three</string> + </array> + </dict> + <dict> + <key>Type</key> + <string>PSTextFieldSpecifier</string> + <key>Title</key> + <string>Name</string> + <key>FooterText</key> + <string>Enter your name</string> + <key>Key</key> + <string>name</string> + </dict> + <dict> + <key>Type</key> + <string>PSToggleSwitchSpecifier</string> + <key>DefaultValue</key> + <true/> + <key>Key</key> + <string>WashHandsBeforeEating</string> + <key>Title</key> + <string>Wash Hands Before Eating</string> + </dict> + <dict> + <key>Type</key> + <string>VLCCustomSpecifier</string> + <key>Subtype</key> + <string>HelloWorld</string> + <key>Title</key> + <string>Greetings Earth</string> + <key>Population</key> + <integer>8200000000</integer> + </dict> + </array> + <key>StringsTable</key> + <string>Root</string> +</dict> +</plist> diff --git a/Sources/Helpers/PreferenceSetting.swift b/Sources/Helpers/PreferenceSetting.swift new file mode 100644 index 000000000..4aaae2e2e --- /dev/null +++ b/Sources/Helpers/PreferenceSetting.swift @@ -0,0 +1,459 @@ +/***************************************************************************** + * PreferenceSetting.swift + * VLC for iOS + ***************************************************************************** + * Copyright (c) 2025 VideoLAN. All rights reserved. + * + * Authors: Craig Reyenga <craig.reyenga # gmail.com> + * + * Refer to the COPYING file of the official project for license. + *****************************************************************************/ + +/// A preference from Settings.app. +/// +/// These structures are decodable from plists. Apple's schema is followed +/// closely, however, the decoder does not support everything in full. +/// +/// https://developer.apple.com/library/archive/documentation/PreferenceSettings/Conceptual/SettingsApplicationSchemaReference/ +enum PreferenceSetting: Equatable { + // Apple: + case groupSpecifier(Group) + case titleSpecifier(Title) + case multiValueSpecifier(MultiValue) + case textFieldSpecifier(TextField) + case toggleSwitchSpecifier(Toggle) + // not implemented yet, because we don't use them: + // case sliderSpecifier(Slider) + // case radioGroupSpecifier(RadioGroup) + // case childPaneSpecifier(ChildPane) + + // Us: + case custom(Custom) + + // Nobody: + case unsupported(String) +} + +// - MARK: PreferenceSettingRoot + +/// Container for preference specifiers +struct PreferenceSettingRoot { + let specifiers: [PreferenceSetting] +} + +// - MARK: Types of Preferences + +extension PreferenceSetting { + struct Group: Equatable { + let title: String + let footerText: String? + } +} + +extension PreferenceSetting { + struct Title: Equatable { + let key: String + let title: String + let choices: Choices + } +} + +extension PreferenceSetting { + struct MultiValue: Equatable { + let key: String + let title: String + let choices: Choices + } +} + +extension PreferenceSetting { + struct TextField: Equatable { + let key: String + let title: String + let defaultValue: String + } +} + +extension PreferenceSetting { + struct Toggle: Equatable { + let key: String + let title: String + let defaultValue: Bool + } +} + +// - MARK: Custom Preferences + +extension PreferenceSetting { + enum Custom: Equatable { + case helloWorld(HelloWorld) + } +} + +extension PreferenceSetting.Custom { + /// An example of a custom preference. Don't use it. + struct HelloWorld: Equatable { + let title: String + let population: Int + } +} + +// - MARK: Number + +extension PreferenceSetting { + /// PropertyListDecoder has a bug where values that are explicitly declared + /// as being integer or float are actually decodable as either. There is no + /// way to distinguish them during decoding. To get around this, we use a + /// structure that provides both types of values and place the burden of + /// choice between the two on the consumers of this data. + struct Number: Equatable { + let float: Float + let integer: Int + + static var zero: Number { + .init(float: 0, integer: 0) + } + + static func from(integer: Int) -> Self { + .init(float: Float(integer), integer: integer) + } + } +} + +// - MARK: Choices + +extension PreferenceSetting { + /// Choices are pairings of titles and values; values can be boolean, numeric, or string. + enum Choices: Equatable { + case bool([BoolChoice], defaultValue: Bool) + case number([NumberChoice], defaultValue: Number) + case string([StringChoice], defaultValue: String) + } + + struct BoolChoice: Equatable { + let title: String + let value: Bool + } + + struct NumberChoice: Equatable { + let title: String + let value: Number + } + + struct StringChoice: Equatable { + let title: String + let value: String + } +} + +// - MARK: Decodable + +extension PreferenceSettingRoot: Decodable { + enum CodingKeys: String, CodingKey { + case specifiers = "PreferenceSpecifiers" + } +} + +extension PreferenceSetting: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let typeString = try container.decode(String.self, forKey: .type) + + switch typeString { + case SettingType.psGroupSpecifier.rawValue: + let pref = try Group(from: decoder) + self = .groupSpecifier(pref) + + case SettingType.psTitleValueSpecifier.rawValue: + let pref = try Title(from: decoder) + self = .titleSpecifier(pref) + + case SettingType.psMultiValueSpecifier.rawValue: + let pref = try MultiValue(from: decoder) + self = .multiValueSpecifier(pref) + + case SettingType.psTextFieldSpecifier.rawValue: + let pref = try TextField(from: decoder) + self = .textFieldSpecifier(pref) + + case SettingType.psToggleSwitchSpecifier.rawValue: + let pref = try Toggle(from: decoder) + self = .toggleSwitchSpecifier(pref) + + case SettingType.vlcCustomSpecifier.rawValue: + let pref = try Custom(from: decoder) + self = .custom(pref) + + case SettingType.psSliderSpecifier.rawValue, + SettingType.psRadioGroupSpecifier.rawValue, + SettingType.psChildPaneSpecifier.rawValue: + fallthrough + + default: + self = .unsupported(typeString) + + } + } + + enum CodingKeys: String, CodingKey { + case type = "Type" + } +} + +extension PreferenceSetting.Group: Decodable { + enum CodingKeys: String, CodingKey { + case title = "Title" + case footerText = "FooterText" + } +} + +extension PreferenceSetting.Title: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.key = try container.decode(String.self, forKey: .key) + self.title = try container.decode(String.self, forKey: .title) + self.choices = try .init(from: decoder) + } + + enum CodingKeys: String, CodingKey { + case key = "Key" + case title = "Title" + } +} + +extension PreferenceSetting.MultiValue: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.key = try container.decode(String.self, forKey: .key) + self.title = try container.decode(String.self, forKey: .title) + self.choices = try .init(from: decoder) + } + + enum CodingKeys: String, CodingKey { + case key = "Key" + case title = "Title" + } +} + +extension PreferenceSetting.TextField: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.key = try container.decode(String.self, forKey: .key) + self.title = try container.decode(String.self, forKey: .title) + self.defaultValue = try container.decodeIfPresent(String.self, forKey: .defaultValue) ?? "" + } + + enum CodingKeys: String, CodingKey { + case key = "Key" + case title = "Title" + case defaultValue = "DefaultValue" + } +} + +extension PreferenceSetting.Toggle: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.key = try container.decode(String.self, forKey: .key) + self.title = try container.decode(String.self, forKey: .title) + self.defaultValue = try container.decode(Bool.self, forKey: .defaultValue) + } + + enum CodingKeys: String, CodingKey { + case key = "Key" + case title = "Title" + case defaultValue = "DefaultValue" + } +} + +extension PreferenceSetting.Custom: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let subtype = try container.decode(String.self, forKey: .subtype) + + switch subtype { + case PreferenceSetting.CustomSettingSubType.helloWorld.rawValue: + self = .helloWorld(try HelloWorld(from: decoder)) + + default: + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Unsupported custom setting subtype: \(subtype)")) + } + } + + enum CodingKeys: String, CodingKey { + case subtype = "Subtype" + } +} + +extension PreferenceSetting.Custom.HelloWorld: Decodable { + enum CodingKeys: String, CodingKey { + case title = "Title" + case population = "Population" + } +} + +extension PreferenceSetting.Choices { + /// A value type used only for the purposes of decoding. + fileprivate enum IntermediateValue: Decodable { + case bool(Bool) + case string(String) + case number(PreferenceSetting.Number) + + var boolValue: Bool? { + switch self { + case let .bool(value): + return value + default: + return nil + } + } + + var stringValue: String? { + switch self { + case let .string(value): + return value + default: + return nil + } + } + + var numberValue: PreferenceSetting.Number? { + switch self { + case let .number(value): + return value + default: + return nil + } + } + + var floatValue: Float? { + switch self { + case let .number(value): + return value.float + default: + return nil + } + } + + var integerValue: Int? { + switch self { + case let .number(value): + return value.integer + default: + return nil + } + } + + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let bool = try? container.decode(Bool.self) { + self = .bool(bool) + return + } + + if let str = try? container.decode(String.self) { + self = .string(str) + return + } + + let float = try container.decode(Float.self) + let integer = try container.decode(Int.self) + + self = .number(PreferenceSetting.Number(float: float, integer: integer)) + } + } + + enum CodingKeys: String, CodingKey { + case titles = "Titles" + case values = "Values" + case defaultValue = "DefaultValue" + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let titles = try container.decode([String].self, forKey: .titles) + let values = try container.decode([IntermediateValue].self, forKey: .values) + let defaultValue = try container.decode(IntermediateValue.self, forKey: .defaultValue) + + guard !titles.isEmpty else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "empty titles array")) + } + + guard titles.count == values.count else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "mismatch between titles and values")) + } + + switch defaultValue { + case let .bool(boolValue): + let vals = values.compactMap(\.boolValue) + let choices = zip(titles, vals).map { t, v in + PreferenceSetting.BoolChoice(title: t, value: v) + } + + guard vals.count == values.count else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "values did not all decode to the same type (bool)")) + } + + guard vals.contains(boolValue) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Default value \(boolValue) not found in values")) + } + + self = .bool(choices, defaultValue: boolValue) + + case let .number(numberValue): + let vals = values.compactMap(\.numberValue) + let choices = zip(titles, vals).map { t, v in + PreferenceSetting.NumberChoice(title: t, value: v) + } + + guard vals.count == values.count else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "values did not all decode to the same type (integer)")) + } + + guard vals.contains(numberValue) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Default value \(numberValue) not found in values")) + } + + self = .number(choices, defaultValue: numberValue) + + case let .string(stringValue): + let vals = values.compactMap(\.stringValue) + let choices = zip(titles, vals).map { t, v in + PreferenceSetting.StringChoice(title: t, value: v) + } + + guard vals.count == values.count else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "values did not all decode to the same type (string)")) + } + + guard vals.contains(stringValue) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Default value \(stringValue) not found in values")) + } + + self = .string(choices, defaultValue: stringValue) + + } + } +} + +// - MARK: SettingType + +fileprivate extension PreferenceSetting { + enum SettingType: String { + case psGroupSpecifier = "PSGroupSpecifier" + case psTitleValueSpecifier = "PSTitleValueSpecifier" + case psMultiValueSpecifier = "PSMultiValueSpecifier" + case psTextFieldSpecifier = "PSTextFieldSpecifier" + case psToggleSwitchSpecifier = "PSToggleSwitchSpecifier" + case psSliderSpecifier = "PSSliderSpecifier" + case psRadioGroupSpecifier = "PSRadioGroupSpecifier" + case psChildPaneSpecifier = "PSChildPaneSpecifier" + case vlcCustomSpecifier = "VLCCustomSpecifier" + } + + enum CustomSettingSubType: String { + case helloWorld = "HelloWorld" + } +} diff --git a/VLC.xcodeproj/project.pbxproj b/VLC.xcodeproj/project.pbxproj index 51e6a0e32..94af9dff1 100644 --- a/VLC.xcodeproj/project.pbxproj +++ b/VLC.xcodeproj/project.pbxproj @@ -95,6 +95,10 @@ 4342C3C327474CA000E52334 /* SortedMediaFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4342C3C227474CA000E52334 /* SortedMediaFiles.swift */; }; 444E5BFA24C6081B0003B69C /* PasscodeLockController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444E5BF924C6081A0003B69C /* PasscodeLockController.swift */; }; 444E5C0024C719480003B69C /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444E5BFF24C719480003B69C /* AboutController.swift */; }; + 448BA8482D83116D008CCDFF /* PreferenceSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 448BA8472D83115D008CCDFF /* PreferenceSetting.swift */; }; + 448BA8492D83116D008CCDFF /* PreferenceSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 448BA8472D83115D008CCDFF /* PreferenceSetting.swift */; }; + 448BA84B2D832D30008CCDFF /* PreferenceSettingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 448BA84A2D832D30008CCDFF /* PreferenceSettingTests.swift */; }; + 448BA84D2D84E1ED008CCDFF /* PreferenceSettingTestsSuccess.plist in Resources */ = {isa = PBXBuildFile; fileRef = 448BA84C2D84E1DE008CCDFF /* PreferenceSettingTestsSuccess.plist */; }; 44B5822024E434FD001A2583 /* MediaGridCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B5821F24E434FD001A2583 /* MediaGridCollectionCell.swift */; }; 44C8BBA324AF2B5C003F8940 /* FeedbackGenerators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44C8BBA224AF2B5C003F8940 /* FeedbackGenerators.swift */; }; 44C8BBAE24AF36F4003F8940 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44C8BBA624AF36F4003F8940 /* SettingsController.swift */; }; @@ -1014,6 +1018,9 @@ 4342C3C227474CA000E52334 /* SortedMediaFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedMediaFiles.swift; sourceTree = "<group>"; }; 444E5BF924C6081A0003B69C /* PasscodeLockController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockController.swift; sourceTree = "<group>"; }; 444E5BFF24C719480003B69C /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = "<group>"; }; + 448BA8472D83115D008CCDFF /* PreferenceSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceSetting.swift; sourceTree = "<group>"; }; + 448BA84A2D832D30008CCDFF /* PreferenceSettingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceSettingTests.swift; sourceTree = "<group>"; }; + 448BA84C2D84E1DE008CCDFF /* PreferenceSettingTestsSuccess.plist */ = {isa = PBXFileReference; explicitFileType = text.plist.xml; path = PreferenceSettingTestsSuccess.plist; sourceTree = "<group>"; }; 44B5821F24E434FD001A2583 /* MediaGridCollectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaGridCollectionCell.swift; sourceTree = "<group>"; }; 44C8BBA224AF2B5C003F8940 /* FeedbackGenerators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackGenerators.swift; sourceTree = "<group>"; }; 44C8BBA624AF36F4003F8940 /* SettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; }; @@ -1867,6 +1874,8 @@ 41533C92211338D500EC3ABA /* Unit Testing */ = { isa = PBXGroup; children = ( + 448BA84C2D84E1DE008CCDFF /* PreferenceSettingTestsSuccess.plist */, + 448BA84A2D832D30008CCDFF /* PreferenceSettingTests.swift */, 41533CA1211343D100EC3ABA /* Info.plist */, 41533C9D2113392F00EC3ABA /* URLHandlerTests.swift */, ); @@ -1901,6 +1910,7 @@ 44C8BBA124AF2B5C003F8940 /* Helpers */ = { isa = PBXGroup; children = ( + 448BA8472D83115D008CCDFF /* PreferenceSetting.swift */, 419A2C651F37A4B70069D224 /* VLCStringsForLocalization.m */, 4152F1611FEF19BD00F1908B /* KeychainCoordinator.swift */, 7D0C209A28C89F5400CCFFEF /* Network */, @@ -3658,6 +3668,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 448BA84D2D84E1ED008CCDFF /* PreferenceSettingTestsSuccess.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4134,6 +4145,7 @@ 1B39FA112C73BBA900F6F960 /* NSURLSession+sharedMPTCPSession.m in Sources */, 1B39FA0B2C73BBA300F6F960 /* NSURLSessionConfiguration+default.m in Sources */, 41533C9E2113392F00EC3ABA /* URLHandlerTests.swift in Sources */, + 448BA84B2D832D30008CCDFF /* PreferenceSettingTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4340,6 +4352,7 @@ 7D50C6202BBD20DF00B9F1A0 /* SettingsController.swift in Sources */, 7D50C6212BBD20DF00B9F1A0 /* VLCNetworkImageView.m in Sources */, 7D50C6222BBD20DF00B9F1A0 /* EqualizerView.swift in Sources */, + 448BA8492D83116D008CCDFF /* PreferenceSetting.swift in Sources */, 7D50C6232BBD20DF00B9F1A0 /* VLCConfettiView.swift in Sources */, 7D50C6252BBD20DF00B9F1A0 /* VLCAppCoordinator.m in Sources */, 7D50C6272BBD20DF00B9F1A0 /* PlayerController.swift in Sources */, @@ -4662,6 +4675,7 @@ 7D3784AE183A9906009EE944 /* VLCDropboxTableViewController.m in Sources */, DD3EFF3B1BDEBCE500B68579 /* VLCNetworkServerBrowserVLCMedia.m in Sources */, 8DB0D71924CED15C00915506 /* VideoPlayerControls.swift in Sources */, + 448BA8482D83116D008CCDFF /* PreferenceSetting.swift in Sources */, 41B93C051A53835300102E8B /* VLCCloudServiceCell.m in Sources */, 1B39FA082C73BBA200F6F960 /* NSURLSessionConfiguration+default.m in Sources */, 8DE1887421089B3A00A091D2 /* MediaLibraryBaseModel.swift in Sources */, @@ -5129,7 +5143,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = Buildsystem/Testing/Unit/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5172,7 +5186,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = Buildsystem/Testing/Unit/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5214,7 +5228,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = Buildsystem/Testing/Unit/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", -- GitLab