Commit 04542a02 by Demid Merzlyakov

IOS-155: overview view controller

parent 6a81dbf7
......@@ -261,6 +261,7 @@
CEC8FBAF2639756A0001A6BF /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */; };
CEC8FBB2263976240001A6BF /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBB1263976240001A6BF /* OnboardingCoordinator.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 */; };
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 */; };
......@@ -569,6 +570,7 @@
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>"; };
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>"; };
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>"; };
......@@ -746,6 +748,7 @@
CD7D3188268F33CC000D01FA /* WidgetPromotionCoordinator.swift */,
CD8579692671FA8100CC4CDA /* ShortsCoordinator.swift */,
CE6E410726EBA7C0009829AE /* SubscriptionCoordinator.swift */,
CED4D66A26ED6A5E00ECF479 /* SubscriptionOverviewCoordinator.swift */,
);
path = Coordinators;
sourceTree = "<group>";
......@@ -1991,6 +1994,7 @@
CDEE8AD725DA882200C289DE /* ForecastPeriodButton.swift in Sources */,
CE6E411426EBC0E9009829AE /* LocalizationChangeObserver.swift in Sources */,
CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */,
CED4D66B26ED6A5E00ECF479 /* SubscriptionOverviewCoordinator.swift in Sources */,
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */,
CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */,
CD8E041625F8F91B001785B6 /* ForecastCellFactory.swift in Sources */,
......
......@@ -44,4 +44,11 @@ class MenuCoordinator: Coordinator {
childCoordinators.append(subscriptionsCoordinator)
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 {
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) {
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 {
}
}
public func purchase(subscription: SubscriptionConfig) {
let log = self.log
let productId = subscription.productId
log.info("Purchase \(productId) start.")
let request: InAppRequest = SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in
defer {
self.activeInAppRequests[productId] = nil
}
switch result {
case .success(let product):
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
case .error(error: let error):
log.error("Purchase: error for \(productId): \(error)")
}
}
activeInAppRequests[productId] = request
}
// public func purchase(subscription: SubscriptionConfig) {
// let log = self.log
// let productId = subscription.productId
// log.info("Purchase \(productId) start.")
// let request: InAppRequest = SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in
// defer {
// self.activeInAppRequests[productId] = nil
// }
// switch result {
// case .success(let product):
// if product.needsFinishTransaction {
// SwiftyStoreKit.finishTransaction(product.transaction)
// }
// case .error(error: let error):
// log.error("Purchase: error for \(productId): \(error)")
// }
// }
// activeInAppRequests[productId] = request
// }
public func purchase(product: SKProduct) {
public func purchase(product: SKProduct, completion: @escaping (Bool) -> ()) {
let log = self.log
log.info("Purchase \(product.productIdentifier) start (SK).")
SwiftyStoreKit.purchaseProduct(product) { result in
......@@ -179,8 +179,10 @@ public class StoreManager {
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
completion(true)
case .error(error: let error):
log.error("Purchase: error for \(product.productIdentifier): \(error)")
completion(false)
}
}
}
......
......@@ -198,6 +198,7 @@
"menu.radar" = "Radar";
"menu.buyNow" = "Buy now";
"menu.settings" = "Settings";
"menu.subscriptionOverview" = "Premium";
"menu.about" = "About us";
"menu.ad" = "Ad choices";
"menu.rateUs" = "Rate us";
......@@ -228,6 +229,7 @@
"subscription.description.items.aqi" = "AQI card";
"subscription.description.items.comingSoon" = "Coming Soon";
"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.monthly" = "Subscribe monthly for #PRICE#";
......
......@@ -9,6 +9,7 @@ import UIKit
public enum MenuRow {
case settings
case subscriptionOverview
case about
case ad
case rateUs
......@@ -35,6 +36,10 @@ public enum MenuRow {
return UIImage(named: "menu_privacyPolicy")
case .deviceId:
return UIImage(named: "menu_device_id")
case .subscriptionOverview:
return nil
#warning("Not implemented!")
//TODO: Implement!
}
}
......@@ -56,15 +61,24 @@ public enum MenuRow {
return "menu.privacy".localized()
case .deviceId:
return "menu.deviceId".localized()
case .subscriptionOverview:
return "menu.subscriptionOverview".localized()
}
}
var roundedCorners:CACornerMask {
switch self {
case .settings:
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
if StoreManager.shared.hasSubscription {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
else {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
case .about:
return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
case .subscriptionOverview:
return [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
case .ad:
return []
case .rateUs:
......@@ -94,8 +108,23 @@ private struct SectionItem {
class MenuCellFactory<T>: CellFactory {
//Private
private let menuViewModel:MenuViewModel
private let sections:[SectionItem] = [SectionItem(type: .info, rows: [.settings]),
SectionItem(type: .settings, rows: [.about, .ad, .help, .faq, .privacy, .deviceId])]
private let sections: [SectionItem] = {
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 var kSectionHeight:CGFloat {
......
......@@ -147,6 +147,8 @@ extension MenuViewController: UITableViewDelegate {
viewModel.viewPrivacyPolicy()
case .deviceId:
viewModel.showDeviceId()
case .subscriptionOverview:
coordinator.openSubscriptionOverview()
default:
break
}
......
......@@ -7,6 +7,74 @@
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 {
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 {
self.coordinator = coordinator
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.viewModel.delegate = self
}
@available(*, unavailable)
......@@ -130,3 +131,10 @@ extension SubscriptionStoreViewController: SubscriptionPurchaseButtonDelegate {
viewModel.purchase(subscription: product)
}
}
extension SubscriptionStoreViewController: SubscriptionViewModelDelegate {
func viewModel(_ vm: SubscriptionViewModel, finishedSubscriptionPurchaseWithResult result: Bool) {
if result {
coordinator.openOverview()
}
}
}
......@@ -9,7 +9,12 @@ import Foundation
import OneWeatherCore
import StoreKit
protocol SubscriptionViewModelDelegate: ViewModelDelegate {
func viewModel(_ vm: SubscriptionViewModel, finishedSubscriptionPurchaseWithResult result: Bool)
}
class SubscriptionViewModel: ViewModelProtocol {
public weak var delegate: SubscriptionViewModelDelegate?
public let storeManager: StoreManager
public init(storeManager: StoreManager) {
......@@ -21,6 +26,10 @@ class SubscriptionViewModel: ViewModelProtocol {
}
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