diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 6d8ebfdee..c97689735 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -64,7 +64,7 @@ public class SignInViewModel: ObservableObject { let user = try await interactor.login(username: username, password: password) analytics.setUserID("\(user.id)") analytics.userLogin(method: .password) - router.showMainScreen() + router.showMainOrWhatsNewScreen() } catch let error { isShowProgress = false if let validationError = error.validationError, diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index a2142684f..e882311dd 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -93,7 +93,7 @@ public class SignUpViewModel: ObservableObject { analytics.setUserID("\(user.id)") analytics.registrationSuccess() isShowProgress = false - router.showMainScreen() + router.showMainOrWhatsNewScreen() } catch let error { isShowProgress = false diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index ddb4ac259..93016c754 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -761,9 +761,9 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -816,7 +816,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -849,7 +849,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -901,7 +901,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -918,7 +918,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -949,7 +949,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -978,8 +978,8 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) @@ -1151,9 +1151,9 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -1206,7 +1206,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -1239,7 +1239,7 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -1291,7 +1291,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -1308,7 +1308,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -1339,7 +1339,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -1368,8 +1368,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) diff --git a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift index d478f0f68..aec540570 100644 --- a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift @@ -37,7 +37,7 @@ final class SignInViewModelTests: XCTestCase { await viewModel.login(username: "email", password: "") Verify(interactor, 0, .login(username: .any, password: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidEmailAddress) XCTAssertEqual(viewModel.isShowProgress, false) @@ -57,7 +57,7 @@ final class SignInViewModelTests: XCTestCase { await viewModel.login(username: "edxUser@edx.com", password: "") Verify(interactor, 0, .login(username: .any, password: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidPasswordLenght) XCTAssertEqual(viewModel.isShowProgress, false) @@ -82,7 +82,7 @@ final class SignInViewModelTests: XCTestCase { Verify(interactor, 1, .login(username: .any, password: .any)) Verify(analytics, .userLogin(method: .any)) - Verify(router, 1, .showMainScreen()) + Verify(router, 1, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.errorMessage, nil) XCTAssertEqual(viewModel.isShowProgress, true) @@ -109,7 +109,7 @@ final class SignInViewModelTests: XCTestCase { await viewModel.login(username: "edxUser@edx.com", password: "password123") Verify(interactor, 1, .login(username: .any, password: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.errorMessage, validationErrorMessage) XCTAssertEqual(viewModel.isShowProgress, false) @@ -132,7 +132,7 @@ final class SignInViewModelTests: XCTestCase { await viewModel.login(username: "edxUser@edx.com", password: "password123") Verify(interactor, 1, .login(username: .any, password: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.invalidCredentials) XCTAssertEqual(viewModel.isShowProgress, false) @@ -155,7 +155,7 @@ final class SignInViewModelTests: XCTestCase { await viewModel.login(username: "edxUser@edx.com", password: "password123") Verify(interactor, 1, .login(username: .any, password: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.unknownError) XCTAssertEqual(viewModel.isShowProgress, false) @@ -180,7 +180,7 @@ final class SignInViewModelTests: XCTestCase { await viewModel.login(username: "edxUser@edx.com", password: "password123") Verify(interactor, 1, .login(username: .any, password: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.errorMessage, CoreLocalization.Error.slowOrNoInternetConnection) XCTAssertEqual(viewModel.isShowProgress, false) diff --git a/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift index 8699b79fc..b59519b27 100644 --- a/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift @@ -128,7 +128,7 @@ final class SignUpViewModelTests: XCTestCase { Verify(interactor, 1, .validateRegistrationFields(fields: .any)) Verify(interactor, 1, .registerUser(fields: .any)) - Verify(router, 1, .showMainScreen()) + Verify(router, 1, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) XCTAssertEqual(viewModel.showError, false) @@ -164,7 +164,7 @@ final class SignUpViewModelTests: XCTestCase { Verify(interactor, 1, .validateRegistrationFields(fields: .any)) Verify(interactor, 0, .registerUser(fields: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) XCTAssertEqual(viewModel.showError, false) @@ -192,7 +192,7 @@ final class SignUpViewModelTests: XCTestCase { Verify(interactor, 1, .validateRegistrationFields(fields: .any)) Verify(interactor, 1, .registerUser(fields: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) XCTAssertEqual(viewModel.showError, true) @@ -220,7 +220,7 @@ final class SignUpViewModelTests: XCTestCase { Verify(interactor, 1, .validateRegistrationFields(fields: .any)) Verify(interactor, 1, .registerUser(fields: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) XCTAssertEqual(viewModel.showError, true) @@ -250,7 +250,7 @@ final class SignUpViewModelTests: XCTestCase { Verify(interactor, 1, .validateRegistrationFields(fields: .any)) Verify(interactor, 1, .registerUser(fields: .any)) - Verify(router, 0, .showMainScreen()) + Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) XCTAssertEqual(viewModel.showError, true) diff --git a/Core/Core/Configuration/BaseRouter.swift b/Core/Core/Configuration/BaseRouter.swift index c86b90f62..034855d87 100644 --- a/Core/Core/Configuration/BaseRouter.swift +++ b/Core/Core/Configuration/BaseRouter.swift @@ -21,7 +21,7 @@ public protocol BaseRouter { func removeLastView(controllers: Int) - func showMainScreen() + func showMainOrWhatsNewScreen() func showLoginScreen() @@ -73,7 +73,7 @@ open class BaseRouterMock: BaseRouter { public func dismiss(animated: Bool) {} - public func showMainScreen() {} + public func showMainOrWhatsNewScreen() {} public func showLoginScreen() {} diff --git a/Core/Core/Configuration/Config.swift b/Core/Core/Configuration/Config.swift index 77f5da816..4cb9f3afc 100644 --- a/Core/Core/Configuration/Config.swift +++ b/Core/Core/Configuration/Config.swift @@ -23,6 +23,8 @@ public class Config { public let feedbackEmail = "support@example.com" + public let whatsNewEnabled: Bool = false + public init(baseURL: String, oAuthClientId: String) { guard let url = URL(string: baseURL) else { fatalError("Ivalid baseURL") diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index a4cf6b418..488ab1a83 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -490,9 +490,9 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -545,7 +545,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -578,7 +578,7 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -630,7 +630,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -647,7 +647,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -678,7 +678,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -707,8 +707,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index 27aebe250..efbb553e6 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -490,9 +490,9 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -545,7 +545,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -578,7 +578,7 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -630,7 +630,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -647,7 +647,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -678,7 +678,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -707,8 +707,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index 1eb44a322..f55ce38ed 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -490,9 +490,9 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -545,7 +545,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -578,7 +578,7 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -630,7 +630,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -647,7 +647,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -678,7 +678,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -707,8 +707,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) diff --git a/Discussion/Discussion.xcodeproj.xcworkspace/contents.xcworkspacedata b/Discussion/Discussion.xcodeproj.xcworkspace/contents.xcworkspacedata index 85b36c90c..d64d30457 100644 --- a/Discussion/Discussion.xcodeproj.xcworkspace/contents.xcworkspacedata +++ b/Discussion/Discussion.xcodeproj.xcworkspace/contents.xcworkspacedata @@ -28,4 +28,7 @@ + + diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index 424aa9aaf..5303b1525 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -490,9 +490,9 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -545,7 +545,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -578,7 +578,7 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -630,7 +630,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -647,7 +647,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -678,7 +678,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -707,8 +707,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) @@ -2033,9 +2033,9 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -2094,7 +2094,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -2165,7 +2165,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -2223,7 +2223,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -2246,7 +2246,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -2283,7 +2283,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -2330,8 +2330,8 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 4fc81e931..08ffca434 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 025DE1A528DB4DAE0053E0F4 /* Profile.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 025DE1A328DB4DAE0053E0F4 /* Profile.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 027DB33028D8A063002B6862 /* Dashboard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 027DB32F28D8A063002B6862 /* Dashboard.framework */; }; 027DB33128D8A063002B6862 /* Dashboard.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 027DB32F28D8A063002B6862 /* Dashboard.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 028A37362ADFF404008CA604 /* WhatsNew.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 028A37352ADFF404008CA604 /* WhatsNew.framework */; }; + 028A37372ADFF404008CA604 /* WhatsNew.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 028A37352ADFF404008CA604 /* WhatsNew.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 0293A2032A6FCA590090A336 /* CorePersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0293A2022A6FCA590090A336 /* CorePersistence.swift */; }; 0293A2052A6FCD430090A336 /* CoursePersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0293A2042A6FCD430090A336 /* CoursePersistence.swift */; }; 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */; }; @@ -59,6 +61,7 @@ 0219C67828F4347600D64452 /* Course.framework in Embed Frameworks */, 025DE1A528DB4DAE0053E0F4 /* Profile.framework in Embed Frameworks */, 027DB33128D8A063002B6862 /* Dashboard.framework in Embed Frameworks */, + 028A37372ADFF404008CA604 /* WhatsNew.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -75,6 +78,7 @@ 025DE1A328DB4DAE0053E0F4 /* Profile.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Profile.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 025EF2F7297177F300B838AB /* OpenEdX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OpenEdX.entitlements; sourceTree = ""; }; 027DB32F28D8A063002B6862 /* Dashboard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Dashboard.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 028A37352ADFF404008CA604 /* WhatsNew.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WhatsNew.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0293A2022A6FCA590090A336 /* CorePersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorePersistence.swift; sourceTree = ""; }; 0293A2042A6FCD430090A336 /* CoursePersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoursePersistence.swift; sourceTree = ""; }; 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPersistence.swift; sourceTree = ""; }; @@ -118,6 +122,7 @@ buildActionMask = 2147483647; files = ( 07A7D78F28F5C9060000BE81 /* Core.framework in Frameworks */, + 028A37362ADFF404008CA604 /* WhatsNew.framework in Frameworks */, 025DE1A428DB4DAE0053E0F4 /* Profile.framework in Frameworks */, 0770DE4B28D0A462006D8A5D /* Authorization.framework in Frameworks */, 072787B128D34D83002E9142 /* Discovery.framework in Frameworks */, @@ -206,6 +211,7 @@ 4E6FB43543890E90BB88D64D /* Frameworks */ = { isa = PBXGroup; children = ( + 028A37352ADFF404008CA604 /* WhatsNew.framework */, 0218196328F734FA00202564 /* Discussion.framework */, 07A7D78E28F5C9060000BE81 /* Core.framework */, 0219C67628F4347600D64452 /* Course.framework */, diff --git a/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme b/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme index c2f6ffa2b..55135d8fa 100644 --- a/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme +++ b/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme @@ -97,13 +97,22 @@ ReferencedContainer = "container:Profile/Profile.xcodeproj"> + + + + + + diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index edc74bd1f..9c2bff47e 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -15,6 +15,7 @@ import Course import Discussion import Authorization import Profile +import WhatsNew // swiftlint:disable function_body_length class AppAssembly: Assembly { @@ -112,6 +113,10 @@ class AppAssembly: Assembly { r.resolve(Router.self)! }.inObjectScope(.container) + container.register(WhatsNewRouter.self) { r in + r.resolve(Router.self)! + }.inObjectScope(.container) + container.register(Config.self) { _ in Config(baseURL: BuildConfiguration.shared.baseURL, oAuthClientId: BuildConfiguration.shared.clientId) }.inObjectScope(.container) @@ -139,6 +144,10 @@ class AppAssembly: Assembly { r.resolve(AppStorage.self)! }.inObjectScope(.container) + container.register(WhatsNewStorage.self) { r in + r.resolve(AppStorage.self)! + }.inObjectScope(.container) + container.register(ProfileStorage.self) { r in r.resolve(AppStorage.self)! }.inObjectScope(.container) diff --git a/OpenEdX/Data/AppStorage.swift b/OpenEdX/Data/AppStorage.swift index 99144be00..0815b4b7f 100644 --- a/OpenEdX/Data/AppStorage.swift +++ b/OpenEdX/Data/AppStorage.swift @@ -9,8 +9,9 @@ import Foundation import KeychainSwift import Core import Profile +import WhatsNew -public class AppStorage: CoreStorage, ProfileStorage { +public class AppStorage: CoreStorage, ProfileStorage, WhatsNewStorage { private let keychain: KeychainSwift private let userDefaults: UserDefaults @@ -58,6 +59,19 @@ public class AppStorage: CoreStorage, ProfileStorage { } } } + + public var whatsNewVersion: String? { + get { + return userDefaults.string(forKey: KEY_WHATSNEW_VERSION) + } + set(newValue) { + if let newValue { + userDefaults.set(newValue, forKey: KEY_WHATSNEW_VERSION) + } else { + userDefaults.removeObject(forKey: KEY_WHATSNEW_VERSION) + } + } + } public var userProfile: DataLayer.UserProfile? { get { @@ -134,4 +148,5 @@ public class AppStorage: CoreStorage, ProfileStorage { private let KEY_USER_PROFILE = "userProfile" private let KEY_USER = "refreshToken" private let KEY_SETTINGS = "userSettings" + private let KEY_WHATSNEW_VERSION = "whatsNewVersion" } diff --git a/OpenEdX/RouteController.swift b/OpenEdX/RouteController.swift index 1aa036dc1..367569cf6 100644 --- a/OpenEdX/RouteController.swift +++ b/OpenEdX/RouteController.swift @@ -9,6 +9,8 @@ import UIKit import SwiftUI import Core import Authorization +import WhatsNew +import Swinject class RouteController: UIViewController { @@ -30,7 +32,7 @@ class RouteController: UIViewController { if let user = appStorage.user, appStorage.accessToken != nil { analytics.setUserID("\(user.id)") DispatchQueue.main.async { - self.showMainScreen() + self.showMainOrWhatsNewScreen() } } else { DispatchQueue.main.async { @@ -47,9 +49,27 @@ class RouteController: UIViewController { present(navigation, animated: false) } - private func showMainScreen() { - let controller = UIHostingController(rootView: MainScreenView()) - navigation.viewControllers = [controller] + private func showMainOrWhatsNewScreen() { + var storage = Container.shared.resolve(WhatsNewStorage.self)! + let config = Container.shared.resolve(Config.self)! + + let viewModel = WhatsNewViewModel(storage: storage) + let shouldShowWhatsNew = viewModel.shouldShowWhatsNew() + + if shouldShowWhatsNew && config.whatsNewEnabled { + if let jsonVersion = viewModel.getVersion() { + storage.whatsNewVersion = jsonVersion + } + let whatsNewView = WhatsNewView( + router: Container.shared.resolve(WhatsNewRouter.self)!, + viewModel: viewModel + ) + let controller = UIHostingController(rootView: whatsNewView) + navigation.viewControllers = [controller] + } else { + let controller = UIHostingController(rootView: MainScreenView()) + navigation.viewControllers = [controller] + } present(navigation, animated: false) } } diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index 58413b862..1dbb690d8 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -16,9 +16,11 @@ import Discussion import Discovery import Dashboard import Profile +import WhatsNew import Combine public class Router: AuthorizationRouter, + WhatsNewRouter, DiscoveryRouter, ProfileRouter, DashboardRouter, @@ -56,10 +58,27 @@ public class Router: AuthorizationRouter, navigationController.setViewControllers(viewControllers, animated: true) } - public func showMainScreen() { + public func showMainOrWhatsNewScreen() { showToolBar() - let controller = UIHostingController(rootView: MainScreenView()) - navigationController.setViewControllers([controller], animated: true) + var storage = Container.shared.resolve(WhatsNewStorage.self)! + let config = Container.shared.resolve(Config.self)! + + let viewModel = WhatsNewViewModel(storage: storage) + let whatsNew = WhatsNewView(router: Container.shared.resolve(WhatsNewRouter.self)!, viewModel: viewModel) + let shouldShowWhatsNew = viewModel.shouldShowWhatsNew() + + if shouldShowWhatsNew && config.whatsNewEnabled { + if let jsonVersion = viewModel.getVersion() { + storage.whatsNewVersion = jsonVersion + } + let controller = UIHostingController(rootView: whatsNew) + navigationController.viewControllers = [controller] + navigationController.setViewControllers([controller], animated: true) + } else { + let controller = UIHostingController(rootView: MainScreenView()) + navigationController.viewControllers = [controller] + navigationController.setViewControllers([controller], animated: true) + } } public func showLoginScreen() { diff --git a/OpenEdX/View/MainScreenView.swift b/OpenEdX/View/MainScreenView.swift index c27d7ea3f..e05451782 100644 --- a/OpenEdX/View/MainScreenView.swift +++ b/OpenEdX/View/MainScreenView.swift @@ -11,6 +11,7 @@ import Core import Swinject import Dashboard import Profile +import WhatsNew import SwiftUIIntrospect struct MainScreenView: View { diff --git a/Podfile b/Podfile index 1d97fa467..c3658683d 100644 --- a/Podfile +++ b/Podfile @@ -48,6 +48,15 @@ abstract_target "App" do end end + target "WhatsNew" do + project './WhatsNew/WhatsNew.xcodeproj' + workspace './WhatsNew/WhatsNew.xcodeproj' + + target 'WhatsNewTests' do + pod 'SwiftyMocky', :git => 'https://github.com/MakeAWishFoundation/SwiftyMocky.git', :tag => '4.2.0' + end + end + target "Dashboard" do project './Dashboard/Dashboard.xcodeproj' workspace './Dashboard/Dashboard.xcodeproj' diff --git a/Podfile.lock b/Podfile.lock index 5b998cd5e..cf6c60e94 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -180,6 +180,6 @@ SPEC CHECKSUMS: SwiftyMocky: c5e96e4ff76ec6dbf5a5941aeb039b5a546954a0 Swinject: 893c9a543000ac2f10ee4cbaf0933c6992c935d5 -PODFILE CHECKSUM: 1639b311802f5d36686512914067b7221ff97a64 +PODFILE CHECKSUM: a44d8de5a5803eb3e3c995134c79c3dad959dbf7 COCOAPODS: 1.12.1 diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index e8f39508d..58466c37c 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -490,9 +490,9 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -545,7 +545,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -578,7 +578,7 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -630,7 +630,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -647,7 +647,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -678,7 +678,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -707,8 +707,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) @@ -2370,9 +2370,9 @@ open class ProfileRouterMock: ProfileRouter, Mock { perform?(`controllers`) } - open func showMainScreen() { - addInvocation(.m_showMainScreen) - let perform = methodPerformValue(.m_showMainScreen) as? () -> Void + open func showMainOrWhatsNewScreen() { + addInvocation(.m_showMainOrWhatsNewScreen) + let perform = methodPerformValue(.m_showMainOrWhatsNewScreen) as? () -> Void perform?() } @@ -2429,7 +2429,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case m_backWithFade case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) - case m_showMainScreen + case m_showMainOrWhatsNewScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen @@ -2478,7 +2478,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsControllers, rhs: rhsControllers, with: matcher), lhsControllers, rhsControllers, "controllers")) return Matcher.ComparisonResult(results) - case (.m_showMainScreen, .m_showMainScreen): return .match + case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match case (.m_showLoginScreen, .m_showLoginScreen): return .match @@ -2534,7 +2534,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_backWithFade: return 0 case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue - case .m_showMainScreen: return 0 + case .m_showMainOrWhatsNewScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 @@ -2555,7 +2555,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_backWithFade: return ".backWithFade()" case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" - case .m_showMainScreen: return ".showMainScreen()" + case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" @@ -2590,7 +2590,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func backWithFade() -> Verify { return Verify(method: .m_backWithFade)} public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} - public static func showMainScreen() -> Verify { return Verify(method: .m_showMainScreen)} + public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} @@ -2631,8 +2631,8 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func removeLastView(controllers: Parameter, perform: @escaping (Int) -> Void) -> Perform { return Perform(method: .m_removeLastView__controllers_controllers(`controllers`), performs: perform) } - public static func showMainScreen(perform: @escaping () -> Void) -> Perform { - return Perform(method: .m_showMainScreen, performs: perform) + public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) diff --git a/WhatsNew/.gitignore b/WhatsNew/.gitignore new file mode 100644 index 000000000..9c22c8b85 --- /dev/null +++ b/WhatsNew/.gitignore @@ -0,0 +1,99 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/* +/WhatsNew.xcodeproj/xcuserdata/ +/WhatsNew.xcworkspace/xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## R.swift +R.generated.swift + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +.DS_Store +.idea +xcode-frameworks diff --git a/WhatsNew/Mockfile b/WhatsNew/Mockfile new file mode 100644 index 000000000..0b15b3b93 --- /dev/null +++ b/WhatsNew/Mockfile @@ -0,0 +1,17 @@ +sourceryCommand: null +sourceryTemplate: null +unit.tests.mock: + sources: + include: + - ./../WhatsNew + - ./WhatsNew + exclude: [] + output: ./WhatsNewTests/WhatsNewMock.generated.swift + targets: + - MyAppUnitTests + import: + - Core + - WhatsNew + - Foundation + - SwiftUI + - Combine \ No newline at end of file diff --git a/WhatsNew/WhatsNew.xcodeproj/project.pbxproj b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj new file mode 100644 index 000000000..dad830243 --- /dev/null +++ b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj @@ -0,0 +1,1499 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 020A7B5F2AE131A9000BAF70 /* WhatsNewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020A7B5E2AE131A9000BAF70 /* WhatsNewModel.swift */; }; + 020A7B612AE136D2000BAF70 /* WhatsNew.json in Resources */ = {isa = PBXBuildFile; fileRef = 020A7B602AE136D2000BAF70 /* WhatsNew.json */; }; + 020AC2692AEBB69E0086E975 /* WhatsNewMock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020AC2682AEBB69E0086E975 /* WhatsNewMock.generated.swift */; }; + 028A37262ADFF3F8008CA604 /* WhatsNew.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 028A371D2ADFF3F7008CA604 /* WhatsNew.framework */; }; + 028A372B2ADFF3F8008CA604 /* WhatsNewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028A372A2ADFF3F8008CA604 /* WhatsNewTests.swift */; }; + 028A373A2ADFF425008CA604 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 028A37392ADFF425008CA604 /* Core.framework */; }; + 02B54E0D2AE0331F00C56962 /* WhatsNewNavigationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B54E0C2AE0331F00C56962 /* WhatsNewNavigationButton.swift */; }; + 02B54E0F2AE0337800C56962 /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B54E0E2AE0337800C56962 /* PageControl.swift */; }; + 02B54E112AE061C100C56962 /* WhatsNewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B54E102AE061C100C56962 /* WhatsNewRouter.swift */; }; + 02E640792ADFF5920079AEDA /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E640782ADFF5920079AEDA /* WhatsNewView.swift */; }; + 02E6407C2ADFF6250079AEDA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 02E6407E2ADFF6250079AEDA /* Localizable.strings */; }; + 02E640812ADFFE440079AEDA /* WhatsNewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E640802ADFFE440079AEDA /* WhatsNewViewModel.swift */; }; + 02E640862ADFFF380079AEDA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 02E640852ADFFF380079AEDA /* Assets.xcassets */; }; + 02E6408A2AE004300079AEDA /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E640892AE004300079AEDA /* Strings.swift */; }; + 02E6408C2AE006680079AEDA /* swiftgen.yml in Resources */ = {isa = PBXBuildFile; fileRef = 02E6408B2AE006680079AEDA /* swiftgen.yml */; }; + 02EC90AA2AE904E1007DE1E0 /* WhatsNewStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EC90A92AE904E1007DE1E0 /* WhatsNewStorage.swift */; }; + 02EC90AC2AE90C64007DE1E0 /* WhatsNewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EC90AB2AE90C64007DE1E0 /* WhatsNewPage.swift */; }; + B3BB9B06B226989A619C6440 /* Pods_App_WhatsNew.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05AC45C7050E30F8394E0C76 /* Pods_App_WhatsNew.framework */; }; + EF5CA11A55CB49F2DA030D25 /* Pods_App_WhatsNew_WhatsNewTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8D1A5DF016EC4630637336C /* Pods_App_WhatsNew_WhatsNewTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 028A37272ADFF3F8008CA604 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 028A37142ADFF3F7008CA604 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 028A371C2ADFF3F7008CA604; + remoteInfo = WhatsNew; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 020A7B5E2AE131A9000BAF70 /* WhatsNewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewModel.swift; sourceTree = ""; }; + 020A7B602AE136D2000BAF70 /* WhatsNew.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = WhatsNew.json; path = WhatsNew/Data/WhatsNew.json; sourceTree = SOURCE_ROOT; }; + 020AC2682AEBB69E0086E975 /* WhatsNewMock.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WhatsNewMock.generated.swift; path = WhatsNewTests/WhatsNewMock.generated.swift; sourceTree = SOURCE_ROOT; }; + 028A371D2ADFF3F7008CA604 /* WhatsNew.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WhatsNew.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 028A37252ADFF3F7008CA604 /* WhatsNewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WhatsNewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 028A372A2ADFF3F8008CA604 /* WhatsNewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewTests.swift; sourceTree = ""; }; + 028A37392ADFF425008CA604 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 02B54E0C2AE0331F00C56962 /* WhatsNewNavigationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewNavigationButton.swift; sourceTree = ""; }; + 02B54E0E2AE0337800C56962 /* PageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControl.swift; sourceTree = ""; }; + 02B54E102AE061C100C56962 /* WhatsNewRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewRouter.swift; sourceTree = ""; }; + 02E640782ADFF5920079AEDA /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewView.swift; sourceTree = ""; }; + 02E6407D2ADFF6250079AEDA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 02E6407F2ADFF6270079AEDA /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; + 02E640802ADFFE440079AEDA /* WhatsNewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewViewModel.swift; sourceTree = ""; }; + 02E640852ADFFF380079AEDA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 02E640892AE004300079AEDA /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; + 02E6408B2AE006680079AEDA /* swiftgen.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftgen.yml; sourceTree = ""; }; + 02EC90A92AE904E1007DE1E0 /* WhatsNewStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewStorage.swift; sourceTree = ""; }; + 02EC90AB2AE90C64007DE1E0 /* WhatsNewPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewPage.swift; sourceTree = ""; }; + 02EC90B12AE91BF1007DE1E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05AC45C7050E30F8394E0C76 /* Pods_App_WhatsNew.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_WhatsNew.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0C01007F0E8CEDCD293E0A68 /* Pods-App-WhatsNew.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.debug.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.debug.xcconfig"; sourceTree = ""; }; + 1E3F4487E7D3A48F5FD12DDA /* Pods-App-WhatsNew-WhatsNewTests.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.releasestage.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.releasestage.xcconfig"; sourceTree = ""; }; + 34C1F2BEAF7F0DCB8E630F33 /* Pods-App-WhatsNew.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.releasestage.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.releasestage.xcconfig"; sourceTree = ""; }; + 365FD817D70DFBCBDE2EAE5F /* Pods-App-WhatsNew-WhatsNewTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.releaseprod.xcconfig"; sourceTree = ""; }; + 4CB92C9DBA730A1B06B076BA /* Pods-App-WhatsNew.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.release.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.release.xcconfig"; sourceTree = ""; }; + 58DF8140E3B3436F58C4C8B9 /* Pods-App-WhatsNew-WhatsNewTests.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.releasedev.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.releasedev.xcconfig"; sourceTree = ""; }; + 6F50A409FBCCC7C08712A25E /* Pods-App-WhatsNew.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.debugdev.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.debugdev.xcconfig"; sourceTree = ""; }; + 9176CDC000731B73D2F10372 /* Pods-App-WhatsNew.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.debugstage.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.debugstage.xcconfig"; sourceTree = ""; }; + 9844714991FA40ECDC228CC9 /* Pods-App-WhatsNew.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.releasedev.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.releasedev.xcconfig"; sourceTree = ""; }; + A2CABA11F5E7F89EFD9A05AC /* Pods-App-WhatsNew-WhatsNewTests.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.debugstage.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.debugstage.xcconfig"; sourceTree = ""; }; + A557A3CED4D6327AAE6AA02C /* Pods-App-WhatsNew-WhatsNewTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.release.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.release.xcconfig"; sourceTree = ""; }; + A76975E21FF282D59CEC4452 /* Pods-App-WhatsNew.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.debugprod.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.debugprod.xcconfig"; sourceTree = ""; }; + AB8156676C9C771D691ADE07 /* Pods-App-WhatsNew.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew/Pods-App-WhatsNew.releaseprod.xcconfig"; sourceTree = ""; }; + B7EAC5E8F0ED2F1F81050C30 /* Pods-App-WhatsNew-WhatsNewTests.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.debugprod.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.debugprod.xcconfig"; sourceTree = ""; }; + E905D28DCAA1940E08C96896 /* Pods-App-WhatsNew-WhatsNewTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.debug.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.debug.xcconfig"; sourceTree = ""; }; + F71207762CEF1763A08C7151 /* Pods-App-WhatsNew-WhatsNewTests.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-WhatsNew-WhatsNewTests.debugdev.xcconfig"; path = "Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests.debugdev.xcconfig"; sourceTree = ""; }; + F8D1A5DF016EC4630637336C /* Pods_App_WhatsNew_WhatsNewTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_WhatsNew_WhatsNewTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 028A371A2ADFF3F7008CA604 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 028A373A2ADFF425008CA604 /* Core.framework in Frameworks */, + B3BB9B06B226989A619C6440 /* Pods_App_WhatsNew.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 028A37222ADFF3F7008CA604 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 028A37262ADFF3F8008CA604 /* WhatsNew.framework in Frameworks */, + EF5CA11A55CB49F2DA030D25 /* Pods_App_WhatsNew_WhatsNewTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 020A7B5D2AE1317E000BAF70 /* Domain */ = { + isa = PBXGroup; + children = ( + 02EC90AB2AE90C64007DE1E0 /* WhatsNewPage.swift */, + ); + path = Domain; + sourceTree = ""; + }; + 028A37132ADFF3F7008CA604 = { + isa = PBXGroup; + children = ( + 02E6408B2AE006680079AEDA /* swiftgen.yml */, + 028A371F2ADFF3F7008CA604 /* WhatsNew */, + 028A37292ADFF3F8008CA604 /* WhatsNewTests */, + 028A371E2ADFF3F7008CA604 /* Products */, + 028A37382ADFF425008CA604 /* Frameworks */, + 3397DFC72A3A62728BCA5367 /* Pods */, + ); + sourceTree = ""; + }; + 028A371E2ADFF3F7008CA604 /* Products */ = { + isa = PBXGroup; + children = ( + 028A371D2ADFF3F7008CA604 /* WhatsNew.framework */, + 028A37252ADFF3F7008CA604 /* WhatsNewTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 028A371F2ADFF3F7008CA604 /* WhatsNew */ = { + isa = PBXGroup; + children = ( + 02E640722ADFF54E0079AEDA /* SwiftGen */, + 02E640822ADFFEB00079AEDA /* Data */, + 020A7B5D2AE1317E000BAF70 /* Domain */, + 02E640752ADFF5700079AEDA /* Presentation */, + 02E6407E2ADFF6250079AEDA /* Localizable.strings */, + 02E640852ADFFF380079AEDA /* Assets.xcassets */, + 02EC90B12AE91BF1007DE1E0 /* Info.plist */, + ); + path = WhatsNew; + sourceTree = ""; + }; + 028A37292ADFF3F8008CA604 /* WhatsNewTests */ = { + isa = PBXGroup; + children = ( + 02EC90B52AE92AEB007DE1E0 /* Presentation */, + 020AC2682AEBB69E0086E975 /* WhatsNewMock.generated.swift */, + ); + path = WhatsNewTests; + sourceTree = ""; + }; + 028A37382ADFF425008CA604 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 028A37392ADFF425008CA604 /* Core.framework */, + 05AC45C7050E30F8394E0C76 /* Pods_App_WhatsNew.framework */, + F8D1A5DF016EC4630637336C /* Pods_App_WhatsNew_WhatsNewTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 02B54E0B2AE0330F00C56962 /* Elements */ = { + isa = PBXGroup; + children = ( + 02B54E0C2AE0331F00C56962 /* WhatsNewNavigationButton.swift */, + 02B54E0E2AE0337800C56962 /* PageControl.swift */, + ); + path = Elements; + sourceTree = ""; + }; + 02E640722ADFF54E0079AEDA /* SwiftGen */ = { + isa = PBXGroup; + children = ( + 02E640892AE004300079AEDA /* Strings.swift */, + ); + path = SwiftGen; + sourceTree = ""; + }; + 02E640752ADFF5700079AEDA /* Presentation */ = { + isa = PBXGroup; + children = ( + 02B54E0B2AE0330F00C56962 /* Elements */, + 02E640782ADFF5920079AEDA /* WhatsNewView.swift */, + 02E640802ADFFE440079AEDA /* WhatsNewViewModel.swift */, + 02B54E102AE061C100C56962 /* WhatsNewRouter.swift */, + ); + path = Presentation; + sourceTree = ""; + }; + 02E640822ADFFEB00079AEDA /* Data */ = { + isa = PBXGroup; + children = ( + 020A7B5E2AE131A9000BAF70 /* WhatsNewModel.swift */, + 020A7B602AE136D2000BAF70 /* WhatsNew.json */, + 02EC90A92AE904E1007DE1E0 /* WhatsNewStorage.swift */, + ); + path = Data; + sourceTree = ""; + }; + 02EC90B52AE92AEB007DE1E0 /* Presentation */ = { + isa = PBXGroup; + children = ( + 028A372A2ADFF3F8008CA604 /* WhatsNewTests.swift */, + ); + path = Presentation; + sourceTree = ""; + }; + 3397DFC72A3A62728BCA5367 /* Pods */ = { + isa = PBXGroup; + children = ( + 0C01007F0E8CEDCD293E0A68 /* Pods-App-WhatsNew.debug.xcconfig */, + 4CB92C9DBA730A1B06B076BA /* Pods-App-WhatsNew.release.xcconfig */, + 9176CDC000731B73D2F10372 /* Pods-App-WhatsNew.debugstage.xcconfig */, + A76975E21FF282D59CEC4452 /* Pods-App-WhatsNew.debugprod.xcconfig */, + 6F50A409FBCCC7C08712A25E /* Pods-App-WhatsNew.debugdev.xcconfig */, + 34C1F2BEAF7F0DCB8E630F33 /* Pods-App-WhatsNew.releasestage.xcconfig */, + AB8156676C9C771D691ADE07 /* Pods-App-WhatsNew.releaseprod.xcconfig */, + 9844714991FA40ECDC228CC9 /* Pods-App-WhatsNew.releasedev.xcconfig */, + E905D28DCAA1940E08C96896 /* Pods-App-WhatsNew-WhatsNewTests.debug.xcconfig */, + A2CABA11F5E7F89EFD9A05AC /* Pods-App-WhatsNew-WhatsNewTests.debugstage.xcconfig */, + B7EAC5E8F0ED2F1F81050C30 /* Pods-App-WhatsNew-WhatsNewTests.debugprod.xcconfig */, + F71207762CEF1763A08C7151 /* Pods-App-WhatsNew-WhatsNewTests.debugdev.xcconfig */, + A557A3CED4D6327AAE6AA02C /* Pods-App-WhatsNew-WhatsNewTests.release.xcconfig */, + 1E3F4487E7D3A48F5FD12DDA /* Pods-App-WhatsNew-WhatsNewTests.releasestage.xcconfig */, + 365FD817D70DFBCBDE2EAE5F /* Pods-App-WhatsNew-WhatsNewTests.releaseprod.xcconfig */, + 58DF8140E3B3436F58C4C8B9 /* Pods-App-WhatsNew-WhatsNewTests.releasedev.xcconfig */, + ); + name = Pods; + path = ../Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 028A37182ADFF3F7008CA604 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 028A371C2ADFF3F7008CA604 /* WhatsNew */ = { + isa = PBXNativeTarget; + buildConfigurationList = 028A372F2ADFF3F8008CA604 /* Build configuration list for PBXNativeTarget "WhatsNew" */; + buildPhases = ( + E5055BD989FEEC50EF87C814 /* [CP] Check Pods Manifest.lock */, + 02E6408E2AE007090079AEDA /* SwiftGen */, + 028A37182ADFF3F7008CA604 /* Headers */, + 028A37192ADFF3F7008CA604 /* Sources */, + 028A371A2ADFF3F7008CA604 /* Frameworks */, + 028A371B2ADFF3F7008CA604 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = WhatsNew; + productName = WhatsNew; + productReference = 028A371D2ADFF3F7008CA604 /* WhatsNew.framework */; + productType = "com.apple.product-type.framework"; + }; + 028A37242ADFF3F7008CA604 /* WhatsNewTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 028A37322ADFF3F8008CA604 /* Build configuration list for PBXNativeTarget "WhatsNewTests" */; + buildPhases = ( + 8685C1ADA448B11AB167C40E /* [CP] Check Pods Manifest.lock */, + 028A37212ADFF3F7008CA604 /* Sources */, + 028A37222ADFF3F7008CA604 /* Frameworks */, + 028A37232ADFF3F7008CA604 /* Resources */, + 8A74692D666D8FF13F7BA64F /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 028A37282ADFF3F8008CA604 /* PBXTargetDependency */, + ); + name = WhatsNewTests; + productName = WhatsNewTests; + productReference = 028A37252ADFF3F7008CA604 /* WhatsNewTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 028A37142ADFF3F7008CA604 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 028A371C2ADFF3F7008CA604 = { + CreatedOnToolsVersion = 15.0; + LastSwiftMigration = 1500; + }; + 028A37242ADFF3F7008CA604 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = 028A37172ADFF3F7008CA604 /* Build configuration list for PBXProject "WhatsNew" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + uk, + ); + mainGroup = 028A37132ADFF3F7008CA604; + productRefGroup = 028A371E2ADFF3F7008CA604 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 028A371C2ADFF3F7008CA604 /* WhatsNew */, + 028A37242ADFF3F7008CA604 /* WhatsNewTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 028A371B2ADFF3F7008CA604 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02E640862ADFFF380079AEDA /* Assets.xcassets in Resources */, + 02E6407C2ADFF6250079AEDA /* Localizable.strings in Resources */, + 020A7B612AE136D2000BAF70 /* WhatsNew.json in Resources */, + 02E6408C2AE006680079AEDA /* swiftgen.yml in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 028A37232ADFF3F7008CA604 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 02E6408E2AE007090079AEDA /* SwiftGen */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftGen; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ -f \"${PODS_ROOT}/SwiftGen/bin/swiftgen\" ]]; then\n \"${PODS_ROOT}/SwiftGen/bin/swiftgen\"\nelse\n echo \"warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it.\"\nfi\n"; + }; + 8685C1ADA448B11AB167C40E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-App-WhatsNew-WhatsNewTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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; + }; + 8A74692D666D8FF13F7BA64F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App-WhatsNew-WhatsNewTests/Pods-App-WhatsNew-WhatsNewTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + E5055BD989FEEC50EF87C814 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-App-WhatsNew-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 028A37192ADFF3F7008CA604 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02E640812ADFFE440079AEDA /* WhatsNewViewModel.swift in Sources */, + 02B54E112AE061C100C56962 /* WhatsNewRouter.swift in Sources */, + 020A7B5F2AE131A9000BAF70 /* WhatsNewModel.swift in Sources */, + 02B54E0F2AE0337800C56962 /* PageControl.swift in Sources */, + 02EC90AC2AE90C64007DE1E0 /* WhatsNewPage.swift in Sources */, + 02EC90AA2AE904E1007DE1E0 /* WhatsNewStorage.swift in Sources */, + 02E6408A2AE004300079AEDA /* Strings.swift in Sources */, + 02E640792ADFF5920079AEDA /* WhatsNewView.swift in Sources */, + 02B54E0D2AE0331F00C56962 /* WhatsNewNavigationButton.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 028A37212ADFF3F7008CA604 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 028A372B2ADFF3F8008CA604 /* WhatsNewTests.swift in Sources */, + 020AC2692AEBB69E0086E975 /* WhatsNewMock.generated.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 028A37282ADFF3F8008CA604 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 028A371C2ADFF3F7008CA604 /* WhatsNew */; + targetProxy = 028A37272ADFF3F8008CA604 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 02E6407E2ADFF6250079AEDA /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 02E6407D2ADFF6250079AEDA /* en */, + 02E6407F2ADFF6270079AEDA /* uk */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 028A372D2ADFF3F8008CA604 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 028A372E2ADFF3F8008CA604 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 028A37302ADFF3F8008CA604 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0C01007F0E8CEDCD293E0A68 /* Pods-App-WhatsNew.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 028A37312ADFF3F8008CA604 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4CB92C9DBA730A1B06B076BA /* Pods-App-WhatsNew.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 028A37332ADFF3F8008CA604 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E905D28DCAA1940E08C96896 /* Pods-App-WhatsNew-WhatsNewTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 028A37342ADFF3F8008CA604 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A557A3CED4D6327AAE6AA02C /* Pods-App-WhatsNew-WhatsNewTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 02E6405E2ADFF4DE0079AEDA /* DebugDev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugDev; + }; + 02E6405F2ADFF4DE0079AEDA /* DebugDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6F50A409FBCCC7C08712A25E /* Pods-App-WhatsNew.debugdev.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugDev; + }; + 02E640602ADFF4DE0079AEDA /* DebugDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F71207762CEF1763A08C7151 /* Pods-App-WhatsNew-WhatsNewTests.debugdev.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugDev; + }; + 02E640612ADFF4E50079AEDA /* DebugProd */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugProd; + }; + 02E640622ADFF4E50079AEDA /* DebugProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A76975E21FF282D59CEC4452 /* Pods-App-WhatsNew.debugprod.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugProd; + }; + 02E640632ADFF4E50079AEDA /* DebugProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B7EAC5E8F0ED2F1F81050C30 /* Pods-App-WhatsNew-WhatsNewTests.debugprod.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugProd; + }; + 02E640642ADFF4EA0079AEDA /* DebugStage */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugStage; + }; + 02E640652ADFF4EA0079AEDA /* DebugStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9176CDC000731B73D2F10372 /* Pods-App-WhatsNew.debugstage.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugStage; + }; + 02E640662ADFF4EA0079AEDA /* DebugStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A2CABA11F5E7F89EFD9A05AC /* Pods-App-WhatsNew-WhatsNewTests.debugstage.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugStage; + }; + 02E640672ADFF4F10079AEDA /* ReleaseDev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseDev; + }; + 02E640682ADFF4F10079AEDA /* ReleaseDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9844714991FA40ECDC228CC9 /* Pods-App-WhatsNew.releasedev.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseDev; + }; + 02E640692ADFF4F10079AEDA /* ReleaseDev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 58DF8140E3B3436F58C4C8B9 /* Pods-App-WhatsNew-WhatsNewTests.releasedev.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseDev; + }; + 02E6406A2ADFF4F70079AEDA /* ReleaseProd */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseProd; + }; + 02E6406B2ADFF4F70079AEDA /* ReleaseProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB8156676C9C771D691ADE07 /* Pods-App-WhatsNew.releaseprod.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseProd; + }; + 02E6406C2ADFF4F70079AEDA /* ReleaseProd */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 365FD817D70DFBCBDE2EAE5F /* Pods-App-WhatsNew-WhatsNewTests.releaseprod.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseProd; + }; + 02E6406D2ADFF4FD0079AEDA /* ReleaseStage */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseStage; + }; + 02E6406E2ADFF4FD0079AEDA /* ReleaseStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 34C1F2BEAF7F0DCB8E630F33 /* Pods-App-WhatsNew.releasestage.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WhatsNew/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.WhatsNew; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseStage; + }; + 02E6406F2ADFF4FD0079AEDA /* ReleaseStage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1E3F4487E7D3A48F5FD12DDA /* Pods-App-WhatsNew-WhatsNewTests.releasestage.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = L8PG7LC3Y3; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.openedx.app.WhatsNewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseStage; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 028A37172ADFF3F7008CA604 /* Build configuration list for PBXProject "WhatsNew" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 028A372D2ADFF3F8008CA604 /* Debug */, + 02E640642ADFF4EA0079AEDA /* DebugStage */, + 02E640612ADFF4E50079AEDA /* DebugProd */, + 02E6405E2ADFF4DE0079AEDA /* DebugDev */, + 028A372E2ADFF3F8008CA604 /* Release */, + 02E6406D2ADFF4FD0079AEDA /* ReleaseStage */, + 02E6406A2ADFF4F70079AEDA /* ReleaseProd */, + 02E640672ADFF4F10079AEDA /* ReleaseDev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 028A372F2ADFF3F8008CA604 /* Build configuration list for PBXNativeTarget "WhatsNew" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 028A37302ADFF3F8008CA604 /* Debug */, + 02E640652ADFF4EA0079AEDA /* DebugStage */, + 02E640622ADFF4E50079AEDA /* DebugProd */, + 02E6405F2ADFF4DE0079AEDA /* DebugDev */, + 028A37312ADFF3F8008CA604 /* Release */, + 02E6406E2ADFF4FD0079AEDA /* ReleaseStage */, + 02E6406B2ADFF4F70079AEDA /* ReleaseProd */, + 02E640682ADFF4F10079AEDA /* ReleaseDev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 028A37322ADFF3F8008CA604 /* Build configuration list for PBXNativeTarget "WhatsNewTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 028A37332ADFF3F8008CA604 /* Debug */, + 02E640662ADFF4EA0079AEDA /* DebugStage */, + 02E640632ADFF4E50079AEDA /* DebugProd */, + 02E640602ADFF4DE0079AEDA /* DebugDev */, + 028A37342ADFF3F8008CA604 /* Release */, + 02E6406F2ADFF4FD0079AEDA /* ReleaseStage */, + 02E6406C2ADFF4F70079AEDA /* ReleaseProd */, + 02E640692ADFF4F10079AEDA /* ReleaseDev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 028A37142ADFF3F7008CA604 /* Project object */; +} diff --git a/WhatsNew/WhatsNew.xcodeproj/xcshareddata/xcschemes/WhatsNew.xcscheme b/WhatsNew/WhatsNew.xcodeproj/xcshareddata/xcschemes/WhatsNew.xcscheme new file mode 100644 index 000000000..c7efb774a --- /dev/null +++ b/WhatsNew/WhatsNew.xcodeproj/xcshareddata/xcschemes/WhatsNew.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/Contents.json b/WhatsNew/WhatsNew/Assets.xcassets/1.0/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/WhatsNew/WhatsNew/Assets.xcassets/1.0/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image1_1.0.imageset/Contents.json b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image1_1.0.imageset/Contents.json new file mode 100644 index 000000000..52b4c6b95 --- /dev/null +++ b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image1_1.0.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Group 97.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "localizable" : true + } +} diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image1_1.0.imageset/Group 97.png b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image1_1.0.imageset/Group 97.png new file mode 100644 index 000000000..1853bf8ab Binary files /dev/null and b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image1_1.0.imageset/Group 97.png differ diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image2_1.0.imageset/Contents.json b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image2_1.0.imageset/Contents.json new file mode 100644 index 000000000..80d1a4ce5 --- /dev/null +++ b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image2_1.0.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Group 96-2.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image2_1.0.imageset/Group 96-2.png b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image2_1.0.imageset/Group 96-2.png new file mode 100644 index 000000000..36b711417 Binary files /dev/null and b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image2_1.0.imageset/Group 96-2.png differ diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image3_1.0.imageset/Contents.json b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image3_1.0.imageset/Contents.json new file mode 100644 index 000000000..a58f59752 --- /dev/null +++ b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image3_1.0.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "globe.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image3_1.0.imageset/globe.png b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image3_1.0.imageset/globe.png new file mode 100644 index 000000000..bbdbb3514 Binary files /dev/null and b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image3_1.0.imageset/globe.png differ diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image4_1.0.imageset/Contents.json b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image4_1.0.imageset/Contents.json new file mode 100644 index 000000000..5b0cf9a6b --- /dev/null +++ b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image4_1.0.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "feature screenshot.jpg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNew/WhatsNew/Assets.xcassets/1.0/image4_1.0.imageset/feature screenshot.jpg b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image4_1.0.imageset/feature screenshot.jpg new file mode 100644 index 000000000..5ca9740d9 Binary files /dev/null and b/WhatsNew/WhatsNew/Assets.xcassets/1.0/image4_1.0.imageset/feature screenshot.jpg differ diff --git a/WhatsNew/WhatsNew/Assets.xcassets/Contents.json b/WhatsNew/WhatsNew/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/WhatsNew/WhatsNew/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNew/WhatsNew/Data/WhatsNew.json b/WhatsNew/WhatsNew/Data/WhatsNew.json new file mode 100644 index 000000000..355c720b6 --- /dev/null +++ b/WhatsNew/WhatsNew/Data/WhatsNew.json @@ -0,0 +1,52 @@ +[ + { + "version": "1.0", + "messages": [ + { + "image": "image1_1.0", + "title": "Improved language support", + "message": "We have added more translations throughout the app so you can learn on edX your way!" + }, + { + "image": "image2_1.0", + "title": "Download videos offline", + "message": "Easily download videos without having an internet connection, so you can keep learning when there isn’t a network around" + }, + { + "image": "image3_1.0", + "title": "Reduced Network Usage", + "message": "Now you can download your content faster to get right into your next lesson!" + }, + { + "image": "image4_1.0", + "title": "Learning Site Switching", + "message": "Switch more easily between multiple learning sites. Find the new options within account settings and easily manage your accounts" + } + ] + }, + { + "version": "0.9", + "messages": [ + { + "image": "image1_1.0", + "title": "1.3 Improved language support", + "message": "We have added more translations throughout the app so you can learn on edX your way!" + }, + { + "image": "image2_1.0", + "title": "Download videos offline", + "message": "Easily download videos without having an internet connection, so you can keep learning when there isn’t a network around" + }, + { + "image": "image3_1.0", + "title": "Reduced Network Usage", + "message": "Now you can download your content faster to get right into your next lesson!" + }, + { + "image": "image4_1.0", + "title": "Learning Site Switching", + "message": "Switch more easily between multiple learning sites. Find the new options within account settings and easily manage your accounts" + } + ] + } +] diff --git a/WhatsNew/WhatsNew/Data/WhatsNewModel.swift b/WhatsNew/WhatsNew/Data/WhatsNewModel.swift new file mode 100644 index 000000000..48302fb62 --- /dev/null +++ b/WhatsNew/WhatsNew/Data/WhatsNewModel.swift @@ -0,0 +1,69 @@ +// +// WhatsNewModel.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 19.10.2023. +// + +import Foundation + +// MARK: - WhatsNewModelElement +public struct WhatsNewModelElement: Codable { + public let version: String + public let messages: [Message] + + public init(version: String, messages: [Message]) { + self.version = version + self.messages = messages + } +} + +// MARK: - Message +public struct Message: Codable { + public let image: String + public let title: String + public let message: String + + public init(image: String, title: String, message: String) { + self.image = image + self.title = title + self.message = message + } +} + +public typealias WhatsNewModel = [WhatsNewModelElement] + +extension WhatsNewModel { + + private func compareVersions(_ version1: String, _ version2: String) -> ComparisonResult { + let v1 = version1.split(separator: ".").compactMap { Int($0) } + let v2 = version2.split(separator: ".").compactMap { Int($0) } + + for (a, b) in zip(v1, v2) { + if a != b { + return a < b ? .orderedAscending : .orderedDescending + } + } + + return v1.count < v2.count ? .orderedAscending : (v1.count > v2.count ? .orderedDescending : .orderedSame) + } + + private func findLatestVersion(_ versions: [String]) -> String? { + guard let latestVersion = versions.max(by: { compareVersions($0, $1) == .orderedAscending }) else { + return nil + } + return latestVersion + } + + + var domain: [WhatsNewPage] { + guard let latestVersion = findLatestVersion(self.map { $0.version }) else { return [] } + return self.first(where: { $0.version == latestVersion })?.messages.map { + WhatsNewPage( + image: $0.image, + title: $0.title, + description: $0.message + ) + } ?? [] + } +} diff --git a/WhatsNew/WhatsNew/Data/WhatsNewStorage.swift b/WhatsNew/WhatsNew/Data/WhatsNewStorage.swift new file mode 100644 index 000000000..35d35d998 --- /dev/null +++ b/WhatsNew/WhatsNew/Data/WhatsNewStorage.swift @@ -0,0 +1,21 @@ +// +// WhatsNewStorage.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 25.10.2023. +// + +import Foundation + +public protocol WhatsNewStorage { + var whatsNewVersion: String? {get set} +} + +#if DEBUG +public class WhatsNewStorageMock: WhatsNewStorage { + + public var whatsNewVersion: String? + + public init() {} +} +#endif diff --git a/WhatsNew/WhatsNew/Domain/WhatsNewPage.swift b/WhatsNew/WhatsNew/Domain/WhatsNewPage.swift new file mode 100644 index 000000000..8172ee037 --- /dev/null +++ b/WhatsNew/WhatsNew/Domain/WhatsNewPage.swift @@ -0,0 +1,14 @@ +// +// WhatsNewPage.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 25.10.2023. +// + +import Foundation + +struct WhatsNewPage { + let image: String + let title: String + let description: String +} diff --git a/WhatsNew/WhatsNew/Info.plist b/WhatsNew/WhatsNew/Info.plist new file mode 100644 index 000000000..f72a0f657 --- /dev/null +++ b/WhatsNew/WhatsNew/Info.plist @@ -0,0 +1,12 @@ + + + + + + diff --git a/WhatsNew/WhatsNew/Presentation/Elements/PageControl.swift b/WhatsNew/WhatsNew/Presentation/Elements/PageControl.swift new file mode 100644 index 000000000..d6050b6ec --- /dev/null +++ b/WhatsNew/WhatsNew/Presentation/Elements/PageControl.swift @@ -0,0 +1,32 @@ +// +// PageControl.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 18.10.2023. +// + +import SwiftUI +import Core + +struct PageControl: View { + let numberOfPages: Int + var currentPage: Int + + private var dots: some View { + HStack(spacing: 8) { + ForEach(0 ..< numberOfPages) { page in + RoundedRectangle(cornerRadius: 4) + .frame(width: page == currentPage ? 24 : 8, height: 8) + .foregroundColor(page == currentPage ? Theme.Colors.accentColor : Theme.Colors.textSecondary) + } + } + } + + var body: some View { + VStack { + Spacer() + dots + Spacer() + } + } +} diff --git a/WhatsNew/WhatsNew/Presentation/Elements/WhatsNewNavigationButton.swift b/WhatsNew/WhatsNew/Presentation/Elements/WhatsNewNavigationButton.swift new file mode 100644 index 000000000..03206348e --- /dev/null +++ b/WhatsNew/WhatsNew/Presentation/Elements/WhatsNewNavigationButton.swift @@ -0,0 +1,63 @@ +// +// CustomButton.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 18.10.2023. +// + +import SwiftUI +import Core + +struct WhatsNewNavigationButton: View { + let type: ButtonType + let action: () -> Void + + enum ButtonType { + case previous, next, done + } + + var body: some View { + Group { + HStack(spacing: 4) { + if type == .previous { + CoreAssets.arrowLeft.swiftUIImage + .renderingMode(.template) + .foregroundColor(Theme.Colors.accentColor) + } + + Text(type == .previous ? WhatsNewLocalization.buttonPrevious + : (type == .next ? WhatsNewLocalization.buttonNext : WhatsNewLocalization.buttonDone )) + .foregroundColor(type == .previous ? Theme.Colors.accentColor : Color.white) + .font(Theme.Fonts.labelLarge) + + if type == .next { + CoreAssets.arrowLeft.swiftUIImage + .renderingMode(.template) + .rotationEffect(Angle(degrees: 180)) + .foregroundColor(Color.white) + } + + if type == .done { + CoreAssets.checkmark.swiftUIImage + .renderingMode(.template) + .foregroundColor(Color.white) + } + }.padding(.horizontal, 20) + .padding(.vertical, 9) + }.fixedSize() + .background(type == .previous + ? Theme.Colors.background + : Theme.Colors.accentColor) + .accessibilityElement(children: .ignore) + .accessibilityLabel(type == .previous ? WhatsNewLocalization.buttonPrevious + : (type == .next ? WhatsNewLocalization.buttonNext : WhatsNewLocalization.buttonDone )) + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(type == .previous + ? Theme.Colors.accentColor + : Theme.Colors.background, lineWidth: 1) + ) + .onTapGesture { action() } + } +} diff --git a/WhatsNew/WhatsNew/Presentation/WhatsNewRouter.swift b/WhatsNew/WhatsNew/Presentation/WhatsNewRouter.swift new file mode 100644 index 000000000..4416e24fd --- /dev/null +++ b/WhatsNew/WhatsNew/Presentation/WhatsNewRouter.swift @@ -0,0 +1,19 @@ +// +// WhatsNewRouter.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 18.10.2023. +// + +import Foundation +import Core + +public protocol WhatsNewRouter: BaseRouter { +} + +// Mark - For testing and SwiftUI preview +#if DEBUG +public class WhatsNewRouterMock: BaseRouterMock, WhatsNewRouter { + public override init() {} +} +#endif diff --git a/WhatsNew/WhatsNew/Presentation/WhatsNewView.swift b/WhatsNew/WhatsNew/Presentation/WhatsNewView.swift new file mode 100644 index 000000000..bc419cb0a --- /dev/null +++ b/WhatsNew/WhatsNew/Presentation/WhatsNewView.swift @@ -0,0 +1,160 @@ +// +// WhatsNewView.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 18.10.2023. +// + +import SwiftUI +import Core + +public struct WhatsNewView: View { + + private let router: WhatsNewRouter + + @ObservedObject + private var viewModel: WhatsNewViewModel + + @Environment (\.isHorizontal) + private var isHorizontal + + @State var index = 0 + + public init(router: WhatsNewRouter, viewModel: WhatsNewViewModel) { + self.router = router + self.viewModel = viewModel + } + + public var body: some View { + GeometryReader { reader in + ZStack(alignment: isHorizontal ? .center : .bottom) { + Theme.Colors.background + .ignoresSafeArea() + adaptiveStack(isHorizontal: isHorizontal) { + TabView(selection: $index) { + ForEach(Array(viewModel.newItems.enumerated()), id: \.offset) { _, new in + adaptiveStack(isHorizontal: isHorizontal) { + ZStack(alignment: .center) { + Image(new.image, bundle: Bundle(for: BundleToken.self)) + .resizable() + .scaledToFit() + .frame(minWidth: 250, maxWidth: 300) + .padding(24) + }.frame(minHeight: 250, maxHeight: 416) + Spacer() + } + } + }.tabViewStyle(.page(indexDisplayMode: .never)) + } + if isHorizontal { + HStack { + Spacer() + + Rectangle() + .foregroundColor(Theme.Colors.background) + .frame(width: reader.size.width / 1.9) + .ignoresSafeArea() + .mask( + LinearGradient( + gradient: Gradient(colors: [ + .clear, + .black, + .black, + .black, + .black, + .black, + .black, + .black, + .black]), + startPoint: .leading, + endPoint: .trailing + ) + ) + } .allowsHitTesting(false) + } + HStack { + if isHorizontal { + Spacer() + } + VStack(spacing: 16) { + VStack { + if !viewModel.newItems.isEmpty { + Text(viewModel.newItems[viewModel.index].title) + .font(Theme.Fonts.titleMedium) + Text(viewModel.newItems[viewModel.index].description) + .font(Theme.Fonts.bodyMedium) + .multilineTextAlignment(.center) + } + }.frame(height: 100) + .allowsHitTesting(false) + + HStack(spacing: 36) { + WhatsNewNavigationButton(type: .previous, action: { + if index != 0 { + withAnimation(.linear(duration: 0.3)) { + index -= 1 + } + } + }).opacity(viewModel.index != 0 ? 1 : 0) + WhatsNewNavigationButton( + type: viewModel.index < viewModel.newItems.count - 1 ? .next : .done, + action: { + if index < viewModel.newItems.count - 1 { + withAnimation(.linear(duration: 0.3)) { + index += 1 + } + } else { + router.showMainOrWhatsNewScreen() + } + } + ) + } + } + .padding(.bottom, isHorizontal ? 0 : 52) + .padding(.horizontal, 24) + .frame(width: isHorizontal ? reader.size.width / 1.9 : nil) + } + VStack { + if isHorizontal { + Spacer() + } + PageControl(numberOfPages: viewModel.newItems.count, currentPage: viewModel.index) + .frame(height: isHorizontal ? 8 : nil) + .allowsHitTesting(false) + .padding(.top, isHorizontal ? 0 : 170) + .padding(.bottom, 8) + } + + }.onChange(of: index) { ind in + withAnimation(.linear(duration: 0.3)) { + viewModel.index = ind + } + } + .navigationTitle(WhatsNewLocalization.title) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing, content: { + Button(action: { + router.showMainOrWhatsNewScreen() + }, label: { + Image(systemName: "xmark") + .foregroundColor(Theme.Colors.accentColor) + }) + }) + } + } + } + + class BundleToken {} +} + +#if DEBUG +struct WhatsNewView_Previews: PreviewProvider { + static var previews: some View { + WhatsNewView( + router: WhatsNewRouterMock(), + viewModel: WhatsNewViewModel(storage: WhatsNewStorageMock()) + ) + .loadFonts() + } +} +#endif diff --git a/WhatsNew/WhatsNew/Presentation/WhatsNewViewModel.swift b/WhatsNew/WhatsNew/Presentation/WhatsNewViewModel.swift new file mode 100644 index 000000000..170472088 --- /dev/null +++ b/WhatsNew/WhatsNew/Presentation/WhatsNewViewModel.swift @@ -0,0 +1,73 @@ +// +// WhatsNewViewModel.swift +// WhatsNew +// +// Created by  Stepanok Ivan on 18.10.2023. +// + +import SwiftUI +import Core +import Swinject + +public class WhatsNewViewModel: ObservableObject { + @Published var index: Int = 0 + @Published var newItems: [WhatsNewPage] = [] + private let storage: WhatsNewStorage + + public init(storage: WhatsNewStorage) { + self.storage = storage + newItems = loadWhatsNew() + } + + public func getVersion() -> String? { + guard let model = loadWhatsNewModel() else { return nil } + return model.first?.version + } + + public func shouldShowWhatsNew() -> Bool { + guard let currentVersion = getVersion() else { return false } + + // If there is no saved version in storage, we always show WhatsNew + guard let savedVersion = storage.whatsNewVersion else { return true } + + // We break down the versions into components major, minor, patch + let savedComponents = savedVersion.components(separatedBy: ".") + let currentComponents = currentVersion.components(separatedBy: ".") + + // Checking major and minor components + if savedComponents.count >= 2 && currentComponents.count >= 2 { + let savedMajor = savedComponents[0] + let savedMinor = savedComponents[1] + + let currentMajor = currentComponents[0] + let currentMinor = currentComponents[1] + + // If major or minor are different, show WhatsNew + if savedMajor != currentMajor || savedMinor != currentMinor { + return true + } + } + return false + } + + func loadWhatsNew() -> [WhatsNewPage] { + guard let domain = loadWhatsNewModel()?.domain else { return [] } + return domain + } + + private func loadWhatsNewModel() -> WhatsNewModel? { + guard let fileUrl = Bundle(for: Self.self).url(forResource: "WhatsNew", withExtension: "json") else { + print("Unable to locate WhatsNew.json") + return nil + } + + do { + let data = try Data(contentsOf: fileUrl) + let decoder = JSONDecoder() + return try decoder.decode(WhatsNewModel.self, from: data) + } catch { + print("Error decoding WhatsNew.json: \(error)") + return nil + } + } +} diff --git a/WhatsNew/WhatsNew/SwiftGen/Strings.swift b/WhatsNew/WhatsNew/SwiftGen/Strings.swift new file mode 100644 index 000000000..8b483b7b4 --- /dev/null +++ b/WhatsNew/WhatsNew/SwiftGen/Strings.swift @@ -0,0 +1,47 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references + +// MARK: - Strings + +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +public enum WhatsNewLocalization { + /// Done + public static let buttonDone = WhatsNewLocalization.tr("Localizable", "BUTTON_DONE", fallback: "Done") + /// Next + public static let buttonNext = WhatsNewLocalization.tr("Localizable", "BUTTON_NEXT", fallback: "Next") + /// Previous + public static let buttonPrevious = WhatsNewLocalization.tr("Localizable", "BUTTON_PREVIOUS", fallback: "Previous") + /// Localizable.strings + /// WhatsNew + /// + /// Created by  Stepanok Ivan on 18.10.2023. + public static let title = WhatsNewLocalization.tr("Localizable", "TITLE", fallback: "What's New") +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension WhatsNewLocalization { + private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/WhatsNew/WhatsNew/en.lproj/Localizable.strings b/WhatsNew/WhatsNew/en.lproj/Localizable.strings new file mode 100644 index 000000000..08fffcb7b --- /dev/null +++ b/WhatsNew/WhatsNew/en.lproj/Localizable.strings @@ -0,0 +1,13 @@ +/* + Localizable.strings + WhatsNew + + Created by  Stepanok Ivan on 18.10.2023. + +*/ + +"TITLE" = "What's New"; +"BUTTON_PREVIOUS" = "Previous"; +"BUTTON_NEXT" = "Next"; +"BUTTON_DONE" = "Done"; + diff --git a/WhatsNew/WhatsNew/uk.lproj/Localizable.strings b/WhatsNew/WhatsNew/uk.lproj/Localizable.strings new file mode 100644 index 000000000..a0194425c --- /dev/null +++ b/WhatsNew/WhatsNew/uk.lproj/Localizable.strings @@ -0,0 +1,12 @@ +/* + Localizable.strings + WhatsNew + + Created by  Stepanok Ivan on 18.10.2023. + +*/ + +"TITLE" = "Що нового"; +"BUTTON_PREVIOUS" = "Назад"; +"BUTTON_NEXT" = "Далі"; +"BUTTON_DONE" = "Завершити"; diff --git a/WhatsNew/WhatsNewTests/Presentation/WhatsNewTests.swift b/WhatsNew/WhatsNewTests/Presentation/WhatsNewTests.swift new file mode 100644 index 000000000..4f43a51dd --- /dev/null +++ b/WhatsNew/WhatsNewTests/Presentation/WhatsNewTests.swift @@ -0,0 +1,27 @@ +// +// WhatsNewTests.swift +// WhatsNewTests +// +// Created by  Stepanok Ivan on 18.10.2023. +// + +import XCTest +@testable import WhatsNew + +final class WhatsNewTests: XCTestCase { + + func testGetVersion() throws { + let viewModel = WhatsNewViewModel(storage: WhatsNewStorageMock()) + let version = viewModel.getVersion() + XCTAssertNotNil(version) + XCTAssertTrue(version == "1.0") + } + + func testshouldShowWhatsNew() throws { + let viewModel = WhatsNewViewModel(storage: WhatsNewStorageMock()) + let version = viewModel.getVersion() + + XCTAssertNotNil(version) + XCTAssertTrue(viewModel.shouldShowWhatsNew()) + } +} diff --git a/WhatsNew/WhatsNewTests/WhatsNewMock.generated.swift b/WhatsNew/WhatsNewTests/WhatsNewMock.generated.swift new file mode 100644 index 000000000..f61f1556d --- /dev/null +++ b/WhatsNew/WhatsNewTests/WhatsNewMock.generated.swift @@ -0,0 +1,19 @@ +// Generated using Sourcery 1.8.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT + + +// Generated with SwiftyMocky 4.2.0 +// Required Sourcery: 1.8.0 + + +import SwiftyMocky +import XCTest +import Core +import WhatsNew +import Foundation +import SwiftUI +import Combine + + +// SwiftyMocky: no AutoMockable found. +// Please define and inherit from AutoMockable, or annotate protocols to be mocked diff --git a/WhatsNew/swiftgen.yml b/WhatsNew/swiftgen.yml new file mode 100644 index 000000000..9aa2d1c3b --- /dev/null +++ b/WhatsNew/swiftgen.yml @@ -0,0 +1,18 @@ +strings: + inputs: + - WhatsNew/en.lproj + outputs: + - templateName: structured-swift5 + params: + publicAccess: true + enumName: WhatsNewLocalization + output: WhatsNew/SwiftGen/Strings.swift +#xcassets: +# inputs: +# - WhatsNew/Assets.xcassets +# outputs: +# templateName: swift5 +# params: +# publicAccess: true +# enumName: WhatsNewAssets +# output: WhatsNew/SwiftGen/Assets.swift diff --git a/generateAllMocks.sh b/generateAllMocks.sh index ac1fe0dd6..0c4b5cfc7 100755 --- a/generateAllMocks.sh +++ b/generateAllMocks.sh @@ -12,4 +12,6 @@ cd ../Discovery cd ../Discussion ./../Pods/SwiftyMocky/bin/swiftymocky generate cd ../Profile +./../Pods/SwiftyMocky/bin/swiftymocky generate +cd ../WhatsNew ./../Pods/SwiftyMocky/bin/swiftymocky generate \ No newline at end of file