From af587895cca0cb638ab6b1000ab027a0ca6f9f49 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Mon, 13 Nov 2023 12:54:03 +0500 Subject: [PATCH 01/12] feat: add config to process config --- .gitignore | 4 + .../Presentation/Base/FieldsView.swift | 4 +- .../Presentation/Login/SignInViewModel.swift | 4 +- .../Registration/SignUpView.swift | 4 +- .../Registration/SignUpViewModel.swift | 4 +- Core/Core.xcodeproj/project.pbxproj | 37 ++- Core/Core/Configuration/CSSInjector.swift | 9 +- .../Combine/Config/AgreementConfig.swift | 36 +++ .../Configuration/Combine/Config/Config.swift | 151 +++++++++++ .../Combine/Config/ConfigTests.swift | 75 ++++++ .../Combine/Config/FirebaseConfig.swift | 105 ++++++++ Core/Core/Configuration/Config.swift | 55 ---- .../{Combine => }/Debounce.swift | 0 .../Core/Data/Repository/AuthRepository.swift | 4 +- Core/Core/Network/API.swift | 4 +- Core/Core/Network/RequestInterceptor.swift | 4 +- Core/Core/View/Base/WebUnitViewModel.swift | 4 +- Course/Course/Data/CourseRepository.swift | 4 +- .../Container/CourseContainerViewModel.swift | 4 +- .../Details/CourseDetailsViewModel.swift | 4 +- .../Dashboard/Data/DashboardRepository.swift | 4 +- .../Discovery/Data/DiscoveryRepository.swift | 4 +- .../Presentation/DiscoveryViewModel.swift | 4 +- .../UpdateViews/UpdateNotificationView.swift | 4 +- .../UpdateViews/UpdateRecommendedView.swift | 4 +- .../UpdateViews/UpdateRequiredView.swift | 4 +- .../Data/Network/DiscussionRepository.swift | 4 +- .../Base/BaseResponsesViewModel.swift | 4 +- .../Responses/ResponsesViewModel.swift | 2 +- .../Comments/Thread/ThreadViewModel.swift | 2 +- .../CreateNewThreadViewModel.swift | 4 +- .../DiscussionTopicsViewModel.swift | 4 +- .../Presentation/Posts/PostsViewModel.swift | 4 +- OpenEdX.xcodeproj/project.pbxproj | 23 +- OpenEdX/AppDelegate.swift | 4 +- OpenEdX/DI/AppAssembly.swift | 10 +- OpenEdX/DI/NetworkAssembly.swift | 4 +- OpenEdX/DI/ScreenAssembly.swift | 38 +-- OpenEdX/Environment.swift | 89 ------- OpenEdX/RouteController.swift | 2 +- OpenEdX/Router.swift | 6 +- OpenEdX/View/MainScreenViewModel.swift | 4 +- Podfile.lock | 2 +- Profile/Profile/Data/ProfileRepository.swift | 4 +- .../Presentation/Profile/ProfileView.swift | 4 +- .../Profile/ProfileViewModel.swift | 4 +- process_config.py | 248 ++++++++++++++++++ process_config.sh | 16 ++ 48 files changed, 780 insertions(+), 242 deletions(-) create mode 100644 Core/Core/Configuration/Combine/Config/AgreementConfig.swift create mode 100644 Core/Core/Configuration/Combine/Config/Config.swift create mode 100644 Core/Core/Configuration/Combine/Config/ConfigTests.swift create mode 100644 Core/Core/Configuration/Combine/Config/FirebaseConfig.swift delete mode 100644 Core/Core/Configuration/Config.swift rename Core/Core/Configuration/{Combine => }/Debounce.swift (100%) delete mode 100644 OpenEdX/Environment.swift create mode 100644 process_config.py create mode 100755 process_config.sh diff --git a/.gitignore b/.gitignore index 582c86cb2..1239ac3d8 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,7 @@ xcode-frameworks vendor/ .bundle/ + +config.yaml +venv/ +Podfile.lock diff --git a/Authorization/Authorization/Presentation/Base/FieldsView.swift b/Authorization/Authorization/Presentation/Base/FieldsView.swift index 1a71a2483..98cfafaaf 100644 --- a/Authorization/Authorization/Presentation/Base/FieldsView.swift +++ b/Authorization/Authorization/Presentation/Base/FieldsView.swift @@ -12,7 +12,7 @@ struct FieldsView: View { let fields: [FieldConfiguration] let router: BaseRouter - let configuration: Config + let config: ConfigProtocol let cssInjector: CSSInjector let proxy: GeometryProxy @Environment(\.colorScheme) var colorScheme @@ -107,7 +107,7 @@ struct FieldsView_Previews: PreviewProvider { FieldsView( fields: fields, router: AuthorizationRouterMock(), - configuration: ConfigMock(), + config: ConfigMock(), cssInjector: CSSInjectorMock(), proxy: proxy ) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 86b3f32ef..350d09af0 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -31,7 +31,7 @@ public class SignInViewModel: ObservableObject { } let router: AuthorizationRouter - private let config: Config + private let config: ConfigProtocol private let interactor: AuthInteractorProtocol private let analytics: AuthorizationAnalytics private let validator: Validator @@ -39,7 +39,7 @@ public class SignInViewModel: ObservableObject { public init( interactor: AuthInteractorProtocol, router: AuthorizationRouter, - config: Config, + config: ConfigProtocol, analytics: AuthorizationAnalytics, validator: Validator ) { diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index d4dc67acf..bc4bd7dee 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -72,7 +72,7 @@ public struct SignUpView: View { FieldsView(fields: requiredFields, router: viewModel.router, - configuration: viewModel.config, + config: viewModel.config, cssInjector: viewModel.cssInjector, proxy: proxy) @@ -80,7 +80,7 @@ public struct SignUpView: View { DisclosureGroup(isExpanded: $disclosureGroupOpen, content: { FieldsView(fields: nonRequiredFields, router: viewModel.router, - configuration: viewModel.config, + config: viewModel.config, cssInjector: viewModel.cssInjector, proxy: proxy).padding(.horizontal, 1) }, label: { diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 3cc1e16fd..ff3691c05 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -25,7 +25,7 @@ public class SignUpViewModel: ObservableObject { @Published var fields: [FieldConfiguration] = [] let router: AuthorizationRouter - let config: Config + let config: ConfigProtocol let cssInjector: CSSInjector private let interactor: AuthInteractorProtocol @@ -36,7 +36,7 @@ public class SignUpViewModel: ObservableObject { interactor: AuthInteractorProtocol, router: AuthorizationRouter, analytics: AuthorizationAnalytics, - config: Config, + config: ConfigProtocol, cssInjector: CSSInjector, validator: Validator ) { diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index eed739e2e..41edbea78 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -114,6 +114,9 @@ 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; + DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */; }; + DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */; }; + DBF6F2482B01E20A0098414B /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2472B01E20A0098414B /* ConfigTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -243,6 +246,9 @@ 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; + DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConfig.swift; sourceTree = ""; }; + DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementConfig.swift; sourceTree = ""; }; + DBF6F2472B01E20A0098414B /* ConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -367,8 +373,8 @@ isa = PBXGroup; children = ( CFC84955299FAC4D0055E497 /* Combine */, + CFC84951299F8B890055E497 /* Debounce.swift */, 0770DE1828D0847D006D8A5D /* BaseRouter.swift */, - 0727876F28D23411002E9142 /* Config.swift */, 0231CDBD2922422D00032416 /* CSSInjector.swift */, 02280F5A294B4E6F0032823A /* Connectivity.swift */, ); @@ -559,11 +565,22 @@ CFC84955299FAC4D0055E497 /* Combine */ = { isa = PBXGroup; children = ( - CFC84951299F8B890055E497 /* Debounce.swift */, + DBF6F2422B014AF30098414B /* Config */, ); path = Combine; sourceTree = ""; }; + DBF6F2422B014AF30098414B /* Config */ = { + isa = PBXGroup; + children = ( + 0727876F28D23411002E9142 /* Config.swift */, + DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, + DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, + DBF6F2472B01E20A0098414B /* ConfigTests.swift */, + ); + path = Config; + sourceTree = ""; + }; F1620A3A2C8B0699EAA61B57 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -638,6 +655,7 @@ TargetAttributes = { 07169468296D996800E3DED6 = { CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1500; }; 0770DE0728D07831006D8A5D = { CreatedOnToolsVersion = 14.0; @@ -738,6 +756,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBF6F2482B01E20A0098414B /* ConfigTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -747,6 +766,7 @@ files = ( 0727878528D31657002E9142 /* Data_User.swift in Sources */, 02F6EF4A28D9F0A700835477 /* DateExtension.swift in Sources */, + DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */, 027BD3AF2909475000392132 /* DismissKeyboardTapHandler.swift in Sources */, 02E225B0291D29EB0067769A /* UrlExtension.swift in Sources */, 0770DE7928D0C4A9006D8A5D /* RoundedCorners.swift in Sources */, @@ -810,6 +830,7 @@ 0770DE1928D0847D006D8A5D /* BaseRouter.swift in Sources */, 0284DBFE28D48C5300830893 /* CourseItem.swift in Sources */, 0248C92329C075EF00DC8402 /* CourseBlockModel.swift in Sources */, + DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */, 072787B628D37A0E002E9142 /* Validator.swift in Sources */, 0236961D28F9A2D200EEF206 /* Data_AuthResponse.swift in Sources */, 02B2B594295C5C7A00914876 /* Thread.swift in Sources */, @@ -974,6 +995,7 @@ 02DD1C9B29E80CE400F35DCE /* DebugStage */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; @@ -986,6 +1008,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1086,6 +1109,7 @@ 02DD1C9E29E80CED00F35DCE /* ReleaseStage */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; @@ -1106,6 +1130,7 @@ 07169470296D996900E3DED6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; @@ -1118,6 +1143,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1126,6 +1152,7 @@ 07169471296D996900E3DED6 /* DebugProd */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; @@ -1138,6 +1165,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1146,6 +1174,7 @@ 07169472296D996900E3DED6 /* DebugDev */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; @@ -1158,6 +1187,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1166,6 +1196,7 @@ 07169473296D996900E3DED6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; @@ -1186,6 +1217,7 @@ 07169474296D996900E3DED6 /* ReleaseProd */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; @@ -1206,6 +1238,7 @@ 07169475296D996900E3DED6 /* ReleaseDev */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; diff --git a/Core/Core/Configuration/CSSInjector.swift b/Core/Core/Configuration/CSSInjector.swift index b90afc26f..57a2c88b0 100644 --- a/Core/Core/Configuration/CSSInjector.swift +++ b/Core/Core/Configuration/CSSInjector.swift @@ -12,11 +12,8 @@ public class CSSInjector { public let baseURL: URL - public init(baseURL: String) { - guard let url = URL(string: baseURL) else { - fatalError("Ivalid baseURL") - } - self.baseURL = url + public init(config: ConfigProtocol) { + self.baseURL = config.baseURL } public enum CssType { @@ -151,7 +148,7 @@ public class CSSInjector { #if DEBUG public class CSSInjectorMock: CSSInjector { public convenience init() { - self.init(baseURL: "https://google.com/") + self.init(config: ConfigMock()) } } #endif diff --git a/Core/Core/Configuration/Combine/Config/AgreementConfig.swift b/Core/Core/Configuration/Combine/Config/AgreementConfig.swift new file mode 100644 index 000000000..fdf293abe --- /dev/null +++ b/Core/Core/Configuration/Combine/Config/AgreementConfig.swift @@ -0,0 +1,36 @@ +// +// AgreementConfig.swift +// Core +// +// Created by Muhammad Umer on 11/13/23. +// + +import Foundation + +public protocol AgreementConfigProtocol { + var privacyPolicyURL: URL? { get } + var tosURL: URL? { get } +} + +private enum AgreementKeys: String { + case privacyPolicyURL = "PRIVACY_POLICY_URL" + case tosURL = "TOS_URL" +} + +public class AgreementConfig: NSObject, AgreementConfigProtocol { + public var privacyPolicyURL: URL? + public var tosURL: URL? + + init(dictionary: [String: AnyObject]) { + privacyPolicyURL = (dictionary[AgreementKeys.privacyPolicyURL.rawValue] as? String).flatMap(URL.init) + tosURL = (dictionary[AgreementKeys.tosURL.rawValue] as? String).flatMap(URL.init) + super.init() + } +} + +private let key = "AGREEMENT_URLS" +extension Config { + public var agreementConfig: AgreementConfigProtocol { + return AgreementConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift new file mode 100644 index 000000000..1de08025c --- /dev/null +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -0,0 +1,151 @@ +// +// Config.swift +// Core +// +// Created by Muhammad Umer on 11/11/2023. +// + +import Foundation + +public protocol ConfigProtocol { + var baseURL: URL { get } + var oAuthClientId: String { get } + var tokenType: TokenType { get } + var feedbackEmail: String { get } + var whatsNewEnabled: Bool { get } + var appStoreLink: String { get } + var agreementConfig: AgreementConfigProtocol { get } +} + +public enum TokenType: String { + case jwt = "JWT" + case bearer = "BEARER" +} + +private enum ConfigKeys: String { + case baseURL = "API_HOST_URL" + case oAuthClientID = "OAUTH_CLIENT_ID" + case feedbackEmailAddress = "FEEDBACK_EMAIL_ADDRESS" + case tokenType = "TOKEN_TYPE" + case environmentDisplayName = "ENVIRONMENT_DISPLAY_NAME" + case platformName = "PLATFORM_NAME" + case organizationCode = "ORGANIZATION_CODE" + case whatsnewEnabled = "WHATS_NEW_ENABLED" + case appstoreID = "APP_STORE_ID" +} + +public class Config { + public static let shared: Config = { + let config = Config() + config.loadConfigPlist() + return config + }() + + private var properties: [String: Any] = [:] + + init(properties: [String: Any] = [:]) { + self.properties = properties + } + + convenience init() { + self.init(properties: [:]) + loadConfigPlist() + } + + private func loadConfigPlist() { + guard let path = Bundle.main.path(forResource: "config", ofType: "plist"), + let data = try? Data(contentsOf: URL(fileURLWithPath: path)), + let dict = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { return } + + properties = dict + print(properties) + } + + internal subscript(key: String) -> Any? { + return properties[key] + } + + func dict(for key: String) -> [String: Any]? { + return properties[key] as? [String: Any] + } + + func value(for key: String) -> T? { + return properties[key] as? T + } + + func value(for key: String) -> Any? { + return properties[key] + } + + func value(for key: String, dict: [String: Any]) -> String? { + return dict[key] as? String ?? nil + } + + func string(for key: String) -> String? { + return value(for: key) as? String ?? nil + } + + func string(for key: String, dict: [String: Any]) -> String? { + return value(for: key, dict: dict) + } + + func bool(for key: String) -> Bool { + return value(for: key) as? Bool ?? false + } +} + +extension Config: ConfigProtocol { + public var baseURL: URL { + return URL(string: string(for: ConfigKeys.baseURL.rawValue)!)! + } + + public var oAuthClientId: String { + return string(for: ConfigKeys.oAuthClientID.rawValue) ?? "" + } + + public var tokenType: TokenType { + if let tokenTypeValue = string(for: ConfigKeys.tokenType.rawValue), + let tokenType = TokenType(rawValue: tokenTypeValue) { + return tokenType + } else { + return .jwt + } + } + + public var feedbackEmail: String { + return string(for: ConfigKeys.feedbackEmailAddress.rawValue) ?? "" + } + + public var whatsNewEnabled: Bool { + return bool(for: ConfigKeys.whatsnewEnabled.rawValue) + } + + private var appStoreId: String { + return string(for: ConfigKeys.appstoreID.rawValue) ?? "0000000000" + } + + public var appStoreLink: String { + "itms-apps://itunes.apple.com/app/id\(appStoreId)?mt=8" + } +} + +// Mark - For testing and SwiftUI preview +#if DEBUG +public class ConfigMock: Config { + private let properties: [String: Any] = [ + "API_HOST_URL": "https://www.example.com", + "OAUTH_CLIENT_ID": "oauth_client_id", + "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", + "TOKEN_TYPE": TokenType.jwt.rawValue, + "WHATS_NEW_ENABLED": false, + "AGREEMENT_URLS": [ + "PRIVACY_POLICY_URL": "https://www.example.com/privacy", + "TOS_URL": "https://www.example.com/tos" + ] + ] + + public init() { + super.init(properties: properties) + } +} +#endif diff --git a/Core/Core/Configuration/Combine/Config/ConfigTests.swift b/Core/Core/Configuration/Combine/Config/ConfigTests.swift new file mode 100644 index 000000000..1c40d26da --- /dev/null +++ b/Core/Core/Configuration/Combine/Config/ConfigTests.swift @@ -0,0 +1,75 @@ +// +// ConfigTests.swift +// CoreTests +// +// Created by Muhammad Umer on 11/13/23. +// + +import XCTest +@testable import Core + +class ConfigTests: XCTestCase { + + private lazy var properties: [String: Any] = [ + "API_HOST_URL": "https://www.example.com", + "OAUTH_CLIENT_ID": "oauth_client_id", + "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", + "TOKEN_TYPE": "JWT", + "WHATS_NEW_ENABLED": false, + "AGREEMENT_URLS": [ + "PRIVACY_POLICY_URL": "https://www.example.com/privacy", + "TOS_URL": "https://www.example.com/tos" + ], + "FIREBASE": firebaseProperties + ] + + private lazy var firebaseProperties: [String: Any] = [ + "ENABLED": true, + "API_KEY": "testApiKey", + "BUNDLE_ID": "testBundleID", + "CLIENT_ID": "testClientID", + "DATABASE_URL": "https://test.database.url", + "GCM_SENDER_ID": "testGCMSenderID", + "GOOGLE_APP_ID": "testGoogleAppID", + "PROJECT_ID": "testProjectID", + "REVERSED_CLIENT_ID": "testReversedClientID", + "STORAGE_BUCKET": "testStorageBucket", + "ANALYTICS_SOURCE": "firebase", + "CLOUD_MESSAGING_ENABLED": true + ] + + func testConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertEqual(config.baseURL.absoluteString, "https://www.example.com") + XCTAssertEqual(config.oAuthClientId, "oauth_client_id") + XCTAssertEqual(config.feedbackEmail, "example@mail.com") + XCTAssertEqual(config.tokenType, TokenType.jwt) + XCTAssertFalse(config.whatsNewEnabled) + } + + func testAgreementConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertEqual(config.agreementConfig.privacyPolicyURL, URL(string: "https://www.example.com/privacy")) + XCTAssertEqual(config.agreementConfig.tosURL, URL(string: "https://www.example.com/tos")) + } + + func testFirebaseConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.firebaseConfig.enabled) + XCTAssertEqual(config.firebaseConfig.apiKey, "testApiKey") + XCTAssertEqual(config.firebaseConfig.bundleID, "testBundleID") + XCTAssertEqual(config.firebaseConfig.clientID, "testClientID") + XCTAssertEqual(config.firebaseConfig.databaseURL, "https://test.database.url") + XCTAssertEqual(config.firebaseConfig.gcmSenderID, "testGCMSenderID") + XCTAssertEqual(config.firebaseConfig.googleAppID, "testGoogleAppID") + XCTAssertEqual(config.firebaseConfig.projectID, "testProjectID") + XCTAssertEqual(config.firebaseConfig.reversedClientID, "testReversedClientID") + XCTAssertEqual(config.firebaseConfig.storageBucket, "testStorageBucket") + XCTAssertEqual(config.firebaseConfig.isAnalyticsSourceFirebase, true) + XCTAssertEqual(config.firebaseConfig.cloudMessagingEnabled, true) + } + +} diff --git a/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift b/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift new file mode 100644 index 000000000..11c70a3c7 --- /dev/null +++ b/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift @@ -0,0 +1,105 @@ +// +// FirebaseConfig.swift +// Core +// +// Created by Muhammad Umer on 11/12/23. +// + +import Foundation +import FirebaseCore + +private enum FirebaseKeys: String { + case enabled = "ENABLED" + case analyticsSource = "ANALYTICS_SOURCE" + case cloudMessagingEnabled = "CLOUD_MESSAGING_ENABLED" + case apiKey = "API_KEY" + case bundleID = "BUNDLE_ID" + case clientID = "CLIENT_ID" + case databaseURL = "DATABASE_URL" + case gcmSenderID = "GCM_SENDER_ID" + case googleAppID = "GOOGLE_APP_ID" + case projectID = "PROJECT_ID" + case reversedClientID = "REVERSED_CLIENT_ID" + case storageBucket = "STORAGE_BUCKET" +} + +enum AnalyticsSource: String { + case firebase + case segment + case none +} + +public class FirebaseConfig: NSObject { + public var enabled: Bool = false + public var cloudMessagingEnabled: Bool = false + public let apiKey: String? + public let bundleID: String? + public let clientID: String? + public let databaseURL: String? + public let gcmSenderID: String? + public let googleAppID: String? + public let projectID: String? + public let reversedClientID: String? + public let storageBucket: String? + + private let analyticsSource: AnalyticsSource + + public var requiredKeysAvailable: Bool { + return apiKey != nil && clientID != nil && googleAppID != nil && gcmSenderID != nil + } + + init(dictionary: [String: AnyObject]) { + apiKey = dictionary[FirebaseKeys.apiKey.rawValue] as? String + clientID = dictionary[FirebaseKeys.clientID.rawValue] as? String + googleAppID = dictionary[FirebaseKeys.googleAppID.rawValue] as? String + gcmSenderID = dictionary[FirebaseKeys.gcmSenderID.rawValue] as? String + bundleID = dictionary[FirebaseKeys.bundleID.rawValue] as? String + databaseURL = dictionary[FirebaseKeys.databaseURL.rawValue] as? String + projectID = dictionary[FirebaseKeys.projectID.rawValue] as? String + reversedClientID = dictionary[FirebaseKeys.reversedClientID.rawValue] as? String + storageBucket = dictionary[FirebaseKeys.storageBucket.rawValue] as? String + + let analyticsSource = dictionary[FirebaseKeys.analyticsSource.rawValue] as? String + self.analyticsSource = AnalyticsSource(rawValue: analyticsSource ?? AnalyticsSource.none.rawValue) ?? .none + + super.init() + + enabled = requiredKeysAvailable && dictionary[FirebaseKeys.enabled.rawValue] as? Bool == true + let cloudMessagingEnabled = dictionary[FirebaseKeys.cloudMessagingEnabled.rawValue] as? Bool ?? false + self.cloudMessagingEnabled = enabled && cloudMessagingEnabled + } + + public var isAnalyticsSourceSegment: Bool { + return analyticsSource == AnalyticsSource.segment + } + + public var isAnalyticsSourceFirebase: Bool { + return analyticsSource == AnalyticsSource.firebase + } + + public var firebaseOptions: FirebaseOptions? { + if enabled, + requiredKeysAvailable, + let bundleID = bundleID, + let googleAppID = googleAppID, + let gcmSenderID = gcmSenderID { + let firebaseOptions = FirebaseOptions(googleAppID: googleAppID, + gcmSenderID: gcmSenderID) + firebaseOptions.apiKey = apiKey + firebaseOptions.projectID = projectID + firebaseOptions.bundleID = bundleID + firebaseOptions.clientID = clientID + firebaseOptions.storageBucket = storageBucket + firebaseOptions.databaseURL = databaseURL + } + + return nil + } +} + +private let key = "FIREBASE" +extension Config { + public var firebaseConfig: FirebaseConfig { + return FirebaseConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Configuration/Config.swift b/Core/Core/Configuration/Config.swift deleted file mode 100644 index 8263f5c16..000000000 --- a/Core/Core/Configuration/Config.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// Config.swift -// Core -// -// Created by Vladimir Chekyrta on 14.09.2022. -// - -import Foundation - -public class Config { - - public let baseURL: URL - public let oAuthClientId: String - public let tokenType: TokenType = .jwt - - public lazy var termsOfUse: URL? = { - URL(string: "\(baseURL.description)/tos") - }() - - public lazy var privacyPolicy: URL? = { - URL(string: "\(baseURL.description)/privacy") - }() - - public let feedbackEmail = "support@example.com" - - private let appStoreId = "0000000000" - public var appStoreLink: String { - "itms-apps://itunes.apple.com/app/id\(appStoreId)?mt=8" - } - public let whatsNewEnabled: Bool = false - - public init(baseURL: String, oAuthClientId: String) { - guard let url = URL(string: baseURL) else { - fatalError("Ivalid baseURL") - } - self.baseURL = url - self.oAuthClientId = oAuthClientId - } -} - -public extension Config { - enum TokenType: String { - case jwt = "JWT" - case bearer = "BEARER" - } -} - -// Mark - For testing and SwiftUI preview -#if DEBUG -public class ConfigMock: Config { - public convenience init() { - self.init(baseURL: "https://google.com/", oAuthClientId: "client_id") - } -} -#endif diff --git a/Core/Core/Configuration/Combine/Debounce.swift b/Core/Core/Configuration/Debounce.swift similarity index 100% rename from Core/Core/Configuration/Combine/Debounce.swift rename to Core/Core/Configuration/Debounce.swift diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index 874159e4c..8c47cb6a1 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -20,9 +20,9 @@ public class AuthRepository: AuthRepositoryProtocol { private let api: API private var appStorage: CoreStorage - private let config: Config + private let config: ConfigProtocol - public init(api: API, appStorage: CoreStorage, config: Config) { + public init(api: API, appStorage: CoreStorage, config: ConfigProtocol) { self.api = api self.appStorage = appStorage self.config = config diff --git a/Core/Core/Network/API.swift b/Core/Core/Network/API.swift index 1142d85ce..7e3964b62 100644 --- a/Core/Core/Network/API.swift +++ b/Core/Core/Network/API.swift @@ -12,9 +12,9 @@ import WebKit public final class API { private let session: Alamofire.Session - private let config: Config + private let config: ConfigProtocol - public init(session: Session, config: Config) { + public init(session: Session, config: ConfigProtocol) { self.session = session self.config = config } diff --git a/Core/Core/Network/RequestInterceptor.swift b/Core/Core/Network/RequestInterceptor.swift index c2b0b00fd..f27b6f310 100644 --- a/Core/Core/Network/RequestInterceptor.swift +++ b/Core/Core/Network/RequestInterceptor.swift @@ -10,10 +10,10 @@ import Alamofire final public class RequestInterceptor: Alamofire.RequestInterceptor { - private let config: Config + private let config: ConfigProtocol private var storage: CoreStorage - public init(config: Config, storage: CoreStorage) { + public init(config: ConfigProtocol, storage: CoreStorage) { self.config = config self.storage = storage } diff --git a/Core/Core/View/Base/WebUnitViewModel.swift b/Core/Core/View/Base/WebUnitViewModel.swift index caa010a93..298abb1d0 100644 --- a/Core/Core/View/Base/WebUnitViewModel.swift +++ b/Core/Core/View/Base/WebUnitViewModel.swift @@ -11,7 +11,7 @@ import SwiftUI public class WebUnitViewModel: ObservableObject { let authInteractor: AuthInteractorProtocol - let config: Config + let config: ConfigProtocol @Published var updatingCookies: Bool = false @Published var cookiesReady: Bool = false @@ -26,7 +26,7 @@ public class WebUnitViewModel: ObservableObject { } } - public init(authInteractor: AuthInteractorProtocol, config: Config) { + public init(authInteractor: AuthInteractorProtocol, config: ConfigProtocol) { self.authInteractor = authInteractor self.config = config } diff --git a/Course/Course/Data/CourseRepository.swift b/Course/Course/Data/CourseRepository.swift index e07f75bb8..72abf3394 100644 --- a/Course/Course/Data/CourseRepository.swift +++ b/Course/Course/Data/CourseRepository.swift @@ -27,12 +27,12 @@ public class CourseRepository: CourseRepositoryProtocol { private let api: API private let appStorage: CoreStorage - private let config: Config + private let config: ConfigProtocol private let persistence: CoursePersistenceProtocol public init(api: API, appStorage: CoreStorage, - config: Config, + config: ConfigProtocol, persistence: CoursePersistenceProtocol) { self.api = api self.appStorage = appStorage diff --git a/Course/Course/Presentation/Container/CourseContainerViewModel.swift b/Course/Course/Presentation/Container/CourseContainerViewModel.swift index a3f90b8e9..fd86eb4c3 100644 --- a/Course/Course/Presentation/Container/CourseContainerViewModel.swift +++ b/Course/Course/Presentation/Container/CourseContainerViewModel.swift @@ -28,7 +28,7 @@ public class CourseContainerViewModel: BaseCourseViewModel { } let router: CourseRouter - let config: Config + let config: ConfigProtocol let connectivity: ConnectivityProtocol let isActive: Bool? @@ -46,7 +46,7 @@ public class CourseContainerViewModel: BaseCourseViewModel { authInteractor: AuthInteractorProtocol, router: CourseRouter, analytics: CourseAnalytics, - config: Config, + config: ConfigProtocol, connectivity: ConnectivityProtocol, manager: DownloadManagerProtocol, isActive: Bool?, diff --git a/Course/Course/Presentation/Details/CourseDetailsViewModel.swift b/Course/Course/Presentation/Details/CourseDetailsViewModel.swift index 6b1e6d747..ac2283d17 100644 --- a/Course/Course/Presentation/Details/CourseDetailsViewModel.swift +++ b/Course/Course/Presentation/Details/CourseDetailsViewModel.swift @@ -32,7 +32,7 @@ public class CourseDetailsViewModel: ObservableObject { private let interactor: CourseInteractorProtocol private let analytics: CourseAnalytics let router: CourseRouter - let config: Config + let config: ConfigProtocol let cssInjector: CSSInjector let connectivity: ConnectivityProtocol @@ -40,7 +40,7 @@ public class CourseDetailsViewModel: ObservableObject { interactor: CourseInteractorProtocol, router: CourseRouter, analytics: CourseAnalytics, - config: Config, + config: ConfigProtocol, cssInjector: CSSInjector, connectivity: ConnectivityProtocol ) { diff --git a/Dashboard/Dashboard/Data/DashboardRepository.swift b/Dashboard/Dashboard/Data/DashboardRepository.swift index ce5721784..cff780083 100644 --- a/Dashboard/Dashboard/Data/DashboardRepository.swift +++ b/Dashboard/Dashboard/Data/DashboardRepository.swift @@ -17,10 +17,10 @@ public class DashboardRepository: DashboardRepositoryProtocol { private let api: API private let storage: CoreStorage - private let config: Config + private let config: ConfigProtocol private let persistence: DashboardPersistenceProtocol - public init(api: API, storage: CoreStorage, config: Config, persistence: DashboardPersistenceProtocol) { + public init(api: API, storage: CoreStorage, config: ConfigProtocol, persistence: DashboardPersistenceProtocol) { self.api = api self.storage = storage self.config = config diff --git a/Discovery/Discovery/Data/DiscoveryRepository.swift b/Discovery/Discovery/Data/DiscoveryRepository.swift index 4ba3fa74b..bf898af35 100644 --- a/Discovery/Discovery/Data/DiscoveryRepository.swift +++ b/Discovery/Discovery/Data/DiscoveryRepository.swift @@ -20,12 +20,12 @@ public class DiscoveryRepository: DiscoveryRepositoryProtocol { private let api: API private let appStorage: CoreStorage - private let config: Config + private let config: ConfigProtocol private let persistence: DiscoveryPersistenceProtocol public init(api: API, appStorage: CoreStorage, - config: Config, + config: ConfigProtocol, persistence: DiscoveryPersistenceProtocol) { self.api = api self.appStorage = appStorage diff --git a/Discovery/Discovery/Presentation/DiscoveryViewModel.swift b/Discovery/Discovery/Presentation/DiscoveryViewModel.swift index 18391bcc5..ec8268ae1 100644 --- a/Discovery/Discovery/Presentation/DiscoveryViewModel.swift +++ b/Discovery/Discovery/Presentation/DiscoveryViewModel.swift @@ -29,14 +29,14 @@ public class DiscoveryViewModel: ObservableObject { } let router: DiscoveryRouter - let config: Config + let config: ConfigProtocol let connectivity: ConnectivityProtocol private let interactor: DiscoveryInteractorProtocol private let analytics: DiscoveryAnalytics public init( router: DiscoveryRouter, - config: Config, + config: ConfigProtocol, interactor: DiscoveryInteractorProtocol, connectivity: ConnectivityProtocol, analytics: DiscoveryAnalytics diff --git a/Discovery/Discovery/Presentation/UpdateViews/UpdateNotificationView.swift b/Discovery/Discovery/Presentation/UpdateViews/UpdateNotificationView.swift index 83098abf7..5f8a3dfab 100644 --- a/Discovery/Discovery/Presentation/UpdateViews/UpdateNotificationView.swift +++ b/Discovery/Discovery/Presentation/UpdateViews/UpdateNotificationView.swift @@ -10,9 +10,9 @@ import Core public struct UpdateNotificationView: View { - private let config: Config + private let config: ConfigProtocol - public init(config: Config) { + public init(config: ConfigProtocol) { self.config = config } diff --git a/Discovery/Discovery/Presentation/UpdateViews/UpdateRecommendedView.swift b/Discovery/Discovery/Presentation/UpdateViews/UpdateRecommendedView.swift index 5059707d7..066119a62 100644 --- a/Discovery/Discovery/Presentation/UpdateViews/UpdateRecommendedView.swift +++ b/Discovery/Discovery/Presentation/UpdateViews/UpdateRecommendedView.swift @@ -12,9 +12,9 @@ public struct UpdateRecommendedView: View { @Environment (\.isHorizontal) private var isHorizontal private let router: DiscoveryRouter - private let config: Config + private let config: ConfigProtocol - public init(router: DiscoveryRouter, config: Config) { + public init(router: DiscoveryRouter, config: ConfigProtocol) { self.router = router self.config = config } diff --git a/Discovery/Discovery/Presentation/UpdateViews/UpdateRequiredView.swift b/Discovery/Discovery/Presentation/UpdateViews/UpdateRequiredView.swift index 9f121b944..0d69d456b 100644 --- a/Discovery/Discovery/Presentation/UpdateViews/UpdateRequiredView.swift +++ b/Discovery/Discovery/Presentation/UpdateViews/UpdateRequiredView.swift @@ -12,10 +12,10 @@ public struct UpdateRequiredView: View { @Environment (\.isHorizontal) private var isHorizontal private let router: DiscoveryRouter - private let config: Config + private let config: ConfigProtocol private let showAccountLink: Bool - public init(router: DiscoveryRouter, config: Config, showAccountLink: Bool = true) { + public init(router: DiscoveryRouter, config: ConfigProtocol, showAccountLink: Bool = true) { self.router = router self.config = config self.showAccountLink = showAccountLink diff --git a/Discussion/Discussion/Data/Network/DiscussionRepository.swift b/Discussion/Discussion/Data/Network/DiscussionRepository.swift index 7e395da51..ab8686ad7 100644 --- a/Discussion/Discussion/Data/Network/DiscussionRepository.swift +++ b/Discussion/Discussion/Data/Network/DiscussionRepository.swift @@ -37,10 +37,10 @@ public class DiscussionRepository: DiscussionRepositoryProtocol { private let api: API private let appStorage: CoreStorage - private let config: Config + private let config: ConfigProtocol private let router: DiscussionRouter - public init(api: API, appStorage: CoreStorage, config: Config, router: DiscussionRouter) { + public init(api: API, appStorage: CoreStorage, config: ConfigProtocol, router: DiscussionRouter) { self.api = api self.appStorage = appStorage self.config = config diff --git a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift index bc2572e8d..4a955aa2e 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift @@ -43,13 +43,13 @@ public class BaseResponsesViewModel { internal let interactor: DiscussionInteractorProtocol internal let router: DiscussionRouter - internal let config: Config + internal let config: ConfigProtocol internal let addPostSubject = CurrentValueSubject(nil) init( interactor: DiscussionInteractorProtocol, router: DiscussionRouter, - config: Config + config: ConfigProtocol ) { self.interactor = interactor self.router = router diff --git a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift index 92555f692..cc7ec0820 100644 --- a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift @@ -19,7 +19,7 @@ public class ResponsesViewModel: BaseResponsesViewModel, ObservableObject { public init( interactor: DiscussionInteractorProtocol, router: DiscussionRouter, - config: Config, + config: ConfigProtocol, threadStateSubject: CurrentValueSubject ) { self.threadStateSubject = threadStateSubject diff --git a/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift b/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift index db10d8039..bc9be9fac 100644 --- a/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift @@ -21,7 +21,7 @@ public class ThreadViewModel: BaseResponsesViewModel, ObservableObject { public init( interactor: DiscussionInteractorProtocol, router: DiscussionRouter, - config: Config, + config: ConfigProtocol, postStateSubject: CurrentValueSubject ) { self.postStateSubject = postStateSubject diff --git a/Discussion/Discussion/Presentation/CreateNewThread/CreateNewThreadViewModel.swift b/Discussion/Discussion/Presentation/CreateNewThread/CreateNewThreadViewModel.swift index 95c907300..46d12d614 100644 --- a/Discussion/Discussion/Presentation/CreateNewThread/CreateNewThreadViewModel.swift +++ b/Discussion/Discussion/Presentation/CreateNewThread/CreateNewThreadViewModel.swift @@ -26,12 +26,12 @@ public class CreateNewThreadViewModel: ObservableObject { public let interactor: DiscussionInteractorProtocol public let router: DiscussionRouter - public let config: Config + public let config: ConfigProtocol public init( interactor: DiscussionInteractorProtocol, router: DiscussionRouter, - config: Config + config: ConfigProtocol ) { self.interactor = interactor self.router = router diff --git a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift index 9364f4d6b..85ec229e8 100644 --- a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift +++ b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift @@ -30,13 +30,13 @@ public class DiscussionTopicsViewModel: ObservableObject { let interactor: DiscussionInteractorProtocol let router: DiscussionRouter let analytics: DiscussionAnalytics - let config: Config + let config: ConfigProtocol public init(title: String, interactor: DiscussionInteractorProtocol, router: DiscussionRouter, analytics: DiscussionAnalytics, - config: Config) { + config: ConfigProtocol) { self.title = title self.interactor = interactor self.router = router diff --git a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift index baad91ffc..04b02c788 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift @@ -76,14 +76,14 @@ public class PostsViewModel: ObservableObject { private var threads: ThreadLists = ThreadLists(threads: []) private let interactor: DiscussionInteractorProtocol private let router: DiscussionRouter - private let config: Config + private let config: ConfigProtocol internal let postStateSubject = CurrentValueSubject(nil) private var cancellable: AnyCancellable? public init( interactor: DiscussionInteractorProtocol, router: DiscussionRouter, - config: Config + config: ConfigProtocol ) { self.interactor = interactor self.router = router diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index d3b1013cc..a5e860417 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -29,7 +29,6 @@ 02ED50D829A66007008341CD /* languages.json in Resources */ = {isa = PBXBuildFile; fileRef = 02ED50DA29A66007008341CD /* languages.json */; }; 02F175312A4DA95B0019CD70 /* MainScreenAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */; }; 071009C928D1DB3F00344290 /* ScreenAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071009C828D1DB3F00344290 /* ScreenAssembly.swift */; }; - 0727876D28D23312002E9142 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0727876C28D23312002E9142 /* Environment.swift */; }; 0727878E28D347C7002E9142 /* MainScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0727878D28D347C7002E9142 /* MainScreenView.swift */; }; 072787B128D34D83002E9142 /* Discovery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 072787B028D34D83002E9142 /* Discovery.framework */; }; 072787B228D34D83002E9142 /* Discovery.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 072787B028D34D83002E9142 /* Discovery.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -94,7 +93,6 @@ 02ED50DB29A6600B008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = uk; path = uk.lproj/languages.json; sourceTree = ""; }; 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenAnalytics.swift; sourceTree = ""; }; 071009C828D1DB3F00344290 /* ScreenAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenAssembly.swift; sourceTree = ""; }; - 0727876C28D23312002E9142 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; 0727878D28D347C7002E9142 /* MainScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenView.swift; sourceTree = ""; }; 072787B028D34D83002E9142 /* Discovery.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Discovery.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0770DE1228D07845006D8A5D /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -198,7 +196,6 @@ 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */, 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */, 0293A2012A6FC9E30090A336 /* Data */, - 0727876C28D23312002E9142 /* Environment.swift */, 0727878C28D347B2002E9142 /* View */, 0770DE1A28D084BC006D8A5D /* DI */, 07D5DA3D28D075AB00752FD9 /* Assets.xcassets */, @@ -256,6 +253,7 @@ 07D5DA2E28D075AA00752FD9 /* Frameworks */, 07D5DA2F28D075AA00752FD9 /* Resources */, 0770DE1528D07845006D8A5D /* Embed Frameworks */, + DB97C0542B002EF00035C36F /* Process Config */, 02F175442A4E3B320019CD70 /* FirebaseCrashlytics */, ); buildRules = ( @@ -375,6 +373,24 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + DB97C0542B002EF00035C36F /* Process Config */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Process Config"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n./process_config.sh\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -395,7 +411,6 @@ 025AD4AC2A6FB95C00AB8FA7 /* DatabaseManager.swift in Sources */, 024E69202AEFC3FB00FA0B59 /* MainScreenViewModel.swift in Sources */, 0770DE2028D0858A006D8A5D /* Router.swift in Sources */, - 0727876D28D23312002E9142 /* Environment.swift in Sources */, 0293A2092A6FCDE50090A336 /* DashboardPersistence.swift in Sources */, 0770DE1728D080A1006D8A5D /* RouteController.swift in Sources */, 071009C928D1DB3F00344290 /* ScreenAssembly.swift in Sources */, diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index bb7c92a3b..1cbcc0ed7 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -31,8 +31,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - if BuildConfiguration.shared.firebaseOptions.apiKey != "" { - FirebaseApp.configure(options: BuildConfiguration.shared.firebaseOptions) + if Config.shared.firebaseConfig.enabled { + FirebaseApp.configure() Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) } diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 9c2bff47e..5ed98dd50 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -117,12 +117,14 @@ class AppAssembly: Assembly { r.resolve(Router.self)! }.inObjectScope(.container) - container.register(Config.self) { _ in - Config(baseURL: BuildConfiguration.shared.baseURL, oAuthClientId: BuildConfiguration.shared.clientId) + container.register(ConfigProtocol.self) { _ in + Config.shared }.inObjectScope(.container) - container.register(CSSInjector.self) { _ in - CSSInjector(baseURL: BuildConfiguration.shared.baseURL) + container.register(CSSInjector.self) { r in + CSSInjector( + config: r.resolve(ConfigProtocol.self)! + ) }.inObjectScope(.container) container.register(KeychainSwift.self) { _ in diff --git a/OpenEdX/DI/NetworkAssembly.swift b/OpenEdX/DI/NetworkAssembly.swift index 1f036a860..83537fb29 100644 --- a/OpenEdX/DI/NetworkAssembly.swift +++ b/OpenEdX/DI/NetworkAssembly.swift @@ -13,7 +13,7 @@ import Swinject class NetworkAssembly: Assembly { func assemble(container: Container) { container.register(RequestInterceptor.self) { r in - RequestInterceptor(config: r.resolve(Config.self)!, storage: r.resolve(CoreStorage.self)!) + RequestInterceptor(config: r.resolve(ConfigProtocol.self)!, storage: r.resolve(CoreStorage.self)!) }.inObjectScope(.container) container.register(Alamofire.Session.self) { r in @@ -38,7 +38,7 @@ class NetworkAssembly: Assembly { }.inObjectScope(.container) container.register(API.self) {r in - API(session: r.resolve(Alamofire.Session.self)!, config: r.resolve(Config.self)!) + API(session: r.resolve(Alamofire.Session.self)!, config: r.resolve(ConfigProtocol.self)!) }.inObjectScope(.container) } } diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index f826a23b6..c50f7e0f0 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -24,7 +24,7 @@ class ScreenAssembly: Assembly { AuthRepository( api: r.resolve(API.self)!, appStorage: r.resolve(CoreStorage.self)!, - config: r.resolve(Config.self)! + config: r.resolve(ConfigProtocol.self)! ) } container.register(AuthInteractorProtocol.self) { r in @@ -37,7 +37,7 @@ class ScreenAssembly: Assembly { container.register(MainScreenViewModel.self) { r in MainScreenViewModel( analytics: r.resolve(MainScreenAnalytics.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, profileInteractor: r.resolve(ProfileInteractorProtocol.self)! ) } @@ -47,7 +47,7 @@ class ScreenAssembly: Assembly { SignInViewModel( interactor: r.resolve(AuthInteractorProtocol.self)!, router: r.resolve(AuthorizationRouter.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, analytics: r.resolve(AuthorizationAnalytics.self)!, validator: r.resolve(Validator.self)! ) @@ -57,7 +57,7 @@ class ScreenAssembly: Assembly { interactor: r.resolve(AuthInteractorProtocol.self)!, router: r.resolve(AuthorizationRouter.self)!, analytics: r.resolve(AuthorizationAnalytics.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, cssInjector: r.resolve(CSSInjector.self)!, validator: r.resolve(Validator.self)! ) @@ -80,7 +80,7 @@ class ScreenAssembly: Assembly { DiscoveryRepository( api: r.resolve(API.self)!, appStorage: r.resolve(CoreStorage.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, persistence: r.resolve(DiscoveryPersistenceProtocol.self)! ) } @@ -92,7 +92,7 @@ class ScreenAssembly: Assembly { container.register(DiscoveryViewModel.self) { r in DiscoveryViewModel( router: r.resolve(DiscoveryRouter.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, interactor: r.resolve(DiscoveryInteractorProtocol.self)!, connectivity: r.resolve(ConnectivityProtocol.self)!, analytics: r.resolve(DiscoveryAnalytics.self)! @@ -118,7 +118,7 @@ class ScreenAssembly: Assembly { DashboardRepository( api: r.resolve(API.self)!, storage: r.resolve(CoreStorage.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, persistence: r.resolve(DashboardPersistenceProtocol.self)! ) } @@ -143,7 +143,7 @@ class ScreenAssembly: Assembly { storage: r.resolve(AppStorage.self)!, coreDataHandler: r.resolve(CoreDataHandlerProtocol.self)!, downloadManager: r.resolve(DownloadManagerProtocol.self)!, - config: r.resolve(Config.self)! + config: r.resolve(ConfigProtocol.self)! ) } container.register(ProfileInteractorProtocol.self) { r in @@ -156,7 +156,7 @@ class ScreenAssembly: Assembly { interactor: r.resolve(ProfileInteractorProtocol.self)!, router: r.resolve(ProfileRouter.self)!, analytics: r.resolve(ProfileAnalytics.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, connectivity: r.resolve(ConnectivityProtocol.self)! ) } @@ -194,7 +194,7 @@ class ScreenAssembly: Assembly { CourseRepository( api: r.resolve(API.self)!, appStorage: r.resolve(CoreStorage.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, persistence: r.resolve(CoursePersistenceProtocol.self)! ) } @@ -208,7 +208,7 @@ class ScreenAssembly: Assembly { interactor: r.resolve(CourseInteractorProtocol.self)!, router: r.resolve(CourseRouter.self)!, analytics: r.resolve(CourseAnalytics.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, cssInjector: r.resolve(CSSInjector.self)!, connectivity: r.resolve(ConnectivityProtocol.self)! ) @@ -223,7 +223,7 @@ class ScreenAssembly: Assembly { authInteractor: r.resolve(AuthInteractorProtocol.self)!, router: r.resolve(CourseRouter.self)!, analytics: r.resolve(CourseAnalytics.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, connectivity: r.resolve(ConnectivityProtocol.self)!, manager: r.resolve(DownloadManagerProtocol.self)!, isActive: isActive, @@ -267,7 +267,7 @@ class ScreenAssembly: Assembly { container.register(WebUnitViewModel.self) { r in WebUnitViewModel(authInteractor: r.resolve(AuthInteractorProtocol.self)!, - config: r.resolve(Config.self)!) + config: r.resolve(ConfigProtocol.self)!) } container.register( @@ -326,7 +326,7 @@ class ScreenAssembly: Assembly { DiscussionRepository( api: r.resolve(API.self)!, appStorage: r.resolve(CoreStorage.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, router: r.resolve(DiscussionRouter.self)! ) } @@ -343,7 +343,7 @@ class ScreenAssembly: Assembly { interactor: r.resolve(DiscussionInteractorProtocol.self)!, router: r.resolve(DiscussionRouter.self)!, analytics: r.resolve(DiscussionAnalytics.self)!, - config: r.resolve(Config.self)! + config: r.resolve(ConfigProtocol.self)! ) } @@ -360,7 +360,7 @@ class ScreenAssembly: Assembly { PostsViewModel( interactor: r.resolve(DiscussionInteractorProtocol.self)!, router: r.resolve(DiscussionRouter.self)!, - config: r.resolve(Config.self)! + config: r.resolve(ConfigProtocol.self)! ) } @@ -368,7 +368,7 @@ class ScreenAssembly: Assembly { ThreadViewModel( interactor: r.resolve(DiscussionInteractorProtocol.self)!, router: r.resolve(DiscussionRouter.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, postStateSubject: subject ) } @@ -377,7 +377,7 @@ class ScreenAssembly: Assembly { ResponsesViewModel( interactor: r.resolve(DiscussionInteractorProtocol.self)!, router: r.resolve(DiscussionRouter.self)!, - config: r.resolve(Config.self)!, + config: r.resolve(ConfigProtocol.self)!, threadStateSubject: subject ) } @@ -386,7 +386,7 @@ class ScreenAssembly: Assembly { CreateNewThreadViewModel( interactor: r.resolve(DiscussionInteractorProtocol.self)!, router: r.resolve(DiscussionRouter.self)!, - config: r.resolve(Config.self)! + config: r.resolve(ConfigProtocol.self)! ) } } diff --git a/OpenEdX/Environment.swift b/OpenEdX/Environment.swift deleted file mode 100644 index e89c0bb88..000000000 --- a/OpenEdX/Environment.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// Environment.swift -// OpenEdX -// -// Created by Vladimir Chekyrta on 14.09.2022. -// - -import Foundation -import Core -import FirebaseCore - -enum `Environment`: String { - case debugDev = "DebugDev" - case releaseDev = "ReleaseDev" - - case debugStage = "DebugStage" - case releaseStage = "ReleaseStage" - - case debugProd = "DebugProd" - case releaseProd = "ReleaseProd" -} - -class BuildConfiguration { - static let shared = BuildConfiguration() - - var environment: Environment - - var baseURL: String { - switch environment { - case .debugDev, .releaseDev: - return "https://example-dev.com" - case .debugStage, .releaseStage: - return "https://example-stage.com" - case .debugProd, .releaseProd: - return "https://example.com" - } - } - - var clientId: String { - switch environment { - case .debugDev, .releaseDev: - return "DEV_CLIENT_ID" - case .debugStage, .releaseStage: - return "STAGE_CLIENT_ID" - case .debugProd, .releaseProd: - return "PROD_CLIENT_ID" - } - } - - var firebaseOptions: FirebaseOptions { - switch environment { - case .debugDev, .releaseDev: - let firebaseOptions = FirebaseOptions(googleAppID: "", - gcmSenderID: "") - firebaseOptions.apiKey = "" - firebaseOptions.projectID = "" - firebaseOptions.bundleID = "" - firebaseOptions.clientID = "" - firebaseOptions.storageBucket = "" - - return firebaseOptions - case .debugStage, .releaseStage: - let firebaseOptions = FirebaseOptions(googleAppID: "", - gcmSenderID: "") - firebaseOptions.apiKey = "" - firebaseOptions.projectID = "" - firebaseOptions.bundleID = "" - firebaseOptions.clientID = "" - firebaseOptions.storageBucket = "" - - return firebaseOptions - case .debugProd, .releaseProd: - let firebaseOptions = FirebaseOptions(googleAppID: "", - gcmSenderID: "") - firebaseOptions.apiKey = "" - firebaseOptions.projectID = "" - firebaseOptions.bundleID = "" - firebaseOptions.clientID = "" - firebaseOptions.storageBucket = "" - - return firebaseOptions - } - } - - init() { - let currentConfiguration = Bundle.main.object(forInfoDictionaryKey: "Configuration") as! String - environment = Environment(rawValue: currentConfiguration)! - } -} diff --git a/OpenEdX/RouteController.swift b/OpenEdX/RouteController.swift index 6d7d0297b..0867d131c 100644 --- a/OpenEdX/RouteController.swift +++ b/OpenEdX/RouteController.swift @@ -51,7 +51,7 @@ class RouteController: UIViewController { private func showMainOrWhatsNewScreen() { var storage = Container.shared.resolve(WhatsNewStorage.self)! - let config = Container.shared.resolve(Config.self)! + let config = Container.shared.resolve(ConfigProtocol.self)! let viewModel = WhatsNewViewModel(storage: storage) let shouldShowWhatsNew = viewModel.shouldShowWhatsNew() diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index 062dfcea2..4923044db 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -61,7 +61,7 @@ public class Router: AuthorizationRouter, public func showMainOrWhatsNewScreen() { showToolBar() var storage = Container.shared.resolve(WhatsNewStorage.self)! - let config = Container.shared.resolve(Config.self)! + let config = Container.shared.resolve(ConfigProtocol.self)! let viewModel = WhatsNewViewModel(storage: storage) let whatsNew = WhatsNewView(router: Container.shared.resolve(WhatsNewRouter.self)!, viewModel: viewModel) @@ -429,7 +429,7 @@ public class Router: AuthorizationRouter, public func showUpdateRequiredView(showAccountLink: Bool = true) { let view = UpdateRequiredView( router: self, - config: Container.shared.resolve(Config.self)!, + config: Container.shared.resolve(ConfigProtocol.self)!, showAccountLink: showAccountLink ) let controller = UIHostingController(rootView: view) @@ -437,7 +437,7 @@ public class Router: AuthorizationRouter, } public func showUpdateRecomendedView() { - let view = UpdateRecommendedView(router: self, config: Container.shared.resolve(Config.self)!) + let view = UpdateRecommendedView(router: self, config: Container.shared.resolve(ConfigProtocol.self)!) self.presentView(transitionStyle: .crossDissolve, view: view) } diff --git a/OpenEdX/View/MainScreenViewModel.swift b/OpenEdX/View/MainScreenViewModel.swift index 0296a6509..d45b3503c 100644 --- a/OpenEdX/View/MainScreenViewModel.swift +++ b/OpenEdX/View/MainScreenViewModel.swift @@ -12,10 +12,10 @@ import Profile class MainScreenViewModel: ObservableObject { private let analytics: MainScreenAnalytics - let config: Config + let config: ConfigProtocol let profileInteractor: ProfileInteractorProtocol - init(analytics: MainScreenAnalytics, config: Config, profileInteractor: ProfileInteractorProtocol) { + init(analytics: MainScreenAnalytics, config: ConfigProtocol, profileInteractor: ProfileInteractorProtocol) { self.analytics = analytics self.config = config self.profileInteractor = profileInteractor diff --git a/Podfile.lock b/Podfile.lock index cf6c60e94..4afedd8c7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -182,4 +182,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a44d8de5a5803eb3e3c995134c79c3dad959dbf7 -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 diff --git a/Profile/Profile/Data/ProfileRepository.swift b/Profile/Profile/Data/ProfileRepository.swift index e445f5225..788c7c513 100644 --- a/Profile/Profile/Data/ProfileRepository.swift +++ b/Profile/Profile/Data/ProfileRepository.swift @@ -30,14 +30,14 @@ public class ProfileRepository: ProfileRepositoryProtocol { private var storage: CoreStorage & ProfileStorage private let downloadManager: DownloadManagerProtocol private let coreDataHandler: CoreDataHandlerProtocol - private let config: Config + private let config: ConfigProtocol public init( api: API, storage: CoreStorage & ProfileStorage, coreDataHandler: CoreDataHandlerProtocol, downloadManager: DownloadManagerProtocol, - config: Config + config: ConfigProtocol ) { self.api = api self.storage = storage diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index 3456a1444..5e3756bd2 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -118,7 +118,7 @@ public struct ProfileView: View { .foregroundColor(Theme.Colors.textSecondary) } - if let tos = viewModel.config.termsOfUse { + if let tos = viewModel.config.agreementConfig.tosURL { Button(action: { viewModel.trackCookiePolicyClicked() UIApplication.shared.open(tos) @@ -136,7 +136,7 @@ public struct ProfileView: View { .foregroundColor(Theme.Colors.textSecondary) } - if let privacy = viewModel.config.privacyPolicy { + if let privacy = viewModel.config.agreementConfig.privacyPolicyURL { Button(action: { viewModel.trackPrivacyPolicyClicked() UIApplication.shared.open(privacy) diff --git a/Profile/Profile/Presentation/Profile/ProfileViewModel.swift b/Profile/Profile/Presentation/Profile/ProfileViewModel.swift index e31d837be..a7647e7df 100644 --- a/Profile/Profile/Presentation/Profile/ProfileViewModel.swift +++ b/Profile/Profile/Presentation/Profile/ProfileViewModel.swift @@ -35,7 +35,7 @@ public class ProfileViewModel: ObservableObject { @Published var latestVersion: String = "" let router: ProfileRouter - let config: Config + let config: ConfigProtocol let connectivity: ConnectivityProtocol private let interactor: ProfileInteractorProtocol @@ -45,7 +45,7 @@ public class ProfileViewModel: ObservableObject { interactor: ProfileInteractorProtocol, router: ProfileRouter, analytics: ProfileAnalytics, - config: Config, + config: ConfigProtocol, connectivity: ConnectivityProtocol ) { self.interactor = interactor diff --git a/process_config.py b/process_config.py new file mode 100644 index 000000000..72ca9d35f --- /dev/null +++ b/process_config.py @@ -0,0 +1,248 @@ +import plistlib +import os +import yaml +from pathlib import Path +import sys + +class PlistManager: + def __init__(self, config_dir, config_files): + self.config_dir = config_dir + self.config_files = config_files + + def get_config_paths(self): + return [Path(self.config_dir) / config_name for config_name in self.config_files] + + def get_product_name(self): + return os.getenv('PRODUCT_NAME') + + def get_bundle_identifier(self): + return os.getenv('PRODUCT_BUNDLE_IDENTIFIER') + + def get_info_plist_path(self): + return os.getenv('INFOPLIST_PATH') + + def get_wrapper_name(self): + return os.getenv('WRAPPER_NAME') + + def get_built_products_path(self): + return os.getenv('BUILT_PRODUCTS_DIR') + + def get_bundle_config_path(self): + return os.path.join(self.get_built_products_path(), self.get_wrapper_name(), 'config.plist') + + def get_app_info_plist_path(self): + built_products_path = self.get_built_products_path() + info_plist_path = self.get_info_plist_path() + + if built_products_path and info_plist_path: + return os.path.join(built_products_path, info_plist_path) + else: + return None + + def get_firebase_info_plist_path(self): + built_products_path = self.get_built_products_path() + wrapper_name = self.get_wrapper_name() + + if built_products_path and wrapper_name: + return os.path.join(built_products_path, wrapper_name, 'GoogleService-Info.plist') + else: + print("The BUILT_PRODUCTS_DIR or WRAPPER_NAME environment variable is not set.") + return None + + def get_firebase_config_path(self): + built_products_path = self.get_built_products_path() + wrapper_name = self.get_wrapper_name() + + if built_products_path and wrapper_name: + return os.path.join(built_products_path, wrapper_name, 'firebase.plist') + else: + print("The BUILT_PRODUCTS_DIR or WRAPPER_NAME environment variable is not set.") + return None + + def load_config(self): + properties = {} + + for path in self.get_config_paths(): + try: + with open(path, 'r') as file: + dict = yaml.safe_load(file) + if dict is not None: + properties.update(dict) + except FileNotFoundError: + print(f"{path} not found. Skipping.") + + return properties + + def yaml_to_plist(self): + plist_data = {} + + for path in self.get_config_paths(): + try: + with open(path, 'r') as file: + yaml_data = yaml.safe_load(file) + if yaml_data is not None: + plist_data.update(yaml_data) + except FileNotFoundError: + print(f"{path} not found. Skipping.") + except yaml.YAMLError as e: + print(f"Error parsing YAML file {path}: {e}") + + return plist_data + + def write_to_plist_file(self, plist, file_path): + file_name = os.path.basename(file_path) + with open(file_path, 'wb') as plist_file: + plistlib.dump(plist, plist_file) + print(f"File {file_name} has been written to {file_path}") + + def print_info_plist_contents(self, plist_path): + if not plist_path: + print(f"Path is not set. {plist_path}") + try: + with open(plist_path, 'rb') as plist_file: + plist_contents = plistlib.load(plist_file) + print(plist_contents) + except Exception as e: + print(f"Error reading plist file: {e}") + + +class ConfigurationManager: + def __init__(self, plist_manager): + self.plist_manager = plist_manager + + def get_environment_variable(self, variable): + return os.getenv(variable) + + def add_url_scheme(self, scheme, plist): + body = { + 'CFBundleTypeRole': 'Editor', + 'CFBundleURLSchemes': scheme + } + existing = plist.get('CFBundleURLTypes', []) + found = any(scheme in entry.get('CFBundleURLSchemes', []) for entry in existing) + if not found: + existing.append(body) + plist['CFBundleURLTypes'] = existing + + def add_firebase_config(self, config, plist, firebase_info_plist_path): + firebase = config.get('FIREBASE', {}) + + if firebase_info_plist_path: + plist['BUNDLE_ID'] = firebase.get('BUNDLE_ID', '') + plist['API_KEY'] = firebase.get('API_KEY', '') + plist['CLIENT_ID'] = firebase.get('CLIENT_ID', '') + plist['GOOGLE_APP_ID'] = firebase.get('GOOGLE_APP_ID', '') + plist['GCM_SENDER_ID'] = firebase.get('GCM_SENDER_ID', '') + + project_id = firebase.get('PROJECT_ID', '') + if project_id: + plist['PROJECT_ID'] = project_id + plist['STORAGE_BUCKET'] = project_id + '.appspot.com' + plist['DATABASE_URL'] = 'https://' + project_id + '.firebaseio.com' + + reversed_client_id = firebase.get('REVERSED_CLIENT_ID', '') + if reversed_client_id: + plist['REVERSED_CLIENT_ID'] = reversed_client_id + + self.plist_manager.write_to_plist_file(plist, self.plist_manager.get_firebase_info_plist_path()) + else: + print("Firebase config is empty. Skipping") + + def add_facebook_config(self, config, plist): + facebook = config.get('FACEBOOK', {}) + key = facebook.get('FACEBOOK_APP_ID') + client_token = facebook.get('CLIENT_TOKEN') + + if key and client_token: + plist["FacebookAppID"] = key + plist["FacebookClientToken"] = client_token + plist["FacebookDisplayName"] = self.plist_manager.get_product_name() + scheme = ["fb" + key] + self.add_url_scheme(scheme, plist) + + def add_google_config(self, config, plist): + google = config.get('GOOGLE', {}) + key = google.get('GOOGLE_PLUS_KEY') + + if key: + scheme = ['.'.join(reversed(key.split('.')))] + self.add_url_scheme(scheme, plist) + + def add_microsoft_config(self, config, plist): + microsoft = config.get('MICROSOFT', {}) + key = microsoft.get('APP_ID') + + if key: + bundle_identifier = self.plist_manager.get_bundle_identifier() + scheme = ["msauth." + bundle_identifier] + self.add_url_scheme(scheme, plist) + + def update_info_plist(self, plist_data, plist_path): + if not plist_path: + print("Path is not set.") + sys.exit(1) + + try: + with open(plist_path, 'rb') as plist_file: + plist_contents = plistlib.load(plist_file) + + plist_contents.update(plist_data) + + try: + plistlib.dumps(plist_contents) + except Exception as e: + print(f"Error validating plist contents: {e}") + sys.exit(1) + + self.plist_manager.write_to_plist_file(plist_contents, plist_path) + + except FileNotFoundError: + print(f"Plist file not found: {plist_path}") + sys.exit(1) + except Exception as e: + print(f"Error reading or writing plist file: {e}") + sys.exit(1) + +def main(): + def parse_yaml(file_path): + try: + with open(file_path, 'r') as file: + return yaml.safe_load(file) + except Exception: + print(f"Error opening or reading the file '{file_path}', No config file was written.") + sys.exit(0) + + yaml_path = 'config.yaml' + yaml_files = ['shared.yaml', 'ios.yaml'] + + config_data = parse_yaml(yaml_path) + directory = config_data.get('directory') + config_name = config_data.get('config') + print(f'directory: {directory}') + print(f'config: {config_name}') + + config_dir = os.path.join(directory, config_name) + plist_manager = PlistManager(config_dir, yaml_files) + config = plist_manager.load_config() + + if not config: + print("Config is empty. Skipping") + else: + configuration_manager = ConfigurationManager(plist_manager) + info_plist_path = plist_manager.get_info_plist_path() + firebase_info_plist_path = plist_manager.get_firebase_config_path() + app_info_plist_path = plist_manager.get_app_info_plist_path() + + plist = {} + configuration_manager.add_firebase_config(config, plist, firebase_info_plist_path) + configuration_manager.add_facebook_config(config, plist) + configuration_manager.add_google_config(config, plist) + configuration_manager.add_microsoft_config(config, plist) + configuration_manager.update_info_plist(plist, app_info_plist_path) + + bundle_config_path = plist_manager.get_bundle_config_path() + config_plist = plist_manager.yaml_to_plist() + plist_manager.write_to_plist_file(config_plist, bundle_config_path) + +if __name__ == "__main__": + main() diff --git a/process_config.sh b/process_config.sh new file mode 100755 index 000000000..8795e2676 --- /dev/null +++ b/process_config.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +VENV_PATH="${SRCROOT}/venv" + +if [ ! -d "$VENV_PATH" ]; then + /usr/bin/python3 -m venv "$VENV_PATH" +fi + +source "$VENV_PATH/bin/activate" + +pip install --upgrade pip +pip install PyYAML + +python process_config.py + +deactivate From dc2de0b68fde0e42164e5617e984a0863368ff56 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Tue, 14 Nov 2023 02:22:56 +0500 Subject: [PATCH 02/12] chore: handle configuration provided as argument, handle firebase crashlytics google app id, remove process_config.sh in favour of build script --- .../Configuration/Combine/Config/Config.swift | 6 +-- .../Combine/Config/ConfigTests.swift | 3 +- .../Combine/Config/FirebaseConfig.swift | 21 +++++++++- OpenEdX.xcodeproj/project.pbxproj | 5 ++- OpenEdX/AppDelegate.swift | 5 ++- process_config.py | 40 ++++++++++++++----- process_config.sh | 16 -------- 7 files changed, 60 insertions(+), 36 deletions(-) delete mode 100755 process_config.sh diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift index 1de08025c..20bca68f3 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -15,6 +15,7 @@ public protocol ConfigProtocol { var whatsNewEnabled: Bool { get } var appStoreLink: String { get } var agreementConfig: AgreementConfigProtocol { get } + var firebaseConfig: FirebaseConfigProtocol { get } } public enum TokenType: String { @@ -43,11 +44,11 @@ public class Config { private var properties: [String: Any] = [:] - init(properties: [String: Any] = [:]) { + internal init(properties: [String: Any] = [:]) { self.properties = properties } - convenience init() { + internal convenience init() { self.init(properties: [:]) loadConfigPlist() } @@ -58,7 +59,6 @@ public class Config { let dict = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { return } properties = dict - print(properties) } internal subscript(key: String) -> Any? { diff --git a/Core/Core/Configuration/Combine/Config/ConfigTests.swift b/Core/Core/Configuration/Combine/Config/ConfigTests.swift index 1c40d26da..e4dd8bf31 100644 --- a/Core/Core/Configuration/Combine/Config/ConfigTests.swift +++ b/Core/Core/Configuration/Combine/Config/ConfigTests.swift @@ -69,7 +69,6 @@ class ConfigTests: XCTestCase { XCTAssertEqual(config.firebaseConfig.reversedClientID, "testReversedClientID") XCTAssertEqual(config.firebaseConfig.storageBucket, "testStorageBucket") XCTAssertEqual(config.firebaseConfig.isAnalyticsSourceFirebase, true) - XCTAssertEqual(config.firebaseConfig.cloudMessagingEnabled, true) + XCTAssertEqual(config.firebaseConfig.cloudMessagingEnabled, true) } - } diff --git a/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift b/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift index 11c70a3c7..fc4ff53dc 100644 --- a/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift +++ b/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift @@ -8,6 +8,23 @@ import Foundation import FirebaseCore +public protocol FirebaseConfigProtocol { + var enabled: Bool { get } + var cloudMessagingEnabled: Bool { get } + var apiKey: String? { get } + var bundleID: String? { get } + var clientID: String? { get } + var databaseURL: String? { get } + var gcmSenderID: String? { get } + var googleAppID: String? { get } + var projectID: String? { get } + var reversedClientID: String? { get } + var storageBucket: String? { get } + var isAnalyticsSourceSegment: Bool { get } + var isAnalyticsSourceFirebase: Bool { get } + var firebaseOptions: FirebaseOptions? { get } +} + private enum FirebaseKeys: String { case enabled = "ENABLED" case analyticsSource = "ANALYTICS_SOURCE" @@ -29,7 +46,7 @@ enum AnalyticsSource: String { case none } -public class FirebaseConfig: NSObject { +public class FirebaseConfig: NSObject, FirebaseConfigProtocol { public var enabled: Bool = false public var cloudMessagingEnabled: Bool = false public let apiKey: String? @@ -99,7 +116,7 @@ public class FirebaseConfig: NSObject { private let key = "FIREBASE" extension Config { - public var firebaseConfig: FirebaseConfig { + public var firebaseConfig: FirebaseConfigProtocol { return FirebaseConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) } } diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index a5e860417..c51caba45 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -331,7 +331,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "case $CONFIGURATION in\n \"DebugDev\" | \"ReleaseDev\" )\n googleAppID=$(grep -A 4 'case .debugDev, .releaseDev:' ${PROJECT_DIR}/${TARGET_NAME}/Environment.swift | grep 'googleAppID:' | awk -F'\"' '{print $2}')\n ;;\n \"DebugStage\" | \"ReleaseStage\" )\n googleAppID=$(grep -A 4 'case .debugStage, .releaseStage:' ${PROJECT_DIR}/${TARGET_NAME}/Environment.swift | grep 'googleAppID:' | awk -F'\"' '{print $2}')\n ;;\n \"DebugProd\" | \"RelesaseProd\" )\n googleAppID=$(grep -A 4 'case .debugProd, .releaseProd:' ${PROJECT_DIR}/${TARGET_NAME}/Environment.swift | grep 'googleAppID:' | awk -F'\"' '{print $2}')\n ;;\n *)\n echo \"Unknown configuration\"\n ;;\nesac\n\nif [ -z \"$googleAppID\" ]\nthen\n echo \"GoogleAppID is empty. The FirebaseCrashlytics script will be skipped.\"\nelse\n \"${PODS_ROOT}/FirebaseCrashlytics/run\" --app-id \"$googleAppID\" -p ios ${DWARF_DSYM_FOLDER_PATH}\nfi\n"; + shellScript = "plistPath=\"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/GoogleService-Info.plist\"\n\ngoogleAppID=$(/usr/libexec/PlistBuddy -c \"Print :GOOGLE_APP_ID\" \"$plistPath\")\n\nif [ -z \"$googleAppID\" ]\nthen\n echo \"GoogleAppID is empty. The FirebaseCrashlytics script will be skipped.\"\nelse\n \"${PODS_ROOT}/FirebaseCrashlytics/run\" --app-id \"$googleAppID\" -p ios \"${DWARF_DSYM_FOLDER_PATH}\"\nfi\n"; }; 0770DE2328D08647006D8A5D /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; @@ -375,6 +375,7 @@ }; DB97C0542B002EF00035C36F /* Process Config */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -389,7 +390,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n./process_config.sh\n"; + shellScript = "#!/bin/bash\n\nVENV_PATH=\"${SRCROOT}/venv\"\n\nif [ ! -d \"$VENV_PATH\" ]; then\n /usr/bin/python3 -m venv \"$VENV_PATH\"\nfi\n\nsource \"$VENV_PATH/bin/activate\"\n\npip install --upgrade pip\npip install PyYAML\n\npython process_config.py \"$CONFIGURATION\"\n\ndeactivate\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 1cbcc0ed7..121f6a51e 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -31,8 +31,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - if Config.shared.firebaseConfig.enabled { - FirebaseApp.configure() + if Config.shared.firebaseConfig.enabled, + let configuration = Config.shared.firebaseConfig.firebaseOptions { + FirebaseApp.configure(options: configuration) Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) } diff --git a/process_config.py b/process_config.py index 72ca9d35f..e9b3515af 100644 --- a/process_config.py +++ b/process_config.py @@ -203,21 +203,43 @@ def update_info_plist(self, plist_data, plist_path): print(f"Error reading or writing plist file: {e}") sys.exit(1) +def parse_yaml(file_path): + try: + with open(file_path, 'r') as file: + return yaml.safe_load(file) + except Exception as e: + print(f"Error opening or reading the file '{file_path}': {e}") + sys.exit(1) + def main(): - def parse_yaml(file_path): - try: - with open(file_path, 'r') as file: - return yaml.safe_load(file) - except Exception: - print(f"Error opening or reading the file '{file_path}', No config file was written.") - sys.exit(0) + if len(sys.argv) < 2: + print("Configuration not provided. Using empty configuration.") + configuration = "" + else: + configuration = sys.argv[1] + print(f"Running with configuration: {configuration}") yaml_path = 'config.yaml' yaml_files = ['shared.yaml', 'ios.yaml'] - + config_data = parse_yaml(yaml_path) directory = config_data.get('directory') - config_name = config_data.get('config') + + config_name = "" + + if configuration: + if configuration in ["DebugDev", "ReleaseDev"]: + config_name = "prod_test" + print(f"Processing for Development environment using config name {config_name}") + elif configuration in ["DebugStage", "ReleaseStage"]: + config_name = "stage" + print(f"Processing for Staging environment using config name {config_name}") + elif configuration in ["DebugProd", "ReleaseProd"]: + config_name = "prod" + print(f"Processing for Production environment using config name {config_name}") + else: + config_name = config_data.get('config') + print(f'directory: {directory}') print(f'config: {config_name}') diff --git a/process_config.sh b/process_config.sh deleted file mode 100755 index 8795e2676..000000000 --- a/process_config.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -VENV_PATH="${SRCROOT}/venv" - -if [ ! -d "$VENV_PATH" ]; then - /usr/bin/python3 -m venv "$VENV_PATH" -fi - -source "$VENV_PATH/bin/activate" - -pip install --upgrade pip -pip install PyYAML - -python process_config.py - -deactivate From bdfe73af91cf50d501a5806947973839091626bf Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Tue, 14 Nov 2023 16:36:45 +0500 Subject: [PATCH 03/12] chore: update implementation to provide config mappings based on Xcode scheme and a default config --- .gitignore | 1 - Core/Core.xcodeproj/project.pbxproj | 4 + .../Combine/Config/AgreementConfig.swift | 9 +- .../Configuration/Combine/Config/Config.swift | 13 +-- .../Combine/Config/FeaturesConfig.swift | 28 ++++++ .../Combine/Config/FirebaseConfig.swift | 21 +---- OpenEdX/AppDelegate.swift | 4 +- OpenEdX/RouteController.swift | 2 +- OpenEdX/Router.swift | 2 +- .../Presentation/Profile/ProfileView.swift | 4 +- config.yaml | 5 ++ default_config/default/config.yaml | 4 + default_config/default/ios.yaml | 1 + default_config/default/shared.yaml | 3 + process_config.py | 86 ++++++++++--------- 15 files changed, 105 insertions(+), 82 deletions(-) create mode 100644 Core/Core/Configuration/Combine/Config/FeaturesConfig.swift create mode 100644 config.yaml create mode 100644 default_config/default/config.yaml create mode 100644 default_config/default/ios.yaml create mode 100644 default_config/default/shared.yaml diff --git a/.gitignore b/.gitignore index 1239ac3d8..c6c0df98a 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,5 @@ xcode-frameworks vendor/ .bundle/ -config.yaml venv/ Podfile.lock diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 41edbea78..29c01870d 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -117,6 +117,7 @@ DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */; }; DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */; }; DBF6F2482B01E20A0098414B /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2472B01E20A0098414B /* ConfigTests.swift */; }; + DBF6F24A2B0380E00098414B /* FeaturesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2492B0380E00098414B /* FeaturesConfig.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -249,6 +250,7 @@ DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConfig.swift; sourceTree = ""; }; DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementConfig.swift; sourceTree = ""; }; DBF6F2472B01E20A0098414B /* ConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; + DBF6F2492B0380E00098414B /* FeaturesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesConfig.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -575,6 +577,7 @@ children = ( 0727876F28D23411002E9142 /* Config.swift */, DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, + DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, DBF6F2472B01E20A0098414B /* ConfigTests.swift */, ); @@ -853,6 +856,7 @@ 02F6EF3B28D9B8EC00835477 /* CourseCellView.swift in Sources */, 023A1138291432FD00D0D354 /* FieldConfiguration.swift in Sources */, 024D865E28F02C6B0077E0A0 /* WebView.swift in Sources */, + DBF6F24A2B0380E00098414B /* FeaturesConfig.swift in Sources */, 02F164372902A9EB0090DDEF /* StringExtension.swift in Sources */, 0231CDBE2922422D00032416 /* CSSInjector.swift in Sources */, 0236961928F9A26900EEF206 /* AuthRepository.swift in Sources */, diff --git a/Core/Core/Configuration/Combine/Config/AgreementConfig.swift b/Core/Core/Configuration/Combine/Config/AgreementConfig.swift index fdf293abe..46059500e 100644 --- a/Core/Core/Configuration/Combine/Config/AgreementConfig.swift +++ b/Core/Core/Configuration/Combine/Config/AgreementConfig.swift @@ -7,17 +7,12 @@ import Foundation -public protocol AgreementConfigProtocol { - var privacyPolicyURL: URL? { get } - var tosURL: URL? { get } -} - private enum AgreementKeys: String { case privacyPolicyURL = "PRIVACY_POLICY_URL" case tosURL = "TOS_URL" } -public class AgreementConfig: NSObject, AgreementConfigProtocol { +public class AgreementConfig: NSObject { public var privacyPolicyURL: URL? public var tosURL: URL? @@ -30,7 +25,7 @@ public class AgreementConfig: NSObject, AgreementConfigProtocol { private let key = "AGREEMENT_URLS" extension Config { - public var agreementConfig: AgreementConfigProtocol { + public var agreement: AgreementConfig { return AgreementConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) } } diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift index 20bca68f3..241a4c7fd 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -12,10 +12,10 @@ public protocol ConfigProtocol { var oAuthClientId: String { get } var tokenType: TokenType { get } var feedbackEmail: String { get } - var whatsNewEnabled: Bool { get } var appStoreLink: String { get } - var agreementConfig: AgreementConfigProtocol { get } - var firebaseConfig: FirebaseConfigProtocol { get } + var agreement: AgreementConfig { get } + var firebase: FirebaseConfig { get } + var features: FeaturesConfig { get } } public enum TokenType: String { @@ -26,12 +26,11 @@ public enum TokenType: String { private enum ConfigKeys: String { case baseURL = "API_HOST_URL" case oAuthClientID = "OAUTH_CLIENT_ID" - case feedbackEmailAddress = "FEEDBACK_EMAIL_ADDRESS" case tokenType = "TOKEN_TYPE" + case feedbackEmailAddress = "FEEDBACK_EMAIL_ADDRESS" case environmentDisplayName = "ENVIRONMENT_DISPLAY_NAME" case platformName = "PLATFORM_NAME" case organizationCode = "ORGANIZATION_CODE" - case whatsnewEnabled = "WHATS_NEW_ENABLED" case appstoreID = "APP_STORE_ID" } @@ -116,10 +115,6 @@ extension Config: ConfigProtocol { return string(for: ConfigKeys.feedbackEmailAddress.rawValue) ?? "" } - public var whatsNewEnabled: Bool { - return bool(for: ConfigKeys.whatsnewEnabled.rawValue) - } - private var appStoreId: String { return string(for: ConfigKeys.appstoreID.rawValue) ?? "0000000000" } diff --git a/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift b/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift new file mode 100644 index 000000000..2d5cac346 --- /dev/null +++ b/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift @@ -0,0 +1,28 @@ +// +// FeaturesConfig.swift +// Core +// +// Created by Muhammad Umer on 11/14/23. +// + +import Foundation + +private enum FeaturesKeys: String { + case whatNewEnabled = "WHATS_NEW_ENABLED" +} + +public class FeaturesConfig: NSObject { + public var whatNewEnabled: Bool + + init(dictionary: [String: AnyObject]) { + whatNewEnabled = dictionary[FeaturesKeys.whatNewEnabled.rawValue] as? Bool ?? false + super.init() + } +} + +private let key = "FEATURES" +extension Config { + public var features: FeaturesConfig { + return FeaturesConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift b/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift index fc4ff53dc..d981ff3ce 100644 --- a/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift +++ b/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift @@ -8,23 +8,6 @@ import Foundation import FirebaseCore -public protocol FirebaseConfigProtocol { - var enabled: Bool { get } - var cloudMessagingEnabled: Bool { get } - var apiKey: String? { get } - var bundleID: String? { get } - var clientID: String? { get } - var databaseURL: String? { get } - var gcmSenderID: String? { get } - var googleAppID: String? { get } - var projectID: String? { get } - var reversedClientID: String? { get } - var storageBucket: String? { get } - var isAnalyticsSourceSegment: Bool { get } - var isAnalyticsSourceFirebase: Bool { get } - var firebaseOptions: FirebaseOptions? { get } -} - private enum FirebaseKeys: String { case enabled = "ENABLED" case analyticsSource = "ANALYTICS_SOURCE" @@ -46,7 +29,7 @@ enum AnalyticsSource: String { case none } -public class FirebaseConfig: NSObject, FirebaseConfigProtocol { +public class FirebaseConfig: NSObject { public var enabled: Bool = false public var cloudMessagingEnabled: Bool = false public let apiKey: String? @@ -116,7 +99,7 @@ public class FirebaseConfig: NSObject, FirebaseConfigProtocol { private let key = "FIREBASE" extension Config { - public var firebaseConfig: FirebaseConfigProtocol { + public var firebase: FirebaseConfig { return FirebaseConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) } } diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 121f6a51e..ba4a256f7 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -31,8 +31,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - if Config.shared.firebaseConfig.enabled, - let configuration = Config.shared.firebaseConfig.firebaseOptions { + if Config.shared.firebase.enabled, + let configuration = Config.shared.firebase.firebaseOptions { FirebaseApp.configure(options: configuration) Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) } diff --git a/OpenEdX/RouteController.swift b/OpenEdX/RouteController.swift index 0867d131c..7478fac65 100644 --- a/OpenEdX/RouteController.swift +++ b/OpenEdX/RouteController.swift @@ -56,7 +56,7 @@ class RouteController: UIViewController { let viewModel = WhatsNewViewModel(storage: storage) let shouldShowWhatsNew = viewModel.shouldShowWhatsNew() - if shouldShowWhatsNew && config.whatsNewEnabled { + if shouldShowWhatsNew && config.features.whatNewEnabled { if let jsonVersion = viewModel.getVersion() { storage.whatsNewVersion = jsonVersion } diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index 4923044db..0286805bb 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -67,7 +67,7 @@ public class Router: AuthorizationRouter, let whatsNew = WhatsNewView(router: Container.shared.resolve(WhatsNewRouter.self)!, viewModel: viewModel) let shouldShowWhatsNew = viewModel.shouldShowWhatsNew() - if shouldShowWhatsNew && config.whatsNewEnabled { + if shouldShowWhatsNew && config.features.whatNewEnabled { if let jsonVersion = viewModel.getVersion() { storage.whatsNewVersion = jsonVersion } diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index 5e3756bd2..5d8dd18d5 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -118,7 +118,7 @@ public struct ProfileView: View { .foregroundColor(Theme.Colors.textSecondary) } - if let tos = viewModel.config.agreementConfig.tosURL { + if let tos = viewModel.config.agreement.tosURL { Button(action: { viewModel.trackCookiePolicyClicked() UIApplication.shared.open(tos) @@ -136,7 +136,7 @@ public struct ProfileView: View { .foregroundColor(Theme.Colors.textSecondary) } - if let privacy = viewModel.config.agreementConfig.privacyPolicyURL { + if let privacy = viewModel.config.agreement.privacyPolicyURL { Button(action: { viewModel.trackPrivacyPolicyClicked() UIApplication.shared.open(privacy) diff --git a/config.yaml b/config.yaml new file mode 100644 index 000000000..c21f3b1cb --- /dev/null +++ b/config.yaml @@ -0,0 +1,5 @@ +config_directory: '../config' +config_mapping: + dev: 'prod_test' + prod: 'prod' + stage: 'stage' \ No newline at end of file diff --git a/default_config/default/config.yaml b/default_config/default/config.yaml new file mode 100644 index 000000000..714452183 --- /dev/null +++ b/default_config/default/config.yaml @@ -0,0 +1,4 @@ +ios: + files: + - shared.yaml + - ios.yaml diff --git a/default_config/default/ios.yaml b/default_config/default/ios.yaml new file mode 100644 index 000000000..543813eed --- /dev/null +++ b/default_config/default/ios.yaml @@ -0,0 +1 @@ +OAUTH_CLIENT_ID: '' diff --git a/default_config/default/shared.yaml b/default_config/default/shared.yaml new file mode 100644 index 000000000..dcf337896 --- /dev/null +++ b/default_config/default/shared.yaml @@ -0,0 +1,3 @@ +API_HOST_URL: 'http://localhost:8000' +ENVIRONMENT_DISPLAY_NAME: 'Localhost' +FEEDBACK_EMAIL_ADDRESS: 'support@example.com' diff --git a/process_config.py b/process_config.py index e9b3515af..e3ce6ed4e 100644 --- a/process_config.py +++ b/process_config.py @@ -201,7 +201,7 @@ def update_info_plist(self, plist_data, plist_path): sys.exit(1) except Exception as e: print(f"Error reading or writing plist file: {e}") - sys.exit(1) + sys.exit(1) def parse_yaml(file_path): try: @@ -211,60 +211,66 @@ def parse_yaml(file_path): print(f"Error opening or reading the file '{file_path}': {e}") sys.exit(1) +def get_configuration_name(configuration, config_mapping): + if configuration in ["DebugDev", "ReleaseDev"]: + return config_mapping.get("dev") + elif configuration in ["DebugStage", "ReleaseStage"]: + return config_mapping.get("stage") + elif configuration in ["DebugProd", "ReleaseProd"]: + return config_mapping.get("prod") + else: + return None + +def process_plist_files(configuration_manager, plist_manager, config): + firebase_info_plist_path = plist_manager.get_firebase_config_path() + app_info_plist_path = plist_manager.get_app_info_plist_path() + + plist = {} + configuration_manager.add_firebase_config(config, plist, firebase_info_plist_path) + configuration_manager.add_facebook_config(config, plist) + configuration_manager.add_google_config(config, plist) + configuration_manager.add_microsoft_config(config, plist) + configuration_manager.update_info_plist(plist, app_info_plist_path) + + bundle_config_path = plist_manager.get_bundle_config_path() + config_plist = plist_manager.yaml_to_plist() + plist_manager.write_to_plist_file(config_plist, bundle_config_path) + def main(): if len(sys.argv) < 2: print("Configuration not provided. Using empty configuration.") configuration = "" else: configuration = sys.argv[1] - print(f"Running with configuration: {configuration}") - - yaml_path = 'config.yaml' - yaml_files = ['shared.yaml', 'ios.yaml'] - - config_data = parse_yaml(yaml_path) - directory = config_data.get('directory') + print(f"Running with configuration: {configuration}") - config_name = "" - - if configuration: - if configuration in ["DebugDev", "ReleaseDev"]: - config_name = "prod_test" - print(f"Processing for Development environment using config name {config_name}") - elif configuration in ["DebugStage", "ReleaseStage"]: - config_name = "stage" - print(f"Processing for Staging environment using config name {config_name}") - elif configuration in ["DebugProd", "ReleaseProd"]: - config_name = "prod" - print(f"Processing for Production environment using config name {config_name}") - else: - config_name = config_data.get('config') + config_data = parse_yaml('config.yaml') - print(f'directory: {directory}') - print(f'config: {config_name}') + config_directory = config_data.get('config_directory', "") + config_mapping = config_data.get('config_mapping', {}) - config_dir = os.path.join(directory, config_name) - plist_manager = PlistManager(config_dir, yaml_files) + config_name = get_configuration_name(configuration, config_mapping) + + if config_name is None: + print("Using default directory") + config_directory = 'default_config' + config_name = "default" + + path = os.path.join(config_directory, config_name, "config.yaml") + print(config_directory) + print(path) + data = parse_yaml(path) + ios_files = data.get('ios').get('files') + + plist_manager = PlistManager(os.path.join(config_directory, config_name), ios_files) config = plist_manager.load_config() if not config: print("Config is empty. Skipping") else: configuration_manager = ConfigurationManager(plist_manager) - info_plist_path = plist_manager.get_info_plist_path() - firebase_info_plist_path = plist_manager.get_firebase_config_path() - app_info_plist_path = plist_manager.get_app_info_plist_path() - - plist = {} - configuration_manager.add_firebase_config(config, plist, firebase_info_plist_path) - configuration_manager.add_facebook_config(config, plist) - configuration_manager.add_google_config(config, plist) - configuration_manager.add_microsoft_config(config, plist) - configuration_manager.update_info_plist(plist, app_info_plist_path) - - bundle_config_path = plist_manager.get_bundle_config_path() - config_plist = plist_manager.yaml_to_plist() - plist_manager.write_to_plist_file(config_plist, bundle_config_path) + process_plist_files(configuration_manager, plist_manager, config) if __name__ == "__main__": main() + # test() From 7b09d047af8827a1f6cd7a9e9e440fb2464c94dc Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Tue, 14 Nov 2023 16:46:27 +0500 Subject: [PATCH 04/12] chore: update config test cases --- .../Configuration/Combine/Config/Config.swift | 6 ++- .../Combine/Config/ConfigTests.swift | 40 +++++++++++-------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift index 241a4c7fd..b4aab68b7 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -131,8 +131,10 @@ public class ConfigMock: Config { "API_HOST_URL": "https://www.example.com", "OAUTH_CLIENT_ID": "oauth_client_id", "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", - "TOKEN_TYPE": TokenType.jwt.rawValue, - "WHATS_NEW_ENABLED": false, + "TOKEN_TYPE": "JWT", + "FEATURES": [ + "WHATS_NEW_ENABLED": false + ], "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", "TOS_URL": "https://www.example.com/tos" diff --git a/Core/Core/Configuration/Combine/Config/ConfigTests.swift b/Core/Core/Configuration/Combine/Config/ConfigTests.swift index e4dd8bf31..b450c6fc5 100644 --- a/Core/Core/Configuration/Combine/Config/ConfigTests.swift +++ b/Core/Core/Configuration/Combine/Config/ConfigTests.swift @@ -15,7 +15,9 @@ class ConfigTests: XCTestCase { "OAUTH_CLIENT_ID": "oauth_client_id", "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", "TOKEN_TYPE": "JWT", - "WHATS_NEW_ENABLED": false, + "FEATURES": [ + "WHATS_NEW_ENABLED": false + ], "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", "TOS_URL": "https://www.example.com/tos" @@ -45,30 +47,36 @@ class ConfigTests: XCTestCase { XCTAssertEqual(config.oAuthClientId, "oauth_client_id") XCTAssertEqual(config.feedbackEmail, "example@mail.com") XCTAssertEqual(config.tokenType, TokenType.jwt) - XCTAssertFalse(config.whatsNewEnabled) + XCTAssertFalse(config.features.whatNewEnabled) + } + + func testFeaturesConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertFalse(config.features.whatNewEnabled) } func testAgreementConfigInitialization() { let config = Config(properties: properties) - XCTAssertEqual(config.agreementConfig.privacyPolicyURL, URL(string: "https://www.example.com/privacy")) - XCTAssertEqual(config.agreementConfig.tosURL, URL(string: "https://www.example.com/tos")) + XCTAssertEqual(config.agreement.privacyPolicyURL, URL(string: "https://www.example.com/privacy")) + XCTAssertEqual(config.agreement.tosURL, URL(string: "https://www.example.com/tos")) } func testFirebaseConfigInitialization() { let config = Config(properties: properties) - XCTAssertTrue(config.firebaseConfig.enabled) - XCTAssertEqual(config.firebaseConfig.apiKey, "testApiKey") - XCTAssertEqual(config.firebaseConfig.bundleID, "testBundleID") - XCTAssertEqual(config.firebaseConfig.clientID, "testClientID") - XCTAssertEqual(config.firebaseConfig.databaseURL, "https://test.database.url") - XCTAssertEqual(config.firebaseConfig.gcmSenderID, "testGCMSenderID") - XCTAssertEqual(config.firebaseConfig.googleAppID, "testGoogleAppID") - XCTAssertEqual(config.firebaseConfig.projectID, "testProjectID") - XCTAssertEqual(config.firebaseConfig.reversedClientID, "testReversedClientID") - XCTAssertEqual(config.firebaseConfig.storageBucket, "testStorageBucket") - XCTAssertEqual(config.firebaseConfig.isAnalyticsSourceFirebase, true) - XCTAssertEqual(config.firebaseConfig.cloudMessagingEnabled, true) + XCTAssertTrue(config.firebase.enabled) + XCTAssertEqual(config.firebase.apiKey, "testApiKey") + XCTAssertEqual(config.firebase.bundleID, "testBundleID") + XCTAssertEqual(config.firebase.clientID, "testClientID") + XCTAssertEqual(config.firebase.databaseURL, "https://test.database.url") + XCTAssertEqual(config.firebase.gcmSenderID, "testGCMSenderID") + XCTAssertEqual(config.firebase.googleAppID, "testGoogleAppID") + XCTAssertEqual(config.firebase.projectID, "testProjectID") + XCTAssertEqual(config.firebase.reversedClientID, "testReversedClientID") + XCTAssertEqual(config.firebase.storageBucket, "testStorageBucket") + XCTAssertEqual(config.firebase.isAnalyticsSourceFirebase, true) + XCTAssertEqual(config.firebase.cloudMessagingEnabled, true) } } From 7b07d05322f9eaa634479ccf00cc1f05ff498886 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Wed, 15 Nov 2023 13:09:52 +0500 Subject: [PATCH 05/12] chore: make pass dictionary to features config --- .../Configuration/Combine/Config/Config.swift | 10 +++--- .../Combine/Config/ConfigTests.swift | 35 ++++++++----------- .../Combine/Config/FeaturesConfig.swift | 5 ++- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift index b4aab68b7..c4a5c0a84 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -41,7 +41,7 @@ public class Config { return config }() - private var properties: [String: Any] = [:] + internal var properties: [String: Any] = [:] internal init(properties: [String: Any] = [:]) { self.properties = properties @@ -127,14 +127,12 @@ extension Config: ConfigProtocol { // Mark - For testing and SwiftUI preview #if DEBUG public class ConfigMock: Config { - private let properties: [String: Any] = [ + private let config: [String: Any] = [ "API_HOST_URL": "https://www.example.com", "OAUTH_CLIENT_ID": "oauth_client_id", "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", "TOKEN_TYPE": "JWT", - "FEATURES": [ - "WHATS_NEW_ENABLED": false - ], + "WHATS_NEW_ENABLED": false, "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", "TOS_URL": "https://www.example.com/tos" @@ -142,7 +140,7 @@ public class ConfigMock: Config { ] public init() { - super.init(properties: properties) + super.init(properties: config) } } #endif diff --git a/Core/Core/Configuration/Combine/Config/ConfigTests.swift b/Core/Core/Configuration/Combine/Config/ConfigTests.swift index b450c6fc5..f67e2ab10 100644 --- a/Core/Core/Configuration/Combine/Config/ConfigTests.swift +++ b/Core/Core/Configuration/Combine/Config/ConfigTests.swift @@ -15,29 +15,24 @@ class ConfigTests: XCTestCase { "OAUTH_CLIENT_ID": "oauth_client_id", "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", "TOKEN_TYPE": "JWT", - "FEATURES": [ - "WHATS_NEW_ENABLED": false - ], + "WHATS_NEW_ENABLED": false, "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", "TOS_URL": "https://www.example.com/tos" ], - "FIREBASE": firebaseProperties - ] - - private lazy var firebaseProperties: [String: Any] = [ - "ENABLED": true, - "API_KEY": "testApiKey", - "BUNDLE_ID": "testBundleID", - "CLIENT_ID": "testClientID", - "DATABASE_URL": "https://test.database.url", - "GCM_SENDER_ID": "testGCMSenderID", - "GOOGLE_APP_ID": "testGoogleAppID", - "PROJECT_ID": "testProjectID", - "REVERSED_CLIENT_ID": "testReversedClientID", - "STORAGE_BUCKET": "testStorageBucket", - "ANALYTICS_SOURCE": "firebase", - "CLOUD_MESSAGING_ENABLED": true + "FIREBASE": [ + "ENABLED": true, + "API_KEY": "testApiKey", + "BUNDLE_ID": "testBundleID", + "CLIENT_ID": "testClientID", + "DATABASE_URL": "https://test.database.url", + "GCM_SENDER_ID": "testGCMSenderID", + "GOOGLE_APP_ID": "testGoogleAppID", + "PROJECT_ID": "testProjectID", + "REVERSED_CLIENT_ID": "testReversedClientID", + "STORAGE_BUCKET": "testStorageBucket", + "ANALYTICS_SOURCE": "firebase", + "CLOUD_MESSAGING_ENABLED": true] ] func testConfigInitialization() { @@ -46,7 +41,7 @@ class ConfigTests: XCTestCase { XCTAssertEqual(config.baseURL.absoluteString, "https://www.example.com") XCTAssertEqual(config.oAuthClientId, "oauth_client_id") XCTAssertEqual(config.feedbackEmail, "example@mail.com") - XCTAssertEqual(config.tokenType, TokenType.jwt) + XCTAssertEqual(config.tokenType.rawValue, "JWT") XCTAssertFalse(config.features.whatNewEnabled) } diff --git a/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift b/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift index 2d5cac346..eb6c6227f 100644 --- a/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift +++ b/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift @@ -14,15 +14,14 @@ private enum FeaturesKeys: String { public class FeaturesConfig: NSObject { public var whatNewEnabled: Bool - init(dictionary: [String: AnyObject]) { + init(dictionary: [String: Any]) { whatNewEnabled = dictionary[FeaturesKeys.whatNewEnabled.rawValue] as? Bool ?? false super.init() } } -private let key = "FEATURES" extension Config { public var features: FeaturesConfig { - return FeaturesConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) + return FeaturesConfig(dictionary: properties) } } From 9e8ea219e5f17f18c5be27753f21126c9da73993 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Fri, 17 Nov 2023 15:16:47 +0500 Subject: [PATCH 06/12] chore: update process_config.py and releated files --- .gitignore | 1 + OpenEdX.xcodeproj/project.pbxproj | 2 +- OpenEdX/Info.plist | 2 + config.yaml | 5 - config_script/process_config.py | 307 ++++++++++++++++++ default_config/default/config.yaml | 4 - default_config/default/ios.yaml | 1 - .../{default/shared.yaml => dev/config.yaml} | 1 + default_config/dev/file_mappings.yaml | 3 + default_config/prod/config.yaml | 4 + default_config/prod/file_mappings.yaml | 3 + default_config/stage/config.yaml | 4 + default_config/stage/file_mappings.yaml | 3 + process_config.py | 23 +- 14 files changed, 349 insertions(+), 14 deletions(-) delete mode 100644 config.yaml create mode 100644 config_script/process_config.py delete mode 100644 default_config/default/config.yaml delete mode 100644 default_config/default/ios.yaml rename default_config/{default/shared.yaml => dev/config.yaml} (85%) create mode 100644 default_config/dev/file_mappings.yaml create mode 100644 default_config/prod/config.yaml create mode 100644 default_config/prod/file_mappings.yaml create mode 100644 default_config/stage/config.yaml create mode 100644 default_config/stage/file_mappings.yaml diff --git a/.gitignore b/.gitignore index c6c0df98a..c8c1be1a6 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,4 @@ vendor/ venv/ Podfile.lock +config_settings.yaml diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index c51caba45..069226267 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -390,7 +390,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/bash\n\nVENV_PATH=\"${SRCROOT}/venv\"\n\nif [ ! -d \"$VENV_PATH\" ]; then\n /usr/bin/python3 -m venv \"$VENV_PATH\"\nfi\n\nsource \"$VENV_PATH/bin/activate\"\n\npip install --upgrade pip\npip install PyYAML\n\npython process_config.py \"$CONFIGURATION\"\n\ndeactivate\n"; + shellScript = "#!/bin/bash\n\nVENV_PATH=\"${SRCROOT}/venv\"\n\nif [ ! -d \"$VENV_PATH\" ]; then\n /usr/bin/python3 -m venv \"$VENV_PATH\"\nfi\n\nsource \"$VENV_PATH/bin/activate\"\n\npip install --upgrade pip\npip install PyYAML\n\nscheme_mapping='{\n \"prod\": [\"ReleaseProd\", \"DebugProd\"],\n \"stage\": [\"ReleaseStage\", \"DebugStage\"],\n \"dev\": [\"ReleaseDev\", \"DebugDev\"]\n}'\n\npython config_script/process_config.py \"$CONFIGURATION\" \"$scheme_mapping\"\n\ndeactivate\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index b94522839..9fadbae9c 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -4,6 +4,8 @@ Configuration $(CONFIGURATION) + ExampleKey + ExampleValue FirebaseAppDelegateProxyEnabled FirebaseAutomaticScreenReportingEnabled diff --git a/config.yaml b/config.yaml deleted file mode 100644 index c21f3b1cb..000000000 --- a/config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -config_directory: '../config' -config_mapping: - dev: 'prod_test' - prod: 'prod' - stage: 'stage' \ No newline at end of file diff --git a/config_script/process_config.py b/config_script/process_config.py new file mode 100644 index 000000000..5d3f6ad91 --- /dev/null +++ b/config_script/process_config.py @@ -0,0 +1,307 @@ +import plistlib +import os +import yaml +from pathlib import Path +import sys +import json + +class PlistManager: + def __init__(self, config_dir, config_files): + self.config_dir = config_dir + self.config_files = config_files + + def get_config_paths(self): + return [Path(self.config_dir) / config_name for config_name in self.config_files] + + def get_product_name(self): + return os.getenv('PRODUCT_NAME') + + def get_bundle_identifier(self): + return os.getenv('PRODUCT_BUNDLE_IDENTIFIER') + + def get_info_plist_path(self): + return os.getenv('INFOPLIST_PATH') + + def get_wrapper_name(self): + return os.getenv('WRAPPER_NAME') + + def get_built_products_path(self): + return os.getenv('BUILT_PRODUCTS_DIR') + + def get_bundle_config_path(self): + return os.path.join(self.get_built_products_path(), self.get_wrapper_name(), 'config.plist') + + def get_app_info_plist_path(self): + built_products_path = self.get_built_products_path() + info_plist_path = self.get_info_plist_path() + + if built_products_path and info_plist_path: + return os.path.join(built_products_path, info_plist_path) + else: + return None + + def get_firebase_info_plist_path(self): + built_products_path = self.get_built_products_path() + wrapper_name = self.get_wrapper_name() + + if built_products_path and wrapper_name: + return os.path.join(built_products_path, wrapper_name, 'GoogleService-Info.plist') + else: + print("The BUILT_PRODUCTS_DIR or WRAPPER_NAME environment variable is not set.") + return None + + def get_firebase_config_path(self): + built_products_path = self.get_built_products_path() + wrapper_name = self.get_wrapper_name() + + if built_products_path and wrapper_name: + return os.path.join(built_products_path, wrapper_name, 'firebase.plist') + else: + print("The BUILT_PRODUCTS_DIR or WRAPPER_NAME environment variable is not set.") + return None + + def load_config(self): + properties = {} + + for path in self.get_config_paths(): + try: + with open(path, 'r') as file: + dict = yaml.safe_load(file) + if dict is not None: + properties.update(dict) + except FileNotFoundError: + print(f"{path} not found. Skipping.") + + return properties + + def yaml_to_plist(self): + plist_data = {} + + for path in self.get_config_paths(): + try: + with open(path, 'r') as file: + yaml_data = yaml.safe_load(file) + if yaml_data is not None: + plist_data.update(yaml_data) + except FileNotFoundError: + print(f"{path} not found. Skipping.") + except yaml.YAMLError as e: + print(f"Error parsing YAML file {path}: {e}") + + return plist_data + + def write_to_plist_file(self, plist, file_path): + file_name = os.path.basename(file_path) + with open(file_path, 'wb') as plist_file: + plistlib.dump(plist, plist_file) + print(f"File {file_name} has been written to {file_path}") + + def print_info_plist_contents(self, plist_path): + if not plist_path: + print(f"Path is not set. {plist_path}") + try: + with open(plist_path, 'rb') as plist_file: + plist_contents = plistlib.load(plist_file) + print(plist_contents) + except Exception as e: + print(f"Error reading plist file: {e}") + + def get_info_plist_contents(self, plist_path): + if not plist_path: + print(f"Path is not set. {plist_path}") + try: + with open(plist_path, 'rb') as plist_file: + plist_contents = plistlib.load(plist_file) + return plist_contents + except Exception as e: + print(f"Error reading plist file: {e}") + return None + + +class ConfigurationManager: + def __init__(self, plist_manager): + self.plist_manager = plist_manager + + def get_environment_variable(self, variable): + return os.getenv(variable) + + def add_url_scheme(self, scheme, plist): + body = { + 'CFBundleTypeRole': 'Editor', + 'CFBundleURLSchemes': scheme + } + existing = plist.get('CFBundleURLTypes', []) + found = any(scheme in entry.get('CFBundleURLSchemes', []) for entry in existing) + + if not found: + existing.append(body) + plist['CFBundleURLTypes'] = existing + + return plist + + def add_firebase_config(self, config, firebase_info_plist_path): + plist = {} + firebase = config.get('FIREBASE', {}) + + if firebase_info_plist_path and firebase: + plist['BUNDLE_ID'] = self.plist_manager.get_bundle_identifier() + plist['API_KEY'] = firebase.get('API_KEY', '') + plist['CLIENT_ID'] = firebase.get('CLIENT_ID', '') + plist['GOOGLE_APP_ID'] = firebase.get('GOOGLE_APP_ID', '') + plist['GCM_SENDER_ID'] = firebase.get('GCM_SENDER_ID', '') + + project_id = firebase.get('PROJECT_ID', '') + if project_id: + plist['PROJECT_ID'] = project_id + plist['STORAGE_BUCKET'] = project_id + '.appspot.com' + plist['DATABASE_URL'] = 'https://' + project_id + '.firebaseio.com' + + reversed_client_id = firebase.get('REVERSED_CLIENT_ID', '') + if reversed_client_id: + plist['REVERSED_CLIENT_ID'] = reversed_client_id + + self.plist_manager.write_to_plist_file(plist, self.plist_manager.get_firebase_info_plist_path()) + else: + print("Firebase config is empty. Skipping") + + def add_facebook_config(self, config, plist): + facebook = config.get('FACEBOOK', {}) + key = facebook.get('FACEBOOK_APP_ID') + client_token = facebook.get('CLIENT_TOKEN') + + if key and client_token: + plist["FacebookAppID"] = key + plist["FacebookClientToken"] = client_token + plist["FacebookDisplayName"] = self.plist_manager.get_product_name() + scheme = ["fb" + key] + self.add_url_scheme(scheme, plist) + + def add_google_config(self, config, plist): + google = config.get('GOOGLE', {}) + key = google.get('GOOGLE_PLUS_KEY') + + if key: + scheme = ['.'.join(reversed(key.split('.')))] + self.add_url_scheme(scheme, plist) + + def add_microsoft_config(self, config, plist): + microsoft = config.get('MICROSOFT', {}) + key = microsoft.get('APP_ID') + + if key: + bundle_identifier = self.plist_manager.get_bundle_identifier() + scheme = ["msauth." + bundle_identifier] + self.add_url_scheme(scheme, plist) + + def update_info_plist(self, plist_data, plist_path): + if not plist_path: + print("Path is not set.") + sys.exit(1) + + try: + with open(plist_path, 'rb') as plist_file: + plist_contents = plistlib.load(plist_file) + + plist_contents.update(plist_data) + + try: + plistlib.dumps(plist_contents) + except Exception as e: + print(f"Error validating plist contents: {e}") + sys.exit(1) + + self.plist_manager.write_to_plist_file(plist_contents, plist_path) + except FileNotFoundError: + print(f"Plist file not found: {plist_path}") + sys.exit(1) + except Exception as e: + print(f"Error reading or writing plist file: {e}") + sys.exit(1) + +def parse_yaml(file_path): + try: + with open(file_path, 'r') as file: + return yaml.safe_load(file) + except Exception as e: + print(f"Unable to open or read the file '{file_path}': {e}") + return None + +CONFIG_SETTINGS_YAML_FILENAME = 'config_settings.yaml' +DEFAULT_CONFIG_PATH = './default_config/' + CONFIG_SETTINGS_YAML_FILENAME +CONFIG_DIRECTORY_NAME = 'config_directory' + +CONFIG_MAPPINGS = 'config_mapping' +MAPPINGS_FILENAME = 'file_mappings.yaml' + +def get_current_config(configuration, scheme_mappings): + for key, values in scheme_mappings.items(): + if configuration in values: + return key + return None + +def process_plist_files(configuration_manager, plist_manager, config): + firebase_info_plist_path = plist_manager.get_firebase_config_path() + info_plist_path = plist_manager.get_app_info_plist_path() + info_plist_content = plist_manager.get_info_plist_contents(info_plist_path) + + configuration_manager.add_firebase_config(config, firebase_info_plist_path) + configuration_manager.add_facebook_config(config, info_plist_content) + configuration_manager.add_google_config(config, info_plist_content) + configuration_manager.add_microsoft_config(config, info_plist_content) + + configuration_manager.update_info_plist(info_plist_content, info_plist_path) + + bundle_config_path = plist_manager.get_bundle_config_path() + config_plist = plist_manager.yaml_to_plist() + plist_manager.write_to_plist_file(config_plist, bundle_config_path) + +def main(configuration, scheme_mappings): + current_config = get_current_config(configuration, scheme_mappings) + + if current_config is None: + print("Config not found in mappings. Exiting.") + sys.exit(1) + + config_settings = parse_yaml(CONFIG_SETTINGS_YAML_FILENAME) + + if not config_settings: + print("Parsing default config.") + config_settings = parse_yaml(DEFAULT_CONFIG_PATH) + + config_directory = config_settings.get(CONFIG_DIRECTORY_NAME) + config_name = config_settings.get(CONFIG_MAPPINGS, {}).get(current_config) + + if config_directory and config_name: + path = os.path.join(config_directory, config_name) + mappings_path = os.path.join(path, MAPPINGS_FILENAME) + data = parse_yaml(mappings_path) + + if data: + ios_files = data.get('ios', {}).get('files', []) + plist_manager = PlistManager(path, ios_files) + config = plist_manager.load_config() + + if config: + configuration_manager = ConfigurationManager(plist_manager) + process_plist_files(configuration_manager, plist_manager, config) + print(f"Config {configuration} parsed and written successfully.") + else: + print("Unable to parse config files") + sys.exit(1) + + else: + print("Files mappings not found") + sys.exit(1) + + else: + print("Config directory or config name is not provided") + sys.exit(1) + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Usage: script.py ") + sys.exit(1) + + configuration = sys.argv[1] + scheme_mappings = json.loads(sys.argv[2]) + main(configuration, scheme_mappings) diff --git a/default_config/default/config.yaml b/default_config/default/config.yaml deleted file mode 100644 index 714452183..000000000 --- a/default_config/default/config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -ios: - files: - - shared.yaml - - ios.yaml diff --git a/default_config/default/ios.yaml b/default_config/default/ios.yaml deleted file mode 100644 index 543813eed..000000000 --- a/default_config/default/ios.yaml +++ /dev/null @@ -1 +0,0 @@ -OAUTH_CLIENT_ID: '' diff --git a/default_config/default/shared.yaml b/default_config/dev/config.yaml similarity index 85% rename from default_config/default/shared.yaml rename to default_config/dev/config.yaml index dcf337896..d7e76817e 100644 --- a/default_config/default/shared.yaml +++ b/default_config/dev/config.yaml @@ -1,3 +1,4 @@ API_HOST_URL: 'http://localhost:8000' ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' +OAUTH_CLIENT_ID: '' diff --git a/default_config/dev/file_mappings.yaml b/default_config/dev/file_mappings.yaml new file mode 100644 index 000000000..86d84fa91 --- /dev/null +++ b/default_config/dev/file_mappings.yaml @@ -0,0 +1,3 @@ +ios: + files: + - config.yaml diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml new file mode 100644 index 000000000..d7e76817e --- /dev/null +++ b/default_config/prod/config.yaml @@ -0,0 +1,4 @@ +API_HOST_URL: 'http://localhost:8000' +ENVIRONMENT_DISPLAY_NAME: 'Localhost' +FEEDBACK_EMAIL_ADDRESS: 'support@example.com' +OAUTH_CLIENT_ID: '' diff --git a/default_config/prod/file_mappings.yaml b/default_config/prod/file_mappings.yaml new file mode 100644 index 000000000..86d84fa91 --- /dev/null +++ b/default_config/prod/file_mappings.yaml @@ -0,0 +1,3 @@ +ios: + files: + - config.yaml diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml new file mode 100644 index 000000000..d7e76817e --- /dev/null +++ b/default_config/stage/config.yaml @@ -0,0 +1,4 @@ +API_HOST_URL: 'http://localhost:8000' +ENVIRONMENT_DISPLAY_NAME: 'Localhost' +FEEDBACK_EMAIL_ADDRESS: 'support@example.com' +OAUTH_CLIENT_ID: '' diff --git a/default_config/stage/file_mappings.yaml b/default_config/stage/file_mappings.yaml new file mode 100644 index 000000000..86d84fa91 --- /dev/null +++ b/default_config/stage/file_mappings.yaml @@ -0,0 +1,3 @@ +ios: + files: + - config.yaml diff --git a/process_config.py b/process_config.py index e3ce6ed4e..e48c43058 100644 --- a/process_config.py +++ b/process_config.py @@ -220,6 +220,19 @@ def get_configuration_name(configuration, config_mapping): return config_mapping.get("prod") else: return None + +def get_configuration_name_two(scheme_mapping, config_mapping): + if scheme_mapping.get('dev'): + return config_mapping.get("dev") + + if configuration in ["dev"]: + return + elif configuration in ["prod"]: + return config_mapping.get("prod") + elif configuration in ["DebugProd", "ReleaseProd"]: + return config_mapping.get("prod") + else: + return None def process_plist_files(configuration_manager, plist_manager, config): firebase_info_plist_path = plist_manager.get_firebase_config_path() @@ -243,7 +256,7 @@ def main(): else: configuration = sys.argv[1] print(f"Running with configuration: {configuration}") - + config_data = parse_yaml('config.yaml') config_directory = config_data.get('config_directory', "") @@ -272,5 +285,9 @@ def main(): process_plist_files(configuration_manager, plist_manager, config) if __name__ == "__main__": - main() - # test() + # main() + path = os.path.join("default_config", "config_settings.yaml") + content = parse_yaml(path) + print(content) + +# check main directory if config_settings.yaml exists else go in default_config and read from there From 379e78e036f39628342c10d92a5592ab4bd92e10 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Fri, 17 Nov 2023 15:44:10 +0500 Subject: [PATCH 07/12] chore: add documentation fix: fix documentation link fix: fix documentation link fix: remove type fix: remove type fix: cleanup --- .gitignore | 1 + Documentation/CONFIGURATION_MANAGEMENT.md | 108 ++++++++ OpenEdX/Info.plist | 2 - README.md | 2 +- config_script/process_config.py | 1 - process_config.py | 293 ---------------------- 6 files changed, 110 insertions(+), 297 deletions(-) create mode 100644 Documentation/CONFIGURATION_MANAGEMENT.md delete mode 100644 process_config.py diff --git a/.gitignore b/.gitignore index c8c1be1a6..8a80e27f8 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,4 @@ vendor/ venv/ Podfile.lock config_settings.yaml +default_config/ \ No newline at end of file diff --git a/Documentation/CONFIGURATION_MANAGEMENT.md b/Documentation/CONFIGURATION_MANAGEMENT.md new file mode 100644 index 000000000..b7db2e6d9 --- /dev/null +++ b/Documentation/CONFIGURATION_MANAGEMENT.md @@ -0,0 +1,108 @@ +# Configuration Management + +This documentation provides a comprehensive solution for integrating and managing configuration files in OpenEdx iOS project. + +## Features + +- **Build Phase Script Integration:** Adds a script to the Build Phase of Xcode. It calls the Xcode build phase run script, which takes care of the virtual environment and installing dependencies and executes a Python script `process_config.py` with `$CONFIGURATION` and `scheme_mappings` argument. +- **Python Script for Configuration:** Utilizes `process_config.py` for: + - Adding essential keys to `Info.plist` (e.g., Facebook, Microsoft keys). + - Creating `GoogleServices.plist` with Firebase keys. + - Generating `config.plist` from `ios.yaml` and `shared.yaml`. + +Inside `Config.swift`, parsing and populating relevant keys and classes are done, e.g. `AgreementConfig.swift` and `FirebaseConfig.swift`. + +## Getting Started + +### Configuration Setup + +Edit a `config_settings.yaml` in the `default_config` folder. It should contain data as follows: + +```yaml +config_directory: '{path_to_config_folder}' +config_mapping: + prod: 'prod' + stage: 'stage' + dev: 'dev' +# These mappings are configurable, e.g. dev: 'prod_test' +``` + +- `config_directory` provides the path of the config directory. +- `config_mappings` provides mappings that can be utilized to map the Xcode build scheme to a defined folder within the config directory, and it will be referenced. + +### Configuration Files + +Two main configuration files are used: `ios.yaml` and `shared.yaml`, placed under the folder defined in `config_mappings`. Additionally, a `mappings.yaml` file is required in the same directory, specifying the YAML files to be processed. Its structure is as follows: + +```yaml +ios: + files: + - {file_one.yaml} + - {file_two.yaml} +``` + +- `ios.yaml` will contain config data specific to iOS, e.g., Firebase keys, Facebook keys, etc. +- `shared.yaml` will contain config data that is shared, e.g., `API_HOST_URL`, `OAUTH_CLIENT_ID`, `TOKEN_TYPE`, etc. + +## Future Support + +- To add config related to some other service, create a class, e.g. `ServiceNameConfig.swift`, to be able to populate related fields. +- Create an `extension` to `Config.swift` to be able to add the newly created service as a variable to the main Config. +- If needed, make a protocol to be referenced inside the scope of `ConfigProtocol` so that the config is available using `ConfigProtocol` service. + +Example: + +```swift +private let key = "KEY" +extension Config { + public var serviceNameConfig: ServiceNameConfig { + return ServiceNameConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) + } +} +``` + +## Note + +If Firebase Configuration is provided the updated `FirebaseCrashlytics` build phase script extracts `googleAppID` from the newly generated `GoogleService-Info.plist` and runs the Crashlytics script with the provifing id. + +## Examples of Config Files + +`ios.yaml`: + +```yaml +OAUTH_CLIENT_ID: '' + +FIREBASE: + ENABLED: true + API_KEY: "testApiKey" + BUNDLE_ID: "testBundleID" + CLIENT_ID: "testClientID" + DATABASE_URL: "https://test.database.url" + GCM_SENDER_ID: "testGCMSenderID" + GOOGLE_APP_ID: "testGoogleAppID" + PROJECT_ID: "testProjectID" + REVERSED_CLIENT_ID: "testReversedClientID" + STORAGE_BUCKET: "testStorageBucket" + ANALYTICS_SOURCE: "firebase" + +MICROSOFT: + ENABLED: true + APP_ID: "microsoftAppID" +``` + +`shared.yaml`: + +```yaml +API_HOST_URL: "https://www.example.com" +FEEDBACK_EMAIL_ADDRESS: "example@mail.com" +TOKEN_TYPE: "JWT" + +AGREEMENT_URLS: + PRIVACY_POLICY_URL: "https://www.example.com/privacy" + TOS_URL: "https://www.example.com/tos" + +# Features +WHATS_NEW_ENABLED: false +``` + +The `default_config` directory is added to the project to provide an idea of how to write config YAML files. diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index 9fadbae9c..b94522839 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -4,8 +4,6 @@ Configuration $(CONFIGURATION) - ExampleKey - ExampleValue FirebaseAppDelegateProxyEnabled FirebaseAutomaticScreenReportingEnabled diff --git a/README.md b/README.md index 668a7a554..7586b4758 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Modern vision of the mobile application for the Open EdX platform from Raccoon G 4. Ensure that the ``OpenEdXDev`` or ``OpenEdXProd`` scheme is selected. -5. Configure the [``Environment.swift`` file](https://github.com/raccoongang/new-edx-app-ios/blob/main/OpenEdX/Environment.swift) with URLs and OAuth credentials for your Open edX instance. +5. Configure `config_settings.yaml` inside `default_config` and `config.yaml` inside sub direcroties to point to your OpenEdx configuration [Configuration Docuementation](./Documentation/CONFIGURATION_MANAGEMENT.md) 6. Click the **Run** button. diff --git a/config_script/process_config.py b/config_script/process_config.py index 5d3f6ad91..6941ce096 100644 --- a/config_script/process_config.py +++ b/config_script/process_config.py @@ -229,7 +229,6 @@ def parse_yaml(file_path): CONFIG_SETTINGS_YAML_FILENAME = 'config_settings.yaml' DEFAULT_CONFIG_PATH = './default_config/' + CONFIG_SETTINGS_YAML_FILENAME CONFIG_DIRECTORY_NAME = 'config_directory' - CONFIG_MAPPINGS = 'config_mapping' MAPPINGS_FILENAME = 'file_mappings.yaml' diff --git a/process_config.py b/process_config.py deleted file mode 100644 index e48c43058..000000000 --- a/process_config.py +++ /dev/null @@ -1,293 +0,0 @@ -import plistlib -import os -import yaml -from pathlib import Path -import sys - -class PlistManager: - def __init__(self, config_dir, config_files): - self.config_dir = config_dir - self.config_files = config_files - - def get_config_paths(self): - return [Path(self.config_dir) / config_name for config_name in self.config_files] - - def get_product_name(self): - return os.getenv('PRODUCT_NAME') - - def get_bundle_identifier(self): - return os.getenv('PRODUCT_BUNDLE_IDENTIFIER') - - def get_info_plist_path(self): - return os.getenv('INFOPLIST_PATH') - - def get_wrapper_name(self): - return os.getenv('WRAPPER_NAME') - - def get_built_products_path(self): - return os.getenv('BUILT_PRODUCTS_DIR') - - def get_bundle_config_path(self): - return os.path.join(self.get_built_products_path(), self.get_wrapper_name(), 'config.plist') - - def get_app_info_plist_path(self): - built_products_path = self.get_built_products_path() - info_plist_path = self.get_info_plist_path() - - if built_products_path and info_plist_path: - return os.path.join(built_products_path, info_plist_path) - else: - return None - - def get_firebase_info_plist_path(self): - built_products_path = self.get_built_products_path() - wrapper_name = self.get_wrapper_name() - - if built_products_path and wrapper_name: - return os.path.join(built_products_path, wrapper_name, 'GoogleService-Info.plist') - else: - print("The BUILT_PRODUCTS_DIR or WRAPPER_NAME environment variable is not set.") - return None - - def get_firebase_config_path(self): - built_products_path = self.get_built_products_path() - wrapper_name = self.get_wrapper_name() - - if built_products_path and wrapper_name: - return os.path.join(built_products_path, wrapper_name, 'firebase.plist') - else: - print("The BUILT_PRODUCTS_DIR or WRAPPER_NAME environment variable is not set.") - return None - - def load_config(self): - properties = {} - - for path in self.get_config_paths(): - try: - with open(path, 'r') as file: - dict = yaml.safe_load(file) - if dict is not None: - properties.update(dict) - except FileNotFoundError: - print(f"{path} not found. Skipping.") - - return properties - - def yaml_to_plist(self): - plist_data = {} - - for path in self.get_config_paths(): - try: - with open(path, 'r') as file: - yaml_data = yaml.safe_load(file) - if yaml_data is not None: - plist_data.update(yaml_data) - except FileNotFoundError: - print(f"{path} not found. Skipping.") - except yaml.YAMLError as e: - print(f"Error parsing YAML file {path}: {e}") - - return plist_data - - def write_to_plist_file(self, plist, file_path): - file_name = os.path.basename(file_path) - with open(file_path, 'wb') as plist_file: - plistlib.dump(plist, plist_file) - print(f"File {file_name} has been written to {file_path}") - - def print_info_plist_contents(self, plist_path): - if not plist_path: - print(f"Path is not set. {plist_path}") - try: - with open(plist_path, 'rb') as plist_file: - plist_contents = plistlib.load(plist_file) - print(plist_contents) - except Exception as e: - print(f"Error reading plist file: {e}") - - -class ConfigurationManager: - def __init__(self, plist_manager): - self.plist_manager = plist_manager - - def get_environment_variable(self, variable): - return os.getenv(variable) - - def add_url_scheme(self, scheme, plist): - body = { - 'CFBundleTypeRole': 'Editor', - 'CFBundleURLSchemes': scheme - } - existing = plist.get('CFBundleURLTypes', []) - found = any(scheme in entry.get('CFBundleURLSchemes', []) for entry in existing) - if not found: - existing.append(body) - plist['CFBundleURLTypes'] = existing - - def add_firebase_config(self, config, plist, firebase_info_plist_path): - firebase = config.get('FIREBASE', {}) - - if firebase_info_plist_path: - plist['BUNDLE_ID'] = firebase.get('BUNDLE_ID', '') - plist['API_KEY'] = firebase.get('API_KEY', '') - plist['CLIENT_ID'] = firebase.get('CLIENT_ID', '') - plist['GOOGLE_APP_ID'] = firebase.get('GOOGLE_APP_ID', '') - plist['GCM_SENDER_ID'] = firebase.get('GCM_SENDER_ID', '') - - project_id = firebase.get('PROJECT_ID', '') - if project_id: - plist['PROJECT_ID'] = project_id - plist['STORAGE_BUCKET'] = project_id + '.appspot.com' - plist['DATABASE_URL'] = 'https://' + project_id + '.firebaseio.com' - - reversed_client_id = firebase.get('REVERSED_CLIENT_ID', '') - if reversed_client_id: - plist['REVERSED_CLIENT_ID'] = reversed_client_id - - self.plist_manager.write_to_plist_file(plist, self.plist_manager.get_firebase_info_plist_path()) - else: - print("Firebase config is empty. Skipping") - - def add_facebook_config(self, config, plist): - facebook = config.get('FACEBOOK', {}) - key = facebook.get('FACEBOOK_APP_ID') - client_token = facebook.get('CLIENT_TOKEN') - - if key and client_token: - plist["FacebookAppID"] = key - plist["FacebookClientToken"] = client_token - plist["FacebookDisplayName"] = self.plist_manager.get_product_name() - scheme = ["fb" + key] - self.add_url_scheme(scheme, plist) - - def add_google_config(self, config, plist): - google = config.get('GOOGLE', {}) - key = google.get('GOOGLE_PLUS_KEY') - - if key: - scheme = ['.'.join(reversed(key.split('.')))] - self.add_url_scheme(scheme, plist) - - def add_microsoft_config(self, config, plist): - microsoft = config.get('MICROSOFT', {}) - key = microsoft.get('APP_ID') - - if key: - bundle_identifier = self.plist_manager.get_bundle_identifier() - scheme = ["msauth." + bundle_identifier] - self.add_url_scheme(scheme, plist) - - def update_info_plist(self, plist_data, plist_path): - if not plist_path: - print("Path is not set.") - sys.exit(1) - - try: - with open(plist_path, 'rb') as plist_file: - plist_contents = plistlib.load(plist_file) - - plist_contents.update(plist_data) - - try: - plistlib.dumps(plist_contents) - except Exception as e: - print(f"Error validating plist contents: {e}") - sys.exit(1) - - self.plist_manager.write_to_plist_file(plist_contents, plist_path) - - except FileNotFoundError: - print(f"Plist file not found: {plist_path}") - sys.exit(1) - except Exception as e: - print(f"Error reading or writing plist file: {e}") - sys.exit(1) - -def parse_yaml(file_path): - try: - with open(file_path, 'r') as file: - return yaml.safe_load(file) - except Exception as e: - print(f"Error opening or reading the file '{file_path}': {e}") - sys.exit(1) - -def get_configuration_name(configuration, config_mapping): - if configuration in ["DebugDev", "ReleaseDev"]: - return config_mapping.get("dev") - elif configuration in ["DebugStage", "ReleaseStage"]: - return config_mapping.get("stage") - elif configuration in ["DebugProd", "ReleaseProd"]: - return config_mapping.get("prod") - else: - return None - -def get_configuration_name_two(scheme_mapping, config_mapping): - if scheme_mapping.get('dev'): - return config_mapping.get("dev") - - if configuration in ["dev"]: - return - elif configuration in ["prod"]: - return config_mapping.get("prod") - elif configuration in ["DebugProd", "ReleaseProd"]: - return config_mapping.get("prod") - else: - return None - -def process_plist_files(configuration_manager, plist_manager, config): - firebase_info_plist_path = plist_manager.get_firebase_config_path() - app_info_plist_path = plist_manager.get_app_info_plist_path() - - plist = {} - configuration_manager.add_firebase_config(config, plist, firebase_info_plist_path) - configuration_manager.add_facebook_config(config, plist) - configuration_manager.add_google_config(config, plist) - configuration_manager.add_microsoft_config(config, plist) - configuration_manager.update_info_plist(plist, app_info_plist_path) - - bundle_config_path = plist_manager.get_bundle_config_path() - config_plist = plist_manager.yaml_to_plist() - plist_manager.write_to_plist_file(config_plist, bundle_config_path) - -def main(): - if len(sys.argv) < 2: - print("Configuration not provided. Using empty configuration.") - configuration = "" - else: - configuration = sys.argv[1] - print(f"Running with configuration: {configuration}") - - config_data = parse_yaml('config.yaml') - - config_directory = config_data.get('config_directory', "") - config_mapping = config_data.get('config_mapping', {}) - - config_name = get_configuration_name(configuration, config_mapping) - - if config_name is None: - print("Using default directory") - config_directory = 'default_config' - config_name = "default" - - path = os.path.join(config_directory, config_name, "config.yaml") - print(config_directory) - print(path) - data = parse_yaml(path) - ios_files = data.get('ios').get('files') - - plist_manager = PlistManager(os.path.join(config_directory, config_name), ios_files) - config = plist_manager.load_config() - - if not config: - print("Config is empty. Skipping") - else: - configuration_manager = ConfigurationManager(plist_manager) - process_plist_files(configuration_manager, plist_manager, config) - -if __name__ == "__main__": - # main() - path = os.path.join("default_config", "config_settings.yaml") - content = parse_yaml(path) - print(content) - -# check main directory if config_settings.yaml exists else go in default_config and read from there From 93c61c59b598e15c88580543feb83b99327a89f8 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Mon, 20 Nov 2023 11:41:57 +0500 Subject: [PATCH 08/12] fix: add default config_settings.yaml --- default_config/config_settings.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 default_config/config_settings.yaml diff --git a/default_config/config_settings.yaml b/default_config/config_settings.yaml new file mode 100644 index 000000000..249e93fc3 --- /dev/null +++ b/default_config/config_settings.yaml @@ -0,0 +1,5 @@ +config_directory: './default_config' +config_mapping: + prod: 'prod' + stage: 'stage' + dev: 'dev' From dbb215538f3f8f9ab8c661f0411de977727a6bba Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Mon, 20 Nov 2023 13:15:49 +0500 Subject: [PATCH 09/12] chore: address feedback --- .../Configuration/Combine/Config/Config.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift index c4a5c0a84..c32e1f1a5 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -37,7 +37,7 @@ private enum ConfigKeys: String { public class Config { public static let shared: Config = { let config = Config() - config.loadConfigPlist() + config.loadAndParseConfig() return config }() @@ -49,10 +49,10 @@ public class Config { internal convenience init() { self.init(properties: [:]) - loadConfigPlist() + loadAndParseConfig() } - private func loadConfigPlist() { + private func loadAndParseConfig() { guard let path = Bundle.main.path(forResource: "config", ofType: "plist"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let dict = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { return } @@ -103,12 +103,10 @@ extension Config: ConfigProtocol { } public var tokenType: TokenType { - if let tokenTypeValue = string(for: ConfigKeys.tokenType.rawValue), - let tokenType = TokenType(rawValue: tokenTypeValue) { - return tokenType - } else { - return .jwt - } + guard let tokenTypeValue = string(for: ConfigKeys.tokenType.rawValue), + let tokenType = TokenType(rawValue: tokenTypeValue) + else { return .jwt } + return tokenType } public var feedbackEmail: String { From 39b9eca3f8cc3e0eb1bf3860eaafacac7ff8bcc7 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Mon, 20 Nov 2023 17:07:03 +0500 Subject: [PATCH 10/12] chore: update config initialization --- Core/Core/Configuration/Combine/Config/Config.swift | 6 +----- OpenEdX.xcodeproj/project.pbxproj | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift index c32e1f1a5..de12c2db2 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -35,11 +35,7 @@ private enum ConfigKeys: String { } public class Config { - public static let shared: Config = { - let config = Config() - config.loadAndParseConfig() - return config - }() + public static let shared = Config() internal var properties: [String: Any] = [:] diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 069226267..e1f36a25e 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -390,7 +390,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/bash\n\nVENV_PATH=\"${SRCROOT}/venv\"\n\nif [ ! -d \"$VENV_PATH\" ]; then\n /usr/bin/python3 -m venv \"$VENV_PATH\"\nfi\n\nsource \"$VENV_PATH/bin/activate\"\n\npip install --upgrade pip\npip install PyYAML\n\nscheme_mapping='{\n \"prod\": [\"ReleaseProd\", \"DebugProd\"],\n \"stage\": [\"ReleaseStage\", \"DebugStage\"],\n \"dev\": [\"ReleaseDev\", \"DebugDev\"]\n}'\n\npython config_script/process_config.py \"$CONFIGURATION\" \"$scheme_mapping\"\n\ndeactivate\n"; + shellScript = "#!/bin/bash\n\nVENV_PATH=\"${SRCROOT}/venv\"\n\n/usr/bin/python3 -m venv \"$VENV_PATH\"\n\nsource \"$VENV_PATH/bin/activate\"\n\npip install --upgrade pip\npip install PyYAML\n\nscheme_mapping='{\n \"prod\": [\"ReleaseProd\", \"DebugProd\"],\n \"stage\": [\"ReleaseStage\", \"DebugStage\"],\n \"dev\": [\"ReleaseDev\", \"DebugDev\"]\n}'\n\npython config_script/process_config.py \"$CONFIGURATION\" \"$scheme_mapping\"\n\ndeactivate\n"; }; /* End PBXShellScriptBuildPhase section */ From b529307ab67c9324063c618de3c36259448bf375 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Mon, 20 Nov 2023 23:05:53 +0500 Subject: [PATCH 11/12] chore: address feedback --- .../Configuration/Combine/Config/Config.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Combine/Config/Config.swift index de12c2db2..8f131f0a2 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Combine/Config/Config.swift @@ -39,11 +39,11 @@ public class Config { internal var properties: [String: Any] = [:] - internal init(properties: [String: Any] = [:]) { + internal init(properties: [String: Any]) { self.properties = properties } - internal convenience init() { + private convenience init() { self.init(properties: [:]) loadAndParseConfig() } @@ -91,17 +91,24 @@ public class Config { extension Config: ConfigProtocol { public var baseURL: URL { - return URL(string: string(for: ConfigKeys.baseURL.rawValue)!)! + guard let urlString = string(for: ConfigKeys.baseURL.rawValue), + let url = URL(string: urlString) else { + fatalError("Unable to find base url in config.") + } + return url } public var oAuthClientId: String { - return string(for: ConfigKeys.oAuthClientID.rawValue) ?? "" + guard let clientID = string(for: ConfigKeys.oAuthClientID.rawValue) else { + fatalError("Unable to find OAuth ClientID in config.") + } + return clientID } public var tokenType: TokenType { guard let tokenTypeValue = string(for: ConfigKeys.tokenType.rawValue), let tokenType = TokenType(rawValue: tokenTypeValue) - else { return .jwt } + else { return .jwt } return tokenType } From a2dce2dfc1a1d669328ef5cbbde340ae12055219 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Tue, 21 Nov 2023 13:48:35 +0500 Subject: [PATCH 12/12] chore: address feedback --- Core/Core.xcodeproj/project.pbxproj | 14 +++++++++++--- .../Configuration/{ => Combine}/Debounce.swift | 0 .../{Combine => }/Config/AgreementConfig.swift | 0 .../{Combine => }/Config/Config.swift | 12 ++++++++---- .../{Combine => }/Config/FeaturesConfig.swift | 0 .../{Combine => }/Config/FirebaseConfig.swift | 0 .../Combine/Config => Tests}/ConfigTests.swift | 6 +++--- OpenEdX/AppDelegate.swift | 8 ++++---- OpenEdX/DI/AppAssembly.swift | 2 +- 9 files changed, 27 insertions(+), 15 deletions(-) rename Core/Core/Configuration/{ => Combine}/Debounce.swift (100%) rename Core/Core/Configuration/{Combine => }/Config/AgreementConfig.swift (100%) rename Core/Core/Configuration/{Combine => }/Config/Config.swift (93%) rename Core/Core/Configuration/{Combine => }/Config/FeaturesConfig.swift (100%) rename Core/Core/Configuration/{Combine => }/Config/FirebaseConfig.swift (100%) rename Core/Core/{Configuration/Combine/Config => Tests}/ConfigTests.swift (95%) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 29c01870d..07152c3ed 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -374,8 +374,8 @@ 0727876E28D233EC002E9142 /* Configuration */ = { isa = PBXGroup; children = ( + DBF6F2422B014AF30098414B /* Config */, CFC84955299FAC4D0055E497 /* Combine */, - CFC84951299F8B890055E497 /* Debounce.swift */, 0770DE1828D0847D006D8A5D /* BaseRouter.swift */, 0231CDBD2922422D00032416 /* CSSInjector.swift */, 02280F5A294B4E6F0032823A /* Connectivity.swift */, @@ -475,6 +475,7 @@ 0770DE5D28D0B209006D8A5D /* Localizable.strings */, 0770DE5128D0ADFF006D8A5D /* Assets.xcassets */, 071009CF28D1E3A600344290 /* Constants.swift */, + DBFB74502B0CA508004370F9 /* Tests */, ); path = Core; sourceTree = ""; @@ -567,7 +568,7 @@ CFC84955299FAC4D0055E497 /* Combine */ = { isa = PBXGroup; children = ( - DBF6F2422B014AF30098414B /* Config */, + CFC84951299F8B890055E497 /* Debounce.swift */, ); path = Combine; sourceTree = ""; @@ -579,11 +580,18 @@ DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, - DBF6F2472B01E20A0098414B /* ConfigTests.swift */, ); path = Config; sourceTree = ""; }; + DBFB74502B0CA508004370F9 /* Tests */ = { + isa = PBXGroup; + children = ( + DBF6F2472B01E20A0098414B /* ConfigTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; F1620A3A2C8B0699EAA61B57 /* Frameworks */ = { isa = PBXGroup; children = ( diff --git a/Core/Core/Configuration/Debounce.swift b/Core/Core/Configuration/Combine/Debounce.swift similarity index 100% rename from Core/Core/Configuration/Debounce.swift rename to Core/Core/Configuration/Combine/Debounce.swift diff --git a/Core/Core/Configuration/Combine/Config/AgreementConfig.swift b/Core/Core/Configuration/Config/AgreementConfig.swift similarity index 100% rename from Core/Core/Configuration/Combine/Config/AgreementConfig.swift rename to Core/Core/Configuration/Config/AgreementConfig.swift diff --git a/Core/Core/Configuration/Combine/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift similarity index 93% rename from Core/Core/Configuration/Combine/Config/Config.swift rename to Core/Core/Configuration/Config/Config.swift index 8f131f0a2..49f1ffdc5 100644 --- a/Core/Core/Configuration/Combine/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -35,7 +35,7 @@ private enum ConfigKeys: String { } public class Config { - public static let shared = Config() + let configFileName = "config" internal var properties: [String: Any] = [:] @@ -43,15 +43,19 @@ public class Config { self.properties = properties } - private convenience init() { + public convenience init() { self.init(properties: [:]) loadAndParseConfig() } private func loadAndParseConfig() { - guard let path = Bundle.main.path(forResource: "config", ofType: "plist"), + guard let path = Bundle.main.path(forResource: configFileName, ofType: "plist"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), - let dict = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { return } + let dict = try? PropertyListSerialization.propertyList( + from: data, + options: [], + format: nil) as? [String: Any] + else { return } properties = dict } diff --git a/Core/Core/Configuration/Combine/Config/FeaturesConfig.swift b/Core/Core/Configuration/Config/FeaturesConfig.swift similarity index 100% rename from Core/Core/Configuration/Combine/Config/FeaturesConfig.swift rename to Core/Core/Configuration/Config/FeaturesConfig.swift diff --git a/Core/Core/Configuration/Combine/Config/FirebaseConfig.swift b/Core/Core/Configuration/Config/FirebaseConfig.swift similarity index 100% rename from Core/Core/Configuration/Combine/Config/FirebaseConfig.swift rename to Core/Core/Configuration/Config/FirebaseConfig.swift diff --git a/Core/Core/Configuration/Combine/Config/ConfigTests.swift b/Core/Core/Tests/ConfigTests.swift similarity index 95% rename from Core/Core/Configuration/Combine/Config/ConfigTests.swift rename to Core/Core/Tests/ConfigTests.swift index f67e2ab10..819970fb4 100644 --- a/Core/Core/Configuration/Combine/Config/ConfigTests.swift +++ b/Core/Core/Tests/ConfigTests.swift @@ -15,7 +15,7 @@ class ConfigTests: XCTestCase { "OAUTH_CLIENT_ID": "oauth_client_id", "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", "TOKEN_TYPE": "JWT", - "WHATS_NEW_ENABLED": false, + "WHATS_NEW_ENABLED": true, "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", "TOS_URL": "https://www.example.com/tos" @@ -42,13 +42,13 @@ class ConfigTests: XCTestCase { XCTAssertEqual(config.oAuthClientId, "oauth_client_id") XCTAssertEqual(config.feedbackEmail, "example@mail.com") XCTAssertEqual(config.tokenType.rawValue, "JWT") - XCTAssertFalse(config.features.whatNewEnabled) + XCTAssertTrue(config.features.whatNewEnabled) } func testFeaturesConfigInitialization() { let config = Config(properties: properties) - XCTAssertFalse(config.features.whatNewEnabled) + XCTAssertTrue(config.features.whatNewEnabled) } func testAgreementConfigInitialization() { diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index ba4a256f7..2c6d8b71b 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -31,14 +31,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - if Config.shared.firebase.enabled, - let configuration = Config.shared.firebase.firebaseOptions { + initDI() + + if let config = Container.shared.resolve(ConfigProtocol.self), + let configuration = config.firebase.firebaseOptions { FirebaseApp.configure(options: configuration) Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) } - initDI() - Theme.Fonts.registerFonts() window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = RouteController() diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 5ed98dd50..e5a491fc7 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -118,7 +118,7 @@ class AppAssembly: Assembly { }.inObjectScope(.container) container.register(ConfigProtocol.self) { _ in - Config.shared + Config() }.inObjectScope(.container) container.register(CSSInjector.self) { r in