Commit cfd25127 by Dmitry Stepanets

- Added in-app checked

- Updated UI
parent f1cc460a
...@@ -164,7 +164,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -164,7 +164,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidBecomeActive(_ application: UIApplication) { func applicationDidBecomeActive(_ application: UIApplication) {
LocationManager.shared.updateEverythingIfNeeded() LocationManager.shared.updateEverythingIfNeeded()
storeManager.verifySubscriptions()
storeManager.verifyPurchases()
if #available(iOS 14, *) { if #available(iOS 14, *) {
WidgetManager.shared.refreshAnalytics() WidgetManager.shared.refreshAnalytics()
......
...@@ -4,7 +4,21 @@ ...@@ -4,7 +4,21 @@
], ],
"products" : [ "products" : [
{
"displayPrice" : "1.99",
"familyShareable" : false,
"internalID" : "22A7BDE0",
"localizations" : [
{
"description" : "",
"displayName" : "Upgrade to 1Weather Pro",
"locale" : "en_US"
}
],
"productID" : "com.onelouder.oneweather.inapp1",
"referenceName" : "1Weather Pro ",
"type" : "NonConsumable"
}
], ],
"settings" : { "settings" : {
"_timeRate" : 6 "_timeRate" : 6
......
...@@ -91,7 +91,7 @@ public class StoreManager { ...@@ -91,7 +91,7 @@ public class StoreManager {
self.checkSubscriptionExpirationLocally() self.checkSubscriptionExpirationLocally()
self.completeTransactions() self.completeTransactions()
self.updateProductInfo() self.updateProductInfo()
self.verifySubscriptions() self.verifyPurchases()
#warning("Not implemented!") #warning("Not implemented!")
//TODO: implement //TODO: implement
...@@ -187,9 +187,44 @@ public class StoreManager { ...@@ -187,9 +187,44 @@ public class StoreManager {
} }
} }
public func verifySubscriptions() { public func purchaseInApp() {
let log = self.log let log = self.log
log.info("Verify subscriptions...") log.info("Purchase In-app start")
SwiftyStoreKit.purchaseProduct(kInAppOneWeatherProId, atomically: true) { result in
switch result {
case .success(let product):
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
log.info("In app bought")
self.removeAdsPurchased = true
case .error(error: let error):
log.error("Purchase in-app: error for \(kInAppOneWeatherProId): \(error)")
}
}
}
public func restoreInApp() {
SwiftyStoreKit.restorePurchases(atomically: true) {[weak self] results in
if results.restoreFailedPurchases.count > 0 {
self?.log.error("Restore Failed: \(results.restoreFailedPurchases)")
}
else if results.restoredPurchases.count > 0 {
let ids = results.restoredPurchases.compactMap{ $0.productId }
if (ids.contains{ $0 == kInAppOneWeatherProId }) {
self?.log.info("Restore Success: \(results.restoredPurchases)")
self?.removeAdsPurchased = true
}
}
else {
self?.log.info("Nothing to Restore")
}
}
}
public func verifyPurchases() {
let log = self.log
log.info("Verify purchases...")
let service: AppleReceiptValidator.VerifyReceiptURLType let service: AppleReceiptValidator.VerifyReceiptURLType
#if DEBUG #if DEBUG
service = .sandbox service = .sandbox
...@@ -201,6 +236,16 @@ public class StoreManager { ...@@ -201,6 +236,16 @@ public class StoreManager {
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
switch result { switch result {
case .success(let receipt): case .success(let receipt):
//In-app
let purchaseInAppResult = SwiftyStoreKit.verifyPurchase(productId: kInAppOneWeatherProId, inReceipt: receipt)
switch purchaseInAppResult {
case .purchased:
self.removeAdsPurchased = true
case .notPurchased:
self.removeAdsPurchased = false
}
//Subscriptions
let purchaseResult = SwiftyStoreKit.verifySubscriptions(ofType: .autoRenewable, productIds: self.allSubscriptionIds, inReceipt: receipt) let purchaseResult = SwiftyStoreKit.verifySubscriptions(ofType: .autoRenewable, productIds: self.allSubscriptionIds, inReceipt: receipt)
switch purchaseResult { switch purchaseResult {
case .purchased(let expiryDate, let items): case .purchased(let expiryDate, let items):
...@@ -223,11 +268,11 @@ public class StoreManager { ...@@ -223,11 +268,11 @@ public class StoreManager {
log.error("Receipt verification failed: \(error)") log.error("Receipt verification failed: \(error)")
self.hasSubscription = false self.hasSubscription = false
self.everHadSubscription = false self.everHadSubscription = false
self.removeAdsPurchased = false
self.subscriptionExpirationDate = nil self.subscriptionExpirationDate = nil
} }
} }
} }
//MARK: - Observers management //MARK: - Observers management
public func add(observer: StoreManagerObserver) { public func add(observer: StoreManagerObserver) {
...@@ -264,6 +309,6 @@ public class StoreManager { ...@@ -264,6 +309,6 @@ public class StoreManager {
extension StoreManager: ConfigManagerDelegate { extension StoreManager: ConfigManagerDelegate {
public func dataUpdated(by configManager: ConfigManager) { public func dataUpdated(by configManager: ConfigManager) {
self.updateProductInfo() self.updateProductInfo()
self.verifySubscriptions() self.verifyPurchases()
} }
} }
{
"images" : [
{
"filename" : "menu_restore.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
...@@ -202,6 +202,7 @@ ...@@ -202,6 +202,7 @@
"menu.upgradeNow" = "Upgrade now"; "menu.upgradeNow" = "Upgrade now";
"menu.settings" = "Settings"; "menu.settings" = "Settings";
"menu.subscriptionOverview" = "Premium"; "menu.subscriptionOverview" = "Premium";
"menu.restorePurchases" = "Restore purchases";
"menu.about" = "About us"; "menu.about" = "About us";
"menu.ad" = "Ad choices"; "menu.ad" = "Ad choices";
"menu.rateUs" = "Rate us"; "menu.rateUs" = "Rate us";
......
...@@ -10,6 +10,7 @@ import UIKit ...@@ -10,6 +10,7 @@ import UIKit
public enum MenuRow { public enum MenuRow {
case settings case settings
case subscriptionOverview case subscriptionOverview
case restorePurchases
case about case about
case ad case ad
case rateUs case rateUs
...@@ -38,6 +39,8 @@ public enum MenuRow { ...@@ -38,6 +39,8 @@ public enum MenuRow {
return UIImage(named: "menu_device_id") return UIImage(named: "menu_device_id")
case .subscriptionOverview: case .subscriptionOverview:
return UIImage(named: "menu_crown") return UIImage(named: "menu_crown")
case .restorePurchases:
return UIImage(named: "menu_restore")
} }
} }
...@@ -61,22 +64,26 @@ public enum MenuRow { ...@@ -61,22 +64,26 @@ public enum MenuRow {
return "menu.deviceId".localized() return "menu.deviceId".localized()
case .subscriptionOverview: case .subscriptionOverview:
return "menu.subscriptionOverview".localized() return "menu.subscriptionOverview".localized()
case .restorePurchases:
return "menu.restorePurchases".localized()
} }
} }
var roundedCorners:CACornerMask { var roundedCorners:CACornerMask {
switch self { switch self {
case .settings: case .settings:
if StoreManager.shared.hasSubscription { if !StoreManager.shared.hasSubscription && StoreManager.shared.removeAdsPurchased {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner] return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
} }
else { else {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} }
case .about: case .restorePurchases:
return [.layerMinXMinYCorner, .layerMaxXMinYCorner] return [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
case .subscriptionOverview: case .subscriptionOverview:
return [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] return [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
case .about:
return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
case .ad: case .ad:
return [] return []
case .rateUs: case .rateUs:
...@@ -131,8 +138,14 @@ class MenuCellFactory<T>: CellFactory { ...@@ -131,8 +138,14 @@ class MenuCellFactory<T>: CellFactory {
rows: [.settings, .subscriptionOverview]) rows: [.settings, .subscriptionOverview])
} }
else { else {
infoSectionItem = SectionItem(type: .info, if StoreManager.shared.removeAdsPurchased {
rows: [.settings]) infoSectionItem = SectionItem(type: .info,
rows: [.settings])
}
else {
infoSectionItem = SectionItem(type: .info,
rows: [.settings, .restorePurchases])
}
} }
let settingsSectionItem = SectionItem(type: .settings, let settingsSectionItem = SectionItem(type: .settings,
......
...@@ -23,6 +23,7 @@ class MenuHeaderView: UIView { ...@@ -23,6 +23,7 @@ class MenuHeaderView: UIView {
prepareView() prepareView()
preparePremium() preparePremium()
updateUI() updateUI()
reload()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
...@@ -34,6 +35,37 @@ class MenuHeaderView: UIView { ...@@ -34,6 +35,37 @@ class MenuHeaderView: UIView {
updateUI() updateUI()
} }
public func reload() {
if StoreManager.shared.removeAdsPurchased {
premiumHeadingLabel.font = AppFont.SFPro.regular(size: 16)
premiumHeadingLabel.text = "menu.upgradeTo".localized()
let descParagpraphStyle = NSMutableParagraphStyle()
descParagpraphStyle.minimumLineHeight = 20
let descriptionAttrString = NSAttributedString(string: "menu.premiumMembership".localized(),
attributes: [.foregroundColor : UIColor.white,
.paragraphStyle : descParagpraphStyle,
.font : AppFont.SFPro.bold(size: 24)])
premiumDescriptionLabel.attributedText = descriptionAttrString
buyButton.setTitle("menu.upgradeNow".localized(), for: .normal)
}
else {
premiumHeadingLabel.font = AppFont.SFPro.bold(size: 23)
premiumHeadingLabel.text = "menu.goPremium".localized()
let descParagpraphStyle = NSMutableParagraphStyle()
descParagpraphStyle.minimumLineHeight = 20
let descriptionAttrString = NSAttributedString(string: "menu.premium.desc".localized(),
attributes: [.foregroundColor : UIColor.white,
.paragraphStyle : descParagpraphStyle,
.font : AppFont.SFPro.regular(size: 14)])
premiumDescriptionLabel.attributedText = descriptionAttrString
buyButton.setTitle("menu.buyNow".localized(), for: .normal)
}
}
//Prvaite //Prvaite
private func updateUI() { private func updateUI() {
backgroundColor = ThemeManager.currentTheme.baseBackgroundColor backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
...@@ -80,35 +112,6 @@ private extension MenuHeaderView { ...@@ -80,35 +112,6 @@ private extension MenuHeaderView {
buyButton.addTarget(self, action: #selector(handleBuyButton), for: .touchUpInside) buyButton.addTarget(self, action: #selector(handleBuyButton), for: .touchUpInside)
premiumContainer.addSubview(buyButton) premiumContainer.addSubview(buyButton)
if StoreManager.shared.removeAdsPurchased {
premiumHeadingLabel.font = AppFont.SFPro.regular(size: 16)
premiumHeadingLabel.text = "menu.upgradeTo".localized()
let descParagpraphStyle = NSMutableParagraphStyle()
descParagpraphStyle.minimumLineHeight = 20
let descriptionAttrString = NSAttributedString(string: "menu.premiumMembership".localized(),
attributes: [.foregroundColor : UIColor.white,
.paragraphStyle : descParagpraphStyle,
.font : AppFont.SFPro.bold(size: 24)])
premiumDescriptionLabel.attributedText = descriptionAttrString
buyButton.setTitle("menu.upgradeNow".localized(), for: .normal)
}
else {
premiumHeadingLabel.font = AppFont.SFPro.bold(size: 23)
premiumHeadingLabel.text = "menu.goPremium".localized()
let descParagpraphStyle = NSMutableParagraphStyle()
descParagpraphStyle.minimumLineHeight = 20
let descriptionAttrString = NSAttributedString(string: "menu.premium.desc".localized(),
attributes: [.foregroundColor : UIColor.white,
.paragraphStyle : descParagpraphStyle,
.font : AppFont.SFPro.regular(size: 14)])
premiumDescriptionLabel.attributedText = descriptionAttrString
buyButton.setTitle("menu.buyNow".localized(), for: .normal)
}
//Constraints //Constraints
premiumContainer.snp.makeConstraints { (make) in premiumContainer.snp.makeConstraints { (make) in
make.top.equalTo(30) make.top.equalTo(30)
......
...@@ -63,6 +63,7 @@ class MenuViewController: UIViewController { ...@@ -63,6 +63,7 @@ class MenuViewController: UIViewController {
super.viewWillAppear(animated) super.viewWillAppear(animated)
menuCellFactory.reload() menuCellFactory.reload()
menuHeaderView.reload()
tableView.reloadData() tableView.reloadData()
} }
...@@ -157,6 +158,8 @@ extension MenuViewController: UITableViewDelegate { ...@@ -157,6 +158,8 @@ extension MenuViewController: UITableViewDelegate {
viewModel.showDeviceId() viewModel.showDeviceId()
case .subscriptionOverview: case .subscriptionOverview:
coordinator.openSubscriptionOverview() coordinator.openSubscriptionOverview()
case .restorePurchases:
viewModel.restorePurchases()
default: default:
break break
} }
...@@ -168,6 +171,7 @@ extension MenuViewController: StoreManagerObserver { ...@@ -168,6 +171,7 @@ extension MenuViewController: StoreManagerObserver {
func storeManagerUpdatedStatus(_ storeManager: StoreManager) { func storeManagerUpdatedStatus(_ storeManager: StoreManager) {
onMain { onMain {
self.menuCellFactory.reload() self.menuCellFactory.reload()
self.menuHeaderView.reload()
self.tableView.reloadData() self.tableView.reloadData()
} }
} }
......
...@@ -33,7 +33,7 @@ class SubscriptionOverviewViewController: UIViewController { ...@@ -33,7 +33,7 @@ class SubscriptionOverviewViewController: UIViewController {
prepareScrollView() prepareScrollView()
//Verify new subscription //Verify new subscription
StoreManager.shared.verifySubscriptions() StoreManager.shared.verifyPurchases()
} }
} }
......
...@@ -69,6 +69,10 @@ class MenuViewModel: NSObject, ViewModelProtocol { ...@@ -69,6 +69,10 @@ class MenuViewModel: NSObject, ViewModelProtocol {
} }
} }
public func restorePurchases() {
storeManager.restoreInApp()
}
public func viewAboutUs() { public func viewAboutUs() {
self.delegate?.presentWebView(url: ONE_WEATHER_ABOUT_US_URL) self.delegate?.presentWebView(url: ONE_WEATHER_ABOUT_US_URL)
analytics(log: .ANALYTICS_VIEW_ABOUT) analytics(log: .ANALYTICS_VIEW_ABOUT)
......
...@@ -22,8 +22,7 @@ class SubscriptionViewModel: ViewModelProtocol { ...@@ -22,8 +22,7 @@ class SubscriptionViewModel: ViewModelProtocol {
} }
public var isProUser: Bool { public var isProUser: Bool {
return true storeManager.removeAdsPurchased
// storeManager.removeAdsPurchased
} }
public func purchase(subscription: SKProduct) { public func purchase(subscription: SKProduct) {
......
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