Skip to content

Commit

Permalink
Vkuznetsov/course upgrade UI (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
forgotvas authored Jun 12, 2024
1 parent e6dc12c commit 18ba99d
Show file tree
Hide file tree
Showing 90 changed files with 3,143 additions and 985 deletions.
221 changes: 175 additions & 46 deletions Authorization/AuthorizationTests/AuthorizationMock.generated.swift

Large diffs are not rendered by default.

69 changes: 64 additions & 5 deletions Core/Core.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Core/Core/Assets.xcassets/UpgradeAccess/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "arrow.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "calendar.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "checkmark.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions Core/Core/Configuration/BaseRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ public protocol BaseRouter {
func presentView(transitionStyle: UIModalTransitionStyle, view: any View, completion: (() -> Void)?)

func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View)
func presentNativeAlert(title: String?, message: String?, actions: [UIAlertAction])
@MainActor
func showUpgradeInfo(
productName: String,
message: String,
sku: String,
courseID: String,
screen: CourseUpgradeScreen,
Expand Down Expand Up @@ -142,9 +144,11 @@ open class BaseRouterMock: BaseRouter {

public func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) {}

public func presentNativeAlert(title: String?, message: String?, actions: [UIAlertAction]) {}
@MainActor
public func showUpgradeInfo(
productName: String,
message: String,
sku: String,
courseID: String,
screen: CourseUpgradeScreen,
Expand Down
151 changes: 89 additions & 62 deletions Core/Core/CourseUpgrade/CourseUpgradeHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,57 +228,79 @@ extension CourseUpgradeHelper {
}

func showError() {
guard let topController = UIApplication.topViewController() else { return }
// not showing any error if payment is canceled by user
if case .error(let error) = upgradeHadler?.state {
if error.isCancelled { return }

let alertController = UIAlertController().showAlert(
withTitle: CoreLocalization.CourseUpgrade.FailureAlert.alertTitle,
message: error.localizedDescription,
onViewController: topController) { _, _, _ in }
var actions: [UIAlertAction] = []

if case .verifyReceiptError(let nestedError) = error, nestedError.errorCode != 409 {
alertController.addButton(
withTitle: CoreLocalization.CourseUpgrade.FailureAlert.refreshToRetry,
style: .default) {[weak self] _ in
guard let self = self else { return }
self.trackUpgradeErrorAction(errorAction: .refreshToRetry, error: error)
Task {
await self.upgradeHadler?.reverifyPayment()
actions.append(
UIAlertAction(
title: CoreLocalization.CourseUpgrade.FailureAlert.refreshToRetry,
style: .default,
handler: { [weak self] _ in
guard let self = self else { return }
self.trackUpgradeErrorAction(errorAction: .refreshToRetry, error: error)
Task {
await self.upgradeHadler?.reverifyPayment()
}
}
}
)
)
}

if case .complete = upgradeHadler?.state, completion != nil {
alertController.addButton(
withTitle: CoreLocalization.CourseUpgrade.FailureAlert.refreshToRetry,
style: .default) {[weak self] _ in
self?.trackUpgradeErrorAction(errorAction: .refreshToRetry, error: error)
self?.showLoader()
self?.completion?()
self?.completion = nil
}
actions.append(
UIAlertAction(
title: CoreLocalization.CourseUpgrade.FailureAlert.refreshToRetry,
style: .default,
handler: { [weak self] _ in
self?.trackUpgradeErrorAction(errorAction: .refreshToRetry, error: error)
self?.showLoader()
self?.completion?()
self?.completion = nil
}
)
)
}

alertController.addButton(withTitle: CoreLocalization.CourseUpgrade.FailureAlert.getHelp) { [weak self] _ in
guard let self = self else { return }
self.trackUpgradeErrorAction(errorAction: .emailSupport, error: error)
self.hideAlertAction()
Task { @MainActor in
await self.router.hideUpgradeLoaderView(animated: true)
}
self.launchEmailComposer(errorMessage: "Error: \(error.formattedError)")
}
actions.append(
UIAlertAction(
title: CoreLocalization.CourseUpgrade.FailureAlert.getHelp,
style: .default,
handler: { [weak self] _ in
guard let self = self else { return }
self.trackUpgradeErrorAction(errorAction: .emailSupport, error: error)
self.hideAlertAction()
Task { @MainActor in
await self.router.hideUpgradeLoaderView(animated: true)
}
self.launchEmailComposer(errorMessage: "Error: \(error.formattedError)")
}
)
)

alertController.addButton(withTitle: CoreLocalization.close, style: .default) { [weak self] _ in
guard let self = self else { return }
Task { @MainActor in
await self.router.hideUpgradeLoaderView(animated: true)
self.trackUpgradeErrorAction(errorAction: .close, error: error)
self.hideAlertAction()
}
}
actions.append(
UIAlertAction(
title: CoreLocalization.close,
style: .default,
handler: { [weak self] _ in
guard let self = self else { return }
Task { @MainActor in
await self.router.hideUpgradeLoaderView(animated: true)
self.trackUpgradeErrorAction(errorAction: .close, error: error)
self.hideAlertAction()
}
}
)
)

router.presentNativeAlert(
title: CoreLocalization.CourseUpgrade.FailureAlert.alertTitle,
message: error.localizedDescription,
actions: actions
)
}
}

Expand Down Expand Up @@ -311,14 +333,13 @@ extension CourseUpgradeHelper {
if removeView == true {
Task {@MainActor in
await router.hideUpgradeLoaderView(animated: true)
}

helperModel = nil

if success == true {
showSuccess()
} else {
showError()
helperModel = nil

if success == true {
showSuccess()
} else {
showError()
}
}
} else if success == false {
showError()
Expand All @@ -328,26 +349,32 @@ extension CourseUpgradeHelper {

extension CourseUpgradeHelper {
private func showSilentRefreshAlert() {
guard let topController = UIApplication.topViewController() else { return }

let alertController = UIAlertController().alert(
withTitle: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertTitle,
message: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertMessage) { _, _, _ in }
var actions: [UIAlertAction] = []

alertController.addButton(
withTitle: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertRefresh,
style: .default) {[weak self] _ in
actions.append(
UIAlertAction(
title: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertRefresh,
style: .default
) { [weak self] _ in
self?.showDashboardScreen()
self?.postSuccessNotification(showLoader: true)
}

alertController.addButton(
withTitle: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertContinue,
style: .default) {[weak self] _ in
self?.reset()
}

topController.present(alertController, animated: true, completion: nil)
)

actions.append(
UIAlertAction(
title: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertContinue,
style: .default
) { [weak self] _ in
self?.reset()
}
)

router.presentNativeAlert(
title: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertTitle,
message: CoreLocalization.CourseUpgrade.SuccessAlert.silentAlertMessage,
actions: actions
)
}

public func showRestorePurchasesAlert() {
Expand Down
45 changes: 45 additions & 0 deletions Core/Core/CourseUpgrade/Modifiers/PaymentSnackbarModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// PaymentSnackbarModifier.swift
// Core
//
// Created by Vadim Kuznetsov on 11.06.24.
//

import SwiftUI
import Theme

public struct PaymentSnackbarModifier: ViewModifier {
@StateObject var viewModel: PaymentSnackbarModifierViewModel = .init()
public func body(content: Content) -> some View {
content
.onAppear {
viewModel.isOnScreen = true
}
.onDisappear {
viewModel.isOnScreen = false
}
.overlay(alignment: .bottom) {
ZStack(alignment: .bottom) {
if viewModel.showPaymentSuccess {
PaymentSnakbarView()
.transition(.move(edge: .bottom))
.onAppear {
doAfter(Theme.Timeout.snackbarMessageLongTimeout) {
viewModel.showPaymentSuccess = false
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.animation(.easeInOut, value: viewModel.showPaymentSuccess)
.allowsHitTesting(false)
.accessibilityHidden(true)
}
}
}

extension View {
public func paymentSnackbar() -> some View {
modifier(PaymentSnackbarModifier())
}
}
40 changes: 40 additions & 0 deletions Core/Core/CourseUpgrade/View/PaymentSnakbarView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// PaymentSnakbarView.swift
// Core
//
// Created by Vadim Kuznetsov on 11.06.24.
//

import SwiftUI
import Theme

public struct PaymentSnakbarView: View {
public var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(CoreLocalization.CourseUpgrade.Snackbar.title)
.font(Theme.Fonts.titleMedium)
.foregroundColor(Theme.Colors.textPrimary)

Text(CoreLocalization.CourseUpgrade.Snackbar.successMessage)
.frame(maxWidth: .infinity, alignment: .leading)
.font(Theme.Fonts.labelLarge)
.foregroundColor(Theme.Colors.textPrimary)
}
.padding(20)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Theme.Colors.datesSectionStroke, lineWidth: 2)
)
.background(Theme.Colors.datesSectionBackground)
.clipShape(
RoundedRectangle(cornerRadius: 8)
)
.padding(16)
}
}

#if DEBUG
#Preview {
PaymentSnakbarView()
}
#endif
Loading

0 comments on commit 18ba99d

Please sign in to comment.