Commit 04542a02 by Demid Merzlyakov

IOS-155: overview view controller

parent 6a81dbf7
...@@ -261,6 +261,7 @@ ...@@ -261,6 +261,7 @@
CEC8FBAF2639756A0001A6BF /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */; }; CEC8FBAF2639756A0001A6BF /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */; };
CEC8FBB2263976240001A6BF /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBB1263976240001A6BF /* OnboardingCoordinator.swift */; }; CEC8FBB2263976240001A6BF /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBB1263976240001A6BF /* OnboardingCoordinator.swift */; };
CEC8FBB5263976400001A6BF /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBB4263976400001A6BF /* OnboardingViewModel.swift */; }; CEC8FBB5263976400001A6BF /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBB4263976400001A6BF /* OnboardingViewModel.swift */; };
CED4D66B26ED6A5E00ECF479 /* SubscriptionOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED4D66A26ED6A5E00ECF479 /* SubscriptionOverviewCoordinator.swift */; };
CEE1150626D987C5008FE415 /* WidgetLocationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE1150526D987C5008FE415 /* WidgetLocationSource.swift */; }; CEE1150626D987C5008FE415 /* WidgetLocationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE1150526D987C5008FE415 /* WidgetLocationSource.swift */; };
CEE8869526C30F680000161B /* OneWeatherUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5909CF26A59AAA00448579 /* OneWeatherUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; CEE8869526C30F680000161B /* OneWeatherUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5909CF26A59AAA00448579 /* OneWeatherUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
CEEB3547266F5D9900E16F90 /* BannerAdCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB3546266F5D9900E16F90 /* BannerAdCell.swift */; }; CEEB3547266F5D9900E16F90 /* BannerAdCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB3546266F5D9900E16F90 /* BannerAdCell.swift */; };
...@@ -569,6 +570,7 @@ ...@@ -569,6 +570,7 @@
CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; }; CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
CEC8FBB1263976240001A6BF /* OnboardingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinator.swift; sourceTree = "<group>"; }; CEC8FBB1263976240001A6BF /* OnboardingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinator.swift; sourceTree = "<group>"; };
CEC8FBB4263976400001A6BF /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; }; CEC8FBB4263976400001A6BF /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
CED4D66A26ED6A5E00ECF479 /* SubscriptionOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionOverviewCoordinator.swift; sourceTree = "<group>"; };
CEE1150526D987C5008FE415 /* WidgetLocationSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetLocationSource.swift; sourceTree = "<group>"; }; CEE1150526D987C5008FE415 /* WidgetLocationSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetLocationSource.swift; sourceTree = "<group>"; };
CEEB3546266F5D9900E16F90 /* BannerAdCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerAdCell.swift; sourceTree = "<group>"; }; CEEB3546266F5D9900E16F90 /* BannerAdCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerAdCell.swift; sourceTree = "<group>"; };
CEEB3548266F5DA900E16F90 /* MRECAdCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRECAdCell.swift; sourceTree = "<group>"; }; CEEB3548266F5DA900E16F90 /* MRECAdCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRECAdCell.swift; sourceTree = "<group>"; };
...@@ -746,6 +748,7 @@ ...@@ -746,6 +748,7 @@
CD7D3188268F33CC000D01FA /* WidgetPromotionCoordinator.swift */, CD7D3188268F33CC000D01FA /* WidgetPromotionCoordinator.swift */,
CD8579692671FA8100CC4CDA /* ShortsCoordinator.swift */, CD8579692671FA8100CC4CDA /* ShortsCoordinator.swift */,
CE6E410726EBA7C0009829AE /* SubscriptionCoordinator.swift */, CE6E410726EBA7C0009829AE /* SubscriptionCoordinator.swift */,
CED4D66A26ED6A5E00ECF479 /* SubscriptionOverviewCoordinator.swift */,
); );
path = Coordinators; path = Coordinators;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -1991,6 +1994,7 @@ ...@@ -1991,6 +1994,7 @@
CDEE8AD725DA882200C289DE /* ForecastPeriodButton.swift in Sources */, CDEE8AD725DA882200C289DE /* ForecastPeriodButton.swift in Sources */,
CE6E411426EBC0E9009829AE /* LocalizationChangeObserver.swift in Sources */, CE6E411426EBC0E9009829AE /* LocalizationChangeObserver.swift in Sources */,
CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */, CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */,
CED4D66B26ED6A5E00ECF479 /* SubscriptionOverviewCoordinator.swift in Sources */,
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */, CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */,
CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */, CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */,
CD8E041625F8F91B001785B6 /* ForecastCellFactory.swift in Sources */, CD8E041625F8F91B001785B6 /* ForecastCellFactory.swift in Sources */,
......
...@@ -44,4 +44,11 @@ class MenuCoordinator: Coordinator { ...@@ -44,4 +44,11 @@ class MenuCoordinator: Coordinator {
childCoordinators.append(subscriptionsCoordinator) childCoordinators.append(subscriptionsCoordinator)
subscriptionsCoordinator.start() subscriptionsCoordinator.start()
} }
public func openSubscriptionOverview() {
let overviewCoordinator = SubscriptionOverviewCoordinator(parentViewController: self.navigationController)
overviewCoordinator.parentCoordinator = self
childCoordinators.append(overviewCoordinator)
overviewCoordinator.start()
}
} }
...@@ -27,6 +27,13 @@ class SubscriptionCoordinator: Coordinator { ...@@ -27,6 +27,13 @@ class SubscriptionCoordinator: Coordinator {
self.parentViewController.present(vc, animated: true) self.parentViewController.present(vc, animated: true)
} }
public func openOverview() {
let overviewCoordinator = SubscriptionOverviewCoordinator(parentViewController: self.parentViewController)
overviewCoordinator.parentCoordinator = self
childCoordinators.append(overviewCoordinator)
overviewCoordinator.start()
}
func viewControllerDidEnd(controller: UIViewController) { func viewControllerDidEnd(controller: UIViewController) {
parentCoordinator?.childDidFinish(child: self) parentCoordinator?.childDidFinish(child: self)
} }
......
//
// SubscriptionOverviewCoordinator.swift
// 1Weather
//
// Created by Demid Merzlyakov on 12.09.2021.
//
import UIKit
import OneWeatherCore
class SubscriptionOverviewCoordinator: Coordinator {
private let parentViewController: UIViewController
public var childCoordinators = [Coordinator]()
public var parentCoordinator: Coordinator?
public init(parentViewController: UIViewController) {
self.parentViewController = parentViewController
}
func start() {
let vc = SubscriptionOverviewViewController(coordinator: self)
self.parentViewController.present(vc, animated: true)
}
func viewControllerDidEnd(controller: UIViewController) {
parentCoordinator?.childDidFinish(child: self)
}
}
...@@ -150,27 +150,27 @@ public class StoreManager { ...@@ -150,27 +150,27 @@ public class StoreManager {
} }
} }
public func purchase(subscription: SubscriptionConfig) { // public func purchase(subscription: SubscriptionConfig) {
let log = self.log // let log = self.log
let productId = subscription.productId // let productId = subscription.productId
log.info("Purchase \(productId) start.") // log.info("Purchase \(productId) start.")
let request: InAppRequest = SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in // let request: InAppRequest = SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in
defer { // defer {
self.activeInAppRequests[productId] = nil // self.activeInAppRequests[productId] = nil
} // }
switch result { // switch result {
case .success(let product): // case .success(let product):
if product.needsFinishTransaction { // if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction) // SwiftyStoreKit.finishTransaction(product.transaction)
} // }
case .error(error: let error): // case .error(error: let error):
log.error("Purchase: error for \(productId): \(error)") // log.error("Purchase: error for \(productId): \(error)")
} // }
} // }
activeInAppRequests[productId] = request // activeInAppRequests[productId] = request
} // }
public func purchase(product: SKProduct) { public func purchase(product: SKProduct, completion: @escaping (Bool) -> ()) {
let log = self.log let log = self.log
log.info("Purchase \(product.productIdentifier) start (SK).") log.info("Purchase \(product.productIdentifier) start (SK).")
SwiftyStoreKit.purchaseProduct(product) { result in SwiftyStoreKit.purchaseProduct(product) { result in
...@@ -179,8 +179,10 @@ public class StoreManager { ...@@ -179,8 +179,10 @@ public class StoreManager {
if product.needsFinishTransaction { if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction) SwiftyStoreKit.finishTransaction(product.transaction)
} }
completion(true)
case .error(error: let error): case .error(error: let error):
log.error("Purchase: error for \(product.productIdentifier): \(error)") log.error("Purchase: error for \(product.productIdentifier): \(error)")
completion(false)
} }
} }
} }
......
...@@ -198,6 +198,7 @@ ...@@ -198,6 +198,7 @@
"menu.radar" = "Radar"; "menu.radar" = "Radar";
"menu.buyNow" = "Buy now"; "menu.buyNow" = "Buy now";
"menu.settings" = "Settings"; "menu.settings" = "Settings";
"menu.subscriptionOverview" = "Premium";
"menu.about" = "About us"; "menu.about" = "About us";
"menu.ad" = "Ad choices"; "menu.ad" = "Ad choices";
"menu.rateUs" = "Rate us"; "menu.rateUs" = "Rate us";
...@@ -228,6 +229,7 @@ ...@@ -228,6 +229,7 @@
"subscription.description.items.aqi" = "AQI card"; "subscription.description.items.aqi" = "AQI card";
"subscription.description.items.comingSoon" = "Coming Soon"; "subscription.description.items.comingSoon" = "Coming Soon";
"subscription.description.items.minutely" = "Minutely Forecast"; "subscription.description.items.minutely" = "Minutely Forecast";
"subscription.description.cancellation" = "In order to cancel subscription, please open App Store > Account > Subscriptions and select 1Weather.";
"subscription.button.buy.yearly" = "Subscribe yearly for #PRICE#"; "subscription.button.buy.yearly" = "Subscribe yearly for #PRICE#";
"subscription.button.buy.monthly" = "Subscribe monthly for #PRICE#"; "subscription.button.buy.monthly" = "Subscribe monthly for #PRICE#";
......
...@@ -9,6 +9,7 @@ import UIKit ...@@ -9,6 +9,7 @@ import UIKit
public enum MenuRow { public enum MenuRow {
case settings case settings
case subscriptionOverview
case about case about
case ad case ad
case rateUs case rateUs
...@@ -35,6 +36,10 @@ public enum MenuRow { ...@@ -35,6 +36,10 @@ public enum MenuRow {
return UIImage(named: "menu_privacyPolicy") return UIImage(named: "menu_privacyPolicy")
case .deviceId: case .deviceId:
return UIImage(named: "menu_device_id") return UIImage(named: "menu_device_id")
case .subscriptionOverview:
return nil
#warning("Not implemented!")
//TODO: Implement!
} }
} }
...@@ -56,15 +61,24 @@ public enum MenuRow { ...@@ -56,15 +61,24 @@ public enum MenuRow {
return "menu.privacy".localized() return "menu.privacy".localized()
case .deviceId: case .deviceId:
return "menu.deviceId".localized() return "menu.deviceId".localized()
case .subscriptionOverview:
return "menu.subscriptionOverview".localized()
} }
} }
var roundedCorners:CACornerMask { var roundedCorners:CACornerMask {
switch self { switch self {
case .settings: case .settings:
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] if StoreManager.shared.hasSubscription {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
else {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
case .about: case .about:
return [.layerMinXMinYCorner, .layerMaxXMinYCorner] return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
case .subscriptionOverview:
return [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
case .ad: case .ad:
return [] return []
case .rateUs: case .rateUs:
...@@ -94,8 +108,23 @@ private struct SectionItem { ...@@ -94,8 +108,23 @@ private struct SectionItem {
class MenuCellFactory<T>: CellFactory { class MenuCellFactory<T>: CellFactory {
//Private //Private
private let menuViewModel:MenuViewModel private let menuViewModel:MenuViewModel
private let sections:[SectionItem] = [SectionItem(type: .info, rows: [.settings]), private let sections: [SectionItem] = {
SectionItem(type: .settings, rows: [.about, .ad, .help, .faq, .privacy, .deviceId])]
let infoSectionItem: SectionItem
if StoreManager.shared.hasSubscription {
infoSectionItem = SectionItem(type: .info,
rows: [.settings, .subscriptionOverview])
}
else {
infoSectionItem = SectionItem(type: .info,
rows: [.settings])
}
let settingsSectionItem = SectionItem(type: .settings,
rows: [.about, .ad, .help, .faq, .privacy, .deviceId])
return [infoSectionItem, settingsSectionItem]
}()
//Public //Public
public var kSectionHeight:CGFloat { public var kSectionHeight:CGFloat {
......
...@@ -147,6 +147,8 @@ extension MenuViewController: UITableViewDelegate { ...@@ -147,6 +147,8 @@ extension MenuViewController: UITableViewDelegate {
viewModel.viewPrivacyPolicy() viewModel.viewPrivacyPolicy()
case .deviceId: case .deviceId:
viewModel.showDeviceId() viewModel.showDeviceId()
case .subscriptionOverview:
coordinator.openSubscriptionOverview()
default: default:
break break
} }
......
...@@ -7,6 +7,74 @@ ...@@ -7,6 +7,74 @@
import UIKit import UIKit
/// A screen where the user is thanked for purchasing a subscription. It is shown after a successful subscription purchase or if the user goes into the Menu and chooses "Premium" item there.
/// https://zpl.io/aBPo75K
class SubscriptionOverviewViewController: UIViewController { class SubscriptionOverviewViewController: UIViewController {
private let coordinator: SubscriptionOverviewCoordinator
private let scrollView = UIScrollView()
private let scrollViewContent = UIView()
private let topHeaderView = SubscriptionTopView()
private let descriptionView = SubscriptionDescriptionView(alreadyPurchased: true)
let footerView = UILabel()
init(coordinator: SubscriptionOverviewCoordinator) {
self.coordinator = coordinator
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) { return nil }
public override func viewDidLoad() {
super.viewDidLoad()
prepareViewController()
prepareFooter()
prepareScrollView()
}
}
//MARK: - UI Setup
extension SubscriptionOverviewViewController {
private func prepareViewController() {
view.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
}
private func prepareScrollView() {
view.addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
scrollView.addSubview(scrollViewContent)
scrollViewContent.snp.makeConstraints { make in
make.top.bottom.left.right.equalToSuperview()
make.width.equalToSuperview()
}
scrollViewContent.addSubview(topHeaderView)
topHeaderView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.left.right.equalToSuperview().inset(24)
}
scrollViewContent.addSubview(descriptionView)
descriptionView.snp.makeConstraints { make in
make.top.equalTo(topHeaderView.snp.bottom).offset(24)
make.left.right.equalToSuperview().inset(24)
}
scrollViewContent.addSubview(footerView)
footerView.snp.makeConstraints { make in
make.top.equalTo(descriptionView.snp.bottom).offset(40)
make.left.right.equalToSuperview().inset(24)
make.bottom.equalToSuperview().inset(100)
}
}
private func prepareFooter() {
footerView.font = AppFont.SFPro.regular(size: 14)
footerView.textColor = ThemeManager.currentTheme.secondaryTextColor
footerView.numberOfLines = 0
footerView.text = "subscription.description.cancellation".localized()
}
} }
...@@ -28,6 +28,7 @@ public class SubscriptionStoreViewController: UIViewController { ...@@ -28,6 +28,7 @@ public class SubscriptionStoreViewController: UIViewController {
self.coordinator = coordinator self.coordinator = coordinator
self.viewModel = viewModel self.viewModel = viewModel
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
self.viewModel.delegate = self
} }
@available(*, unavailable) @available(*, unavailable)
...@@ -130,3 +131,10 @@ extension SubscriptionStoreViewController: SubscriptionPurchaseButtonDelegate { ...@@ -130,3 +131,10 @@ extension SubscriptionStoreViewController: SubscriptionPurchaseButtonDelegate {
viewModel.purchase(subscription: product) viewModel.purchase(subscription: product)
} }
} }
extension SubscriptionStoreViewController: SubscriptionViewModelDelegate {
func viewModel(_ vm: SubscriptionViewModel, finishedSubscriptionPurchaseWithResult result: Bool) {
if result {
coordinator.openOverview()
}
}
}
...@@ -9,7 +9,12 @@ import Foundation ...@@ -9,7 +9,12 @@ import Foundation
import OneWeatherCore import OneWeatherCore
import StoreKit import StoreKit
protocol SubscriptionViewModelDelegate: ViewModelDelegate {
func viewModel(_ vm: SubscriptionViewModel, finishedSubscriptionPurchaseWithResult result: Bool)
}
class SubscriptionViewModel: ViewModelProtocol { class SubscriptionViewModel: ViewModelProtocol {
public weak var delegate: SubscriptionViewModelDelegate?
public let storeManager: StoreManager public let storeManager: StoreManager
public init(storeManager: StoreManager) { public init(storeManager: StoreManager) {
...@@ -21,6 +26,10 @@ class SubscriptionViewModel: ViewModelProtocol { ...@@ -21,6 +26,10 @@ class SubscriptionViewModel: ViewModelProtocol {
} }
public func purchase(subscription: SKProduct) { public func purchase(subscription: SKProduct) {
storeManager.purchase(product: subscription) storeManager.purchase(product: subscription) { success in
onMain {
self.delegate?.viewModel(self, finishedSubscriptionPurchaseWithResult: success)
}
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment