Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
1
1weather
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Dmitriy Stepanets
1weather
Commits
e5ea3459
Commit
e5ea3459
authored
Sep 11, 2021
by
Demid Merzlyakov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
IOS-155: basic layout of the subscription overview screeen.
parent
db14649d
Show whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
623 additions
and
18 deletions
+623
-18
1Weather.xcodeproj/project.pbxproj
+48
-0
1Weather/Configuration/LocalConfig.plist
+1
-1
1Weather/Coordinators/MenuCoordinator.swift
+7
-0
1Weather/Coordinators/SubscriptionCoordinator.swift
+33
-0
1Weather/InApps/Configuration.storekit
+1
-1
1Weather/InApps/StoreManager.swift
+41
-10
1Weather/Resources/Assets.xcassets/subscriptions/Contents.json
+6
-0
1Weather/Resources/Assets.xcassets/subscriptions/subscription_logo.imageset/1WeatherPremium.pdf
+0
-0
1Weather/Resources/Assets.xcassets/subscriptions/subscription_logo.imageset/Contents.json
+15
-0
1Weather/Resources/OneWeatherColorsAsset.xcassets/subscriptions/Contents.json
+6
-0
1Weather/Resources/OneWeatherColorsAsset.xcassets/subscriptions/subscription_button.colorset/Contents.json
+38
-0
1Weather/Resources/OneWeatherColorsAsset.xcassets/subscriptions/subscription_button_background.colorset/Contents.json
+38
-0
1Weather/Resources/en.lproj/Localizable.strings
+8
-0
1Weather/UI/Helpers/Themes/DefaultTheme.swift
+9
-0
1Weather/UI/Helpers/Themes/ThemeProtocol.swift
+4
-0
1Weather/UI/View controllers/Menu/MenuViewController.swift
+4
-5
1Weather/UI/View controllers/Subscriptions/SubscriptionOverviewViewController.swift
+12
-0
1Weather/UI/View controllers/Subscriptions/SubscriptionStoreViewController.swift
+132
-0
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionDescriptionView.swift
+21
-0
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionProDescriptionView.swift
+22
-0
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionPurchaseButton.swift
+82
-0
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionTopView.swift
+68
-0
1Weather/ViewModels/SubscriptionViewModel.swift
+26
-0
OneWeatherCore/OneWeatherCore/ModelObjects/Config/Subscriptions/SubscriptionsListConfig.swift
+1
-1
No files found.
1Weather.xcodeproj/project.pbxproj
View file @
e5ea3459
...
...
@@ -235,6 +235,14 @@
CE578FE725FB415F00E8B85D
/* LocationsViewModel.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE578FE425FB415F00E8B85D
/* LocationsViewModel.swift */
;
};
CE5F0CBC268A031800B99572
/* OneWeatherWidgetsBundle.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE5F0CBB268A031800B99572
/* OneWeatherWidgetsBundle.swift */
;
};
CE6BE4942634170800626822
/* USStateCode.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6BE4932634170800626822
/* USStateCode.swift */
;
};
CE6E410326EBA3B0009829AE
/* SubscriptionStoreViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E410226EBA3B0009829AE
/* SubscriptionStoreViewController.swift */
;
};
CE6E410626EBA3EB009829AE
/* SubscriptionOverviewViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E410526EBA3EB009829AE
/* SubscriptionOverviewViewController.swift */
;
};
CE6E410826EBA7C0009829AE
/* SubscriptionCoordinator.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E410726EBA7C0009829AE
/* SubscriptionCoordinator.swift */
;
};
CE6E410A26EBA800009829AE
/* SubscriptionViewModel.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E410926EBA800009829AE
/* SubscriptionViewModel.swift */
;
};
CE6E410C26EBAE84009829AE
/* SubscriptionTopView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E410B26EBAE84009829AE
/* SubscriptionTopView.swift */
;
};
CE6E410E26EBBB3B009829AE
/* SubscriptionDescriptionView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E410D26EBBB3B009829AE
/* SubscriptionDescriptionView.swift */
;
};
CE6E411026EBBB57009829AE
/* SubscriptionProDescriptionView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E410F26EBBB57009829AE
/* SubscriptionProDescriptionView.swift */
;
};
CE6E411226EBBB64009829AE
/* SubscriptionPurchaseButton.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E411126EBBB64009829AE
/* SubscriptionPurchaseButton.swift */
;
};
CE6E411426EBC0E9009829AE
/* LocalizationChangeObserver.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE6E411326EBC0E9009829AE
/* LocalizationChangeObserver.swift */
;
};
CE7298C9267A34F3002745D0
/* BlendFIPSSource.framework in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEEF40FF265E47FF00425D8F
/* BlendFIPSSource.framework */
;
};
CE7298CC267A34F5002745D0
/* BlendHealthSource.framework in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CD3883C12657B6A10070FD6F
/* BlendHealthSource.framework */
;
};
...
...
@@ -538,6 +546,14 @@
CE5F0CBB268A031800B99572
/* OneWeatherWidgetsBundle.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
OneWeatherWidgetsBundle.swift
;
sourceTree
=
"<group>"
;
};
CE6BE4932634170800626822
/* USStateCode.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
USStateCode.swift
;
sourceTree
=
"<group>"
;
};
CE6E410026EB4A92009829AE
/* StoreKitTestCertificate.cer */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
file
;
path
=
StoreKitTestCertificate.cer
;
sourceTree
=
"<group>"
;
};
CE6E410226EBA3B0009829AE
/* SubscriptionStoreViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionStoreViewController.swift
;
sourceTree
=
"<group>"
;
};
CE6E410526EBA3EB009829AE
/* SubscriptionOverviewViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionOverviewViewController.swift
;
sourceTree
=
"<group>"
;
};
CE6E410726EBA7C0009829AE
/* SubscriptionCoordinator.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionCoordinator.swift
;
sourceTree
=
"<group>"
;
};
CE6E410926EBA800009829AE
/* SubscriptionViewModel.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionViewModel.swift
;
sourceTree
=
"<group>"
;
};
CE6E410B26EBAE84009829AE
/* SubscriptionTopView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionTopView.swift
;
sourceTree
=
"<group>"
;
};
CE6E410D26EBBB3B009829AE
/* SubscriptionDescriptionView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionDescriptionView.swift
;
sourceTree
=
"<group>"
;
};
CE6E410F26EBBB57009829AE
/* SubscriptionProDescriptionView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionProDescriptionView.swift
;
sourceTree
=
"<group>"
;
};
CE6E411126EBBB64009829AE
/* SubscriptionPurchaseButton.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SubscriptionPurchaseButton.swift
;
sourceTree
=
"<group>"
;
};
CE6E411326EBC0E9009829AE
/* LocalizationChangeObserver.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
LocalizationChangeObserver.swift
;
sourceTree
=
"<group>"
;
};
CE81A421266E289E00800EFF
/* NativeAdView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
NativeAdView.swift
;
sourceTree
=
"<group>"
;
};
CE849DB52638C33600DEFFBD
/* OneWeatherNotificationServiceExtension.appex */
=
{
isa
=
PBXFileReference
;
explicitFileType
=
"wrapper.app-extension"
;
includeInIndex
=
0
;
path
=
OneWeatherNotificationServiceExtension.appex
;
sourceTree
=
BUILT_PRODUCTS_DIR
;
};
...
...
@@ -729,6 +745,7 @@
CEC8FBB1263976240001A6BF
/* OnboardingCoordinator.swift */
,
CD7D3188268F33CC000D01FA
/* WidgetPromotionCoordinator.swift */
,
CD8579692671FA8100CC4CDA
/* ShortsCoordinator.swift */
,
CE6E410726EBA7C0009829AE
/* SubscriptionCoordinator.swift */
,
);
path
=
Coordinators
;
sourceTree
=
"<group>"
;
...
...
@@ -922,6 +939,7 @@
CD6761872625C3360079D273
/* RadarViewModel.swift */
,
CEC8FBB4263976400001A6BF
/* OnboardingViewModel.swift */
,
CD85796E26721C2900CC4CDA
/* ShortsViewModel.swift */
,
CE6E410926EBA800009829AE
/* SubscriptionViewModel.swift */
,
);
path
=
ViewModels
;
sourceTree
=
"<group>"
;
...
...
@@ -947,6 +965,7 @@
CD6B3038257267E2004B34B3
/* View controllers */
=
{
isa
=
PBXGroup
;
children
=
(
CE6E410126EBA386009829AE
/* Subscriptions */
,
CD857EA0268B290500B5E069
/* WidgetPromotion */
,
CD5692B22653D46100A3CDBE
/* SplashAnimation */
,
CEC8FBAD263975170001A6BF
/* Onboarding */
,
...
...
@@ -1405,6 +1424,27 @@
path
=
Widgets
;
sourceTree
=
"<group>"
;
};
CE6E410126EBA386009829AE
/* Subscriptions */
=
{
isa
=
PBXGroup
;
children
=
(
CE6E410426EBA3B4009829AE
/* Views */
,
CE6E410226EBA3B0009829AE
/* SubscriptionStoreViewController.swift */
,
CE6E410526EBA3EB009829AE
/* SubscriptionOverviewViewController.swift */
,
);
path
=
Subscriptions
;
sourceTree
=
"<group>"
;
};
CE6E410426EBA3B4009829AE
/* Views */
=
{
isa
=
PBXGroup
;
children
=
(
CE6E410B26EBAE84009829AE
/* SubscriptionTopView.swift */
,
CE6E410D26EBBB3B009829AE
/* SubscriptionDescriptionView.swift */
,
CE6E410F26EBBB57009829AE
/* SubscriptionProDescriptionView.swift */
,
CE6E411126EBBB64009829AE
/* SubscriptionPurchaseButton.swift */
,
);
path
=
Views
;
sourceTree
=
"<group>"
;
};
CE81A420266E288C00800EFF
/* NativeAdViews */
=
{
isa
=
PBXGroup
;
children
=
(
...
...
@@ -1861,6 +1901,7 @@
isa
=
PBXSourcesBuildPhase
;
buildActionMask
=
2147483647
;
files
=
(
CE6E410E26EBBB3B009829AE
/* SubscriptionDescriptionView.swift in Sources */
,
CD6C22F32667815000D75659
/* EnvironmentManager.swift in Sources */
,
CD6C22F2266780ED00D75659
/* AdConfigManager.swift in Sources */
,
CD35DFD0260344A500F2138F
/* ForecastConditionView.swift in Sources */
,
...
...
@@ -1878,6 +1919,7 @@
CD2C22812670C36A001ADA9A
/* ShortsView.swift in Sources */
,
CD866A6C260F676400E96A5C
/* SettingsDetailsCellFactory.swift in Sources */
,
CDDE8D7F262EED4D00267931
/* MapLegendWeatherView.swift in Sources */
,
CE6E410C26EBAE84009829AE
/* SubscriptionTopView.swift in Sources */
,
87C171F425FF7A4000DA3464
/* PopularCitiesManager.swift in Sources */
,
CD85797326721DD400CC4CDA
/* UIColor+Highlight.swift in Sources */
,
CD67616A262575CD0079D273
/* MapLegendGradientView.swift in Sources */
,
...
...
@@ -1891,6 +1933,7 @@
CD67617C2625A60B0079D273
/* MapLayersDismissAnimator.swift in Sources */
,
CD18728B2624763000AFEDAA
/* MapLegendView.swift in Sources */
,
CEEB3549266F5DA900E16F90
/* MRECAdCell.swift in Sources */
,
CE6E410626EBA3EB009829AE
/* SubscriptionOverviewViewController.swift in Sources */
,
CDB0D4CA2670CAD00081C773
/* ShortsCollectionViewCell.swift in Sources */
,
CDDCD5092680C18B00E089AD
/* ShortsUnreadNudgeView.swift in Sources */
,
CE13B818262480B3007CBD4D
/* A9BidObject.swift in Sources */
,
...
...
@@ -1907,8 +1950,10 @@
CD67617726259DD70079D273
/* MapLayersPresentationAnimator.swift in Sources */
,
CD58529226A02B3200D61021
/* PromotionWidgetViewWrapper.swift in Sources */
,
CD3D567F268C705900DB99B6
/* PromotionPresentationAnimator.swift in Sources */
,
CE6E411026EBBB57009829AE
/* SubscriptionProDescriptionView.swift in Sources */
,
CD7BF1582620410800A30DF5
/* RadarCoordinator.swift in Sources */
,
CDD0F1EE25725BCF00CF5017
/* ThemeManager.swift in Sources */
,
CE6E411226EBBB64009829AE
/* SubscriptionPurchaseButton.swift in Sources */
,
CD1237F4255D889F00C98139
/* GradientView.swift in Sources */
,
CD2C22832670C579001ADA9A
/* ShortsCollectionViewLayout.swift in Sources */
,
CD37D3EF260DF4E6002669D6
/* SettingsViewController.swift in Sources */
,
...
...
@@ -1924,6 +1969,7 @@
CD866A6F260F67F200E96A5C
/* SettingsDetailsViewModel.swift in Sources */
,
CD71709025FA317700A63C27
/* ForecastTimePeriodView.swift in Sources */
,
CE13B812262480B3007CBD4D
/* NativeAdItem.swift in Sources */
,
CE6E410826EBA7C0009829AE
/* SubscriptionCoordinator.swift in Sources */
,
CD857EA5268B36EA00B5E069
/* PromotionHeaderView.swift in Sources */
,
CD37D3E5260CB05C002669D6
/* MenuFooterView.swift in Sources */
,
CDE18DD825D16CB200C80ED9
/* NavigationCityButton.swift in Sources */
,
...
...
@@ -1937,6 +1983,7 @@
CD37D401260DF744002669D6
/* SettingsCell.swift in Sources */
,
CDC6125725E7AB1A00188DA7
/* TodayAirQualityCell.swift in Sources */
,
CD8B60AE263819400055CB3F
/* NWSAlertViewController.swift in Sources */
,
CE6E410326EBA3B0009829AE
/* SubscriptionStoreViewController.swift in Sources */
,
CD593BCF2608A50900C93428
/* ForecastHourlyCell.swift in Sources */
,
CDB0D4CC2670D12F0081C773
/* TodayShortsCell.swift in Sources */
,
CD1DDD332602305200AC62B2
/* ForecastInfoCell.swift in Sources */
,
...
...
@@ -2002,6 +2049,7 @@
CD8B60B3263819790055CB3F
/* NWSAlertCell.swift in Sources */
,
CE13B81C262480B3007CBD4D
/* Interstitial.swift in Sources */
,
CD7D3189268F33CC000D01FA
/* WidgetPromotionCoordinator.swift in Sources */
,
CE6E410A26EBA800009829AE
/* SubscriptionViewModel.swift in Sources */
,
CDDE8D7C262EED3C00267931
/* MapLegendSevereView.swift in Sources */
,
CD6761882625C3360079D273
/* RadarViewModel.swift in Sources */
,
CDF8F12A262089A200DB384A
/* MapTimeView.swift in Sources */
,
...
...
1Weather/Configuration/LocalConfig.plist
View file @
e5ea3459
...
...
@@ -12,7 +12,7 @@
&
quot
;
product_id
&
quot
;
:
&
quot
;
com.onelouder.oneweather.subscription.premium.yearly
&
quot
;
}
,
&
quot
;
monthly_discounted
&
quot
;
:
{
&
quot
;
product_id
&
quot
;
:
&
quot
;
com.onelouder.oneweather.subscription.premium.for
p
ro
&
quot
;
&
quot
;
product_id
&
quot
;
:
&
quot
;
com.onelouder.oneweather.subscription.premium.for
P
ro
&
quot
;
}
,
&
quot
;
yearly_discounted
&
quot
;
:
{
&
quot
;
product_id
&
quot
;
:
&
quot
;
com.onelouder.oneweather.subscription.premium.yearly.forPro
&
quot
;
...
...
1Weather/Coordinators/MenuCoordinator.swift
View file @
e5ea3459
...
...
@@ -37,4 +37,11 @@ class MenuCoordinator: Coordinator {
childCoordinators
.
append
(
settingsCoordinator
)
settingsCoordinator
.
start
()
}
func
openSubscriptions
()
{
let
subscriptionsCoordinator
=
SubscriptionCoordinator
(
parentViewController
:
self
.
navigationController
)
subscriptionsCoordinator
.
parentCoordinator
=
self
childCoordinators
.
append
(
subscriptionsCoordinator
)
subscriptionsCoordinator
.
start
()
}
}
1Weather/Coordinators/SubscriptionCoordinator.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionCoordinator.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
UIKit
import
OneWeatherCore
class
SubscriptionCoordinator
:
Coordinator
{
private
let
parentViewController
:
UIViewController
public
var
childCoordinators
=
[
Coordinator
]()
public
var
parentCoordinator
:
Coordinator
?
private
let
storeManager
:
StoreManager
public
init
(
parentViewController
:
UIViewController
,
storeManager
:
StoreManager
=
StoreManager
.
shared
)
{
self
.
parentViewController
=
parentViewController
self
.
storeManager
=
storeManager
}
public
func
start
()
{
let
viewModel
=
SubscriptionViewModel
(
storeManager
:
storeManager
)
let
vc
=
SubscriptionStoreViewController
(
coordinator
:
self
,
viewModel
:
viewModel
)
self
.
parentViewController
.
present
(
vc
,
animated
:
true
)
}
func
viewControllerDidEnd
(
controller
:
UIViewController
)
{
parentCoordinator
?
.
childDidFinish
(
child
:
self
)
}
}
1Weather/InApps/Configuration.storekit
View file @
e5ea3459
...
...
@@ -77,7 +77,7 @@
"locale" : "en_US"
}
],
"productID" : "com.onelouder.oneweather.subscription.premium.for
p
ro",
"productID" : "com.onelouder.oneweather.subscription.premium.for
P
ro",
"recurringSubscriptionPeriod" : "P1M",
"referenceName" : "Premium Subscription Monthly (for Pro)",
"subscriptionGroupID" : "AC6BEB61",
...
...
1Weather/InApps/StoreManager.swift
View file @
e5ea3459
...
...
@@ -9,6 +9,7 @@ import Foundation
import
OneWeatherCore
import
OneWeatherAnalytics
import
SwiftyStoreKit
import
StoreKit
public
protocol
StoreManagerObserver
{
func
storeManagerUpdatedStatus
(
_
storeManager
:
StoreManager
)
...
...
@@ -21,6 +22,8 @@ public class StoreManager {
private
var
activeInAppRequests
=
[
String
:
InAppRequest
]()
private
static
let
sharedSecret
=
"9462f5b715774b2dafac935098e3f3c6"
private
var
productInfoCache
=
[
String
:
SKProduct
]()
public
static
let
shared
=
StoreManager
()
public
init
(
configManager
:
ConfigManager
=
ConfigManager
.
shared
)
{
...
...
@@ -28,6 +31,19 @@ public class StoreManager {
self
.
configManager
.
add
(
delegate
:
self
)
}
public
var
monthly
:
SKProduct
?
{
productInfoCache
[
configManager
.
config
.
subscriptionsConfig
.
monthly
.
productId
]
}
public
var
yearly
:
SKProduct
?
{
productInfoCache
[
configManager
.
config
.
subscriptionsConfig
.
yearly
.
productId
]
}
public
var
monthlyDiscounted
:
SKProduct
?
{
productInfoCache
[
configManager
.
config
.
subscriptionsConfig
.
monthlyDiscounted
.
productId
]
}
public
var
yearlyDiscounted
:
SKProduct
?
{
productInfoCache
[
configManager
.
config
.
subscriptionsConfig
.
yearlyDiscounted
.
productId
]
}
// MARK: - Get known status
@UserDefaultsValue("com.inmobi.oneweather.WeatherKey", defaultValue: false, userDefaults: UserDefaults.appDefaults)
...
...
@@ -75,6 +91,8 @@ public class StoreManager {
self
.
checkSubscriptionExpirationLocally
()
self
.
completeTransactions
()
self
.
updateProductInfo
()
self
.
verifySubscriptions
()
#warning("Not implemented!")
//TODO: implement
// We need to verify subscription status on startup as well
...
...
@@ -116,22 +134,19 @@ public class StoreManager {
log
.
debug
(
"Update product info: subscriptions disabled. Skip."
)
return
}
let
subscriptions
:
[
SubscriptionConfig
]
if
removeAdsPurchased
{
subscriptions
=
[
config
.
monthlyDiscounted
,
config
.
yearlyDiscounted
]
}
else
{
subscriptions
=
[
config
.
monthly
,
config
.
yearly
]
}
let
subscriptions
:
[
SubscriptionConfig
]
=
[
config
.
monthlyDiscounted
,
config
.
yearlyDiscounted
,
config
.
monthly
,
config
.
yearly
]
let
productIds
=
Set
<
String
>
(
subscriptions
.
map
{
$0
.
productId
})
SwiftyStoreKit
.
retrieveProductsInfo
(
productIds
)
{
result
in
SwiftyStoreKit
.
retrieveProductsInfo
(
productIds
)
{
[
weak
self
]
result
in
guard
let
self
=
self
else
{
return
}
if
let
error
=
result
.
error
{
log
.
error
(
"Update info: error:
\(
error
)
"
)
}
for
product
in
result
.
retrievedProducts
{
log
.
debug
(
"For
\(
product
.
productIdentifier
)
:
\(
product
.
debugDescription
)
"
)
self
.
productInfoCache
[
product
.
productIdentifier
]
=
product
}
log
.
info
(
"Product info cache contains:
\(
self
.
productInfoCache
.
keys
.
joined
(
separator
:
", "
)
)
"
)
}
}
...
...
@@ -155,12 +170,27 @@ public class StoreManager {
activeInAppRequests
[
productId
]
=
request
}
public
func
purchase
(
product
:
SKProduct
)
{
let
log
=
self
.
log
log
.
info
(
"Purchase
\(
product
.
productIdentifier
)
start (SK)."
)
SwiftyStoreKit
.
purchaseProduct
(
product
)
{
result
in
switch
result
{
case
.
success
(
let
product
):
if
product
.
needsFinishTransaction
{
SwiftyStoreKit
.
finishTransaction
(
product
.
transaction
)
}
case
.
error
(
error
:
let
error
):
log
.
error
(
"Purchase: error for
\(
product
.
productIdentifier
)
:
\(
error
)
"
)
}
}
}
public
func
verifySubscriptions
()
{
let
log
=
self
.
log
log
.
info
(
"Verify subscriptions..."
)
let
service
:
AppleReceiptValidator
.
VerifyReceiptURLType
#if DEBUG
service
=
.
production
service
=
.
sandbox
#else
service
=
.
production
#endif
...
...
@@ -225,5 +255,6 @@ public class StoreManager {
extension
StoreManager
:
ConfigManagerDelegate
{
public
func
dataUpdated
(
by
configManager
:
ConfigManager
)
{
self
.
updateProductInfo
()
self
.
verifySubscriptions
()
}
}
1Weather/Resources/Assets.xcassets/subscriptions/Contents.json
0 → 100644
View file @
e5ea3459
{
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
1Weather/Resources/Assets.xcassets/subscriptions/subscription_logo.imageset/1WeatherPremium.pdf
0 → 100644
View file @
e5ea3459
This source diff could not be displayed because it is too large. You can
view the blob
instead.
1Weather/Resources/Assets.xcassets/subscriptions/subscription_logo.imageset/Contents.json
0 → 100644
View file @
e5ea3459
{
"images"
:
[
{
"filename"
:
"1WeatherPremium.pdf"
,
"idiom"
:
"universal"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
},
"properties"
:
{
"preserves-vector-representation"
:
true
}
}
1Weather/Resources/OneWeatherColorsAsset.xcassets/subscriptions/Contents.json
0 → 100644
View file @
e5ea3459
{
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
1Weather/Resources/OneWeatherColorsAsset.xcassets/subscriptions/subscription_button.colorset/Contents.json
0 → 100644
View file @
e5ea3459
{
"colors"
:
[
{
"color"
:
{
"color-space"
:
"srgb"
,
"components"
:
{
"alpha"
:
"1.000"
,
"blue"
:
"1.000"
,
"green"
:
"1.000"
,
"red"
:
"1.000"
}
},
"idiom"
:
"universal"
},
{
"appearances"
:
[
{
"appearance"
:
"luminosity"
,
"value"
:
"dark"
}
],
"color"
:
{
"color-space"
:
"srgb"
,
"components"
:
{
"alpha"
:
"1.000"
,
"blue"
:
"1.000"
,
"green"
:
"1.000"
,
"red"
:
"1.000"
}
},
"idiom"
:
"universal"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
1Weather/Resources/OneWeatherColorsAsset.xcassets/subscriptions/subscription_button_background.colorset/Contents.json
0 → 100644
View file @
e5ea3459
{
"colors"
:
[
{
"color"
:
{
"color-space"
:
"display-p3"
,
"components"
:
{
"alpha"
:
"1.000"
,
"blue"
:
"0.922"
,
"green"
:
"0.399"
,
"red"
:
"0.205"
}
},
"idiom"
:
"universal"
},
{
"appearances"
:
[
{
"appearance"
:
"luminosity"
,
"value"
:
"dark"
}
],
"color"
:
{
"color-space"
:
"display-p3"
,
"components"
:
{
"alpha"
:
"1.000"
,
"blue"
:
"0.922"
,
"green"
:
"0.399"
,
"red"
:
"0.205"
}
},
"idiom"
:
"universal"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
1Weather/Resources/en.lproj/Localizable.strings
View file @
e5ea3459
...
...
@@ -212,6 +212,14 @@
"menu.deviceId.text" = "Your device identifier is: ";
"menu.help.unableToSendEmail.text" = "Device is unable to send email.";
//Subscriptions
"subscription.header.oneWeather" = "1Weather";
"subscription.header.premium" = "PREMIUM";
"subscription.button.buy.yearly" = "Subscribe yearly for #PRICE#";
"subscription.button.buy.monthly" = "Subscribe monthly for #PRICE#";
"subscription.button.upgrade.yearly" = "Upgrade for #PRICE# #DISCOUNT_PRICE# / year";
"subscription.button.upgrade.monthly" = "Upgrade for #PRICE# #DISCOUNT_PRICE# / month";
//Settings
"settings.theme.automatic" = "Automatic";
"settings.theme.automaticDesc" = "Enable light or dark theme based on your device brightness and display settings";
...
...
1Weather/UI/Helpers/Themes/DefaultTheme.swift
View file @
e5ea3459
...
...
@@ -129,4 +129,13 @@ struct DefaultTheme: ThemeProtocol {
var
widgetPromotionText
:
UIColor
{
return
UIColor
(
named
:
"widget_promotion_text"
)
??
.
red
}
// Subscriptions
var
subscriptionPurchaseBackgroundColor
:
UIColor
{
return
UIColor
(
named
:
"subscription_button_background"
)
??
.
red
}
var
subscriptionPurchaseColor
:
UIColor
{
return
UIColor
(
named
:
"subscription_button"
)
??
.
red
}
}
1Weather/UI/Helpers/Themes/ThemeProtocol.swift
View file @
e5ea3459
...
...
@@ -56,4 +56,8 @@ public protocol ThemeProtocol {
//Widget promotion
var
widgetPromotionBackground
:
UIColor
{
get
}
var
widgetPromotionText
:
UIColor
{
get
}
// Subscriptions
var
subscriptionPurchaseBackgroundColor
:
UIColor
{
get
}
var
subscriptionPurchaseColor
:
UIColor
{
get
}
}
1Weather/UI/View controllers/Menu/MenuViewController.swift
View file @
e5ea3459
...
...
@@ -73,10 +73,6 @@ class MenuViewController: UIViewController {
view
.
backgroundColor
=
ThemeManager
.
currentTheme
.
baseBackgroundColor
tableView
.
backgroundColor
=
ThemeManager
.
currentTheme
.
baseBackgroundColor
}
private
func
upgradeToPro
()
{
viewModel
.
updateToPro
()
}
}
//MARK:- Prepare
...
...
@@ -87,7 +83,7 @@ private extension MenuViewController {
func
prepareTableViewHeader
()
{
menuHeaderView
.
onTapBuy
=
{
[
weak
self
]
in
self
?
.
viewModel
.
updateToPro
()
self
?
.
coordinator
.
openSubscriptions
()
}
}
...
...
@@ -109,6 +105,9 @@ private extension MenuViewController {
tableView
.
snp
.
makeConstraints
{
(
make
)
in
make
.
edges
.
equalToSuperview
()
}
if
self
.
viewModel
.
storeManager
.
hasSubscription
||
!
self
.
viewModel
.
featureAvailabilityManager
.
isAvailable
(
feature
:
.
subscription
)
{
self
.
tableView
.
tableHeaderView
=
nil
}
}
}
...
...
1Weather/UI/View controllers/Subscriptions/SubscriptionOverviewViewController.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionOverviewViewController.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
UIKit
class
SubscriptionOverviewViewController
:
UIViewController
{
}
1Weather/UI/View controllers/Subscriptions/SubscriptionStoreViewController.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionStoreViewController.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
UIKit
import
SnapKit
import
StoreKit
/// A screen containing buttons to purchase a subscription.
/// Relevant design URLs:
/// For users who previously purchased an in-app to disable ads: https://zpl.io/anzPMKr
/// For usual users: https://zpl.io/bLJ8rOd
public
class
SubscriptionStoreViewController
:
UIViewController
{
private
let
coordinator
:
SubscriptionCoordinator
private
let
viewModel
:
SubscriptionViewModel
private
var
localizationObserver
:
LocalizationChangeObserver
!
private
let
scrollView
=
UIScrollView
()
private
let
scrollViewContent
=
UIView
()
private
let
topHeaderView
=
SubscriptionTopView
()
private
let
dynamicContentView
=
UIView
()
init
(
coordinator
:
SubscriptionCoordinator
,
viewModel
:
SubscriptionViewModel
)
{
self
.
coordinator
=
coordinator
self
.
viewModel
=
viewModel
super
.
init
(
nibName
:
nil
,
bundle
:
nil
)
}
@available(*, unavailable)
required
init
?(
coder
:
NSCoder
)
{
return
nil
}
public
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
prepareViewController
()
prepareScrollView
()
rebuildUI
()
}
}
//MARK: - UI Setup
extension
SubscriptionStoreViewController
{
private
func
prepareViewController
()
{
view
.
backgroundColor
=
ThemeManager
.
currentTheme
.
baseBackgroundColor
localizationObserver
=
LocalizationChangeObserver
{
[
weak
self
]
in
self
?
.
rebuildUI
()
}
}
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
.
left
.
right
.
equalToSuperview
()
}
scrollViewContent
.
addSubview
(
dynamicContentView
)
dynamicContentView
.
translatesAutoresizingMaskIntoConstraints
=
false
dynamicContentView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
topHeaderView
.
snp
.
bottom
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
24
)
make
.
bottom
.
equalToSuperview
()
}
}
private
func
rebuildUI
()
{
for
subview
in
dynamicContentView
.
subviews
{
subview
.
removeFromSuperview
()
}
let
descriptionView
:
UIView
if
viewModel
.
isProUser
{
descriptionView
=
SubscriptionProDescriptionView
()
}
else
{
descriptionView
=
SubscriptionDescriptionView
()
}
dynamicContentView
.
addSubview
(
descriptionView
)
descriptionView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
left
.
right
.
equalToSuperview
()
}
var
lastView
:
UIView
=
descriptionView
if
let
yearly
=
viewModel
.
storeManager
.
yearly
{
let
yearlyButton
=
SubscriptionPurchaseButton
(
type
:
.
yearly
,
subscription
:
yearly
,
discountSubscription
:
viewModel
.
isProUser
?
viewModel
.
storeManager
.
yearlyDiscounted
:
nil
)
dynamicContentView
.
addSubview
(
yearlyButton
)
yearlyButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
lastView
.
snp
.
bottom
)
.
offset
(
20
)
make
.
left
.
right
.
equalToSuperview
()
}
lastView
=
yearlyButton
yearlyButton
.
delegate
=
self
}
if
let
monthly
=
viewModel
.
storeManager
.
monthly
{
let
monthlyButton
=
SubscriptionPurchaseButton
(
type
:
.
monthly
,
subscription
:
monthly
,
discountSubscription
:
viewModel
.
isProUser
?
viewModel
.
storeManager
.
monthlyDiscounted
:
nil
)
dynamicContentView
.
addSubview
(
monthlyButton
)
monthlyButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
lastView
.
snp
.
bottom
)
.
offset
(
20
)
make
.
left
.
right
.
equalToSuperview
()
}
lastView
=
monthlyButton
monthlyButton
.
delegate
=
self
}
lastView
.
snp
.
makeConstraints
{
make
in
make
.
bottom
.
equalToSuperview
()
}
}
}
extension
SubscriptionStoreViewController
:
SubscriptionPurchaseButtonDelegate
{
func
button
(
_
button
:
SubscriptionPurchaseButton
,
triggeredPurchaseOf
product
:
SKProduct
)
{
viewModel
.
purchase
(
subscription
:
product
)
}
}
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionDescriptionView.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionDescriptionView.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
UIKit
/// A view containing decription of features of premium subscription for users who HAVE NOT previously purchased an in-app to remove ads.
class
SubscriptionDescriptionView
:
UIView
{
public
init
()
{
super
.
init
(
frame
:
.
zero
)
self
.
translatesAutoresizingMaskIntoConstraints
=
false
}
@available(*, unavailable)
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionProDescriptionView.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionProDescriptionView.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
UIKit
/// A view containing decription of features of upgrade to premium subscription for pro users (users who have previously purchased an in-app to remove ads).
class
SubscriptionProDescriptionView
:
UIView
{
public
init
()
{
super
.
init
(
frame
:
.
zero
)
self
.
translatesAutoresizingMaskIntoConstraints
=
false
}
@available(*, unavailable)
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionPurchaseButton.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionPurchaseButton.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
UIKit
import
StoreKit
protocol
SubscriptionPurchaseButtonDelegate
:
AnyObject
{
func
button
(
_
button
:
SubscriptionPurchaseButton
,
triggeredPurchaseOf
product
:
SKProduct
)
}
class
SubscriptionPurchaseButton
:
UIButton
{
public
enum
SubscriptionType
:
String
{
case
monthly
case
yearly
}
private
let
discountSubscription
:
SKProduct
?
private
let
subscription
:
SKProduct
private
let
subscriptionType
:
SubscriptionPurchaseButton
.
SubscriptionType
public
weak
var
delegate
:
SubscriptionPurchaseButtonDelegate
?
public
init
(
type
:
SubscriptionPurchaseButton
.
SubscriptionType
,
subscription
:
SKProduct
,
discountSubscription
:
SKProduct
?
=
nil
)
{
self
.
discountSubscription
=
discountSubscription
self
.
subscription
=
subscription
self
.
subscriptionType
=
type
super
.
init
(
frame
:
.
zero
)
self
.
translatesAutoresizingMaskIntoConstraints
=
false
self
.
setAttributedTitle
(
buildTitle
(),
for
:
.
normal
)
self
.
backgroundColor
=
ThemeManager
.
currentTheme
.
subscriptionPurchaseBackgroundColor
self
.
setTitleColor
(
ThemeManager
.
currentTheme
.
subscriptionPurchaseColor
,
for
:
.
normal
)
self
.
clipsToBounds
=
true
self
.
layer
.
cornerRadius
=
6
self
.
snp
.
makeConstraints
{
make
in
make
.
height
.
equalTo
(
52
)
}
addTarget
(
self
,
action
:
#selector(
handleTap
)
,
for
:
.
touchUpInside
)
}
@available(*, unavailable)
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
private
func
buildTitle
()
->
NSAttributedString
{
guard
let
price
=
subscription
.
localizedPrice
else
{
return
NSAttributedString
()
}
let
template
=
"subscription.button.
\(
showUpgradeText
?
"upgrade"
:
"buy"
)
.
\(
subscriptionType
.
rawValue
)
"
.
localized
()
var
withPrices
=
template
.
replacingOccurrences
(
of
:
"#PRICE#"
,
with
:
price
)
if
let
discountSubscription
=
self
.
discountSubscription
{
guard
let
discountPrice
=
discountSubscription
.
localizedPrice
else
{
return
NSAttributedString
()
}
withPrices
=
withPrices
.
replacingOccurrences
(
of
:
"#DISCOUNT_PRICE#"
,
with
:
discountPrice
)
}
var
result
=
NSMutableAttributedString
(
string
:
withPrices
)
#warning("Not implemented markup!")
//TODO: implement markup
return
result
}
private
var
subscriptionToBuy
:
SKProduct
{
discountSubscription
??
subscription
}
private
var
showUpgradeText
:
Bool
{
discountSubscription
!=
nil
}
@objc
private
func
handleTap
()
{
delegate
?
.
button
(
self
,
triggeredPurchaseOf
:
subscriptionToBuy
)
}
}
1Weather/UI/View controllers/Subscriptions/Views/SubscriptionTopView.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionTopView.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
UIKit
import
SnapKit
class
SubscriptionTopView
:
UIView
{
private
let
oneWeatherLabel
=
UILabel
()
private
let
premiumLabel
=
UILabel
()
private
let
logoView
=
UIImageView
()
private
var
localizationObserver
:
LocalizationChangeObserver
!
public
init
()
{
super
.
init
(
frame
:
.
zero
)
self
.
translatesAutoresizingMaskIntoConstraints
=
false
localizationObserver
=
LocalizationChangeObserver
{
[
weak
self
]
in
self
?
.
updateButtonTexts
()
}
backgroundColor
=
ThemeManager
.
currentTheme
.
subscriptionPurchaseBackgroundColor
oneWeatherLabel
.
font
=
AppFont
.
SFPro
.
bold
(
size
:
32
)
premiumLabel
.
font
=
AppFont
.
SFPro
.
bold
(
size
:
24
)
oneWeatherLabel
.
textColor
=
ThemeManager
.
currentTheme
.
subscriptionPurchaseColor
premiumLabel
.
textColor
=
ThemeManager
.
currentTheme
.
subscriptionPurchaseColor
updateButtonTexts
()
logoView
.
image
=
UIImage
(
named
:
"subscription_logo"
)
logoView
.
contentMode
=
.
scaleAspectFit
addSubview
(
oneWeatherLabel
)
addSubview
(
premiumLabel
)
addSubview
(
logoView
)
oneWeatherLabel
.
snp
.
makeConstraints
{
make
in
make
.
leading
.
equalToSuperview
()
.
offset
(
28
)
make
.
top
.
equalToSuperview
()
.
offset
(
81
)
make
.
trailing
.
lessThanOrEqualTo
(
logoView
.
snp
.
leading
)
.
priority
(
.
low
)
}
premiumLabel
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
oneWeatherLabel
.
snp
.
bottom
)
.
offset
(
4
)
make
.
leading
.
equalTo
(
oneWeatherLabel
.
snp
.
leading
)
make
.
trailing
.
lessThanOrEqualTo
(
logoView
.
snp
.
leading
)
.
priority
(
.
low
)
}
logoView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalToSuperview
()
.
offset
(
46
)
make
.
trailing
.
equalToSuperview
()
.
offset
(
-
18
)
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
14
)
make
.
width
.
equalTo
(
190
)
make
.
height
.
equalTo
(
160
)
}
}
private
func
updateButtonTexts
()
{
oneWeatherLabel
.
text
=
"subscription.header.oneWeather"
.
localized
()
premiumLabel
.
text
=
"subscription.header.premium"
.
localized
()
}
@available(*, unavailable)
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
1Weather/ViewModels/SubscriptionViewModel.swift
0 → 100644
View file @
e5ea3459
//
// SubscriptionViewModel.swift
// 1Weather
//
// Created by Demid Merzlyakov on 10.09.2021.
//
import
Foundation
import
OneWeatherCore
import
StoreKit
class
SubscriptionViewModel
:
ViewModelProtocol
{
public
let
storeManager
:
StoreManager
public
init
(
storeManager
:
StoreManager
)
{
self
.
storeManager
=
storeManager
}
public
var
isProUser
:
Bool
{
storeManager
.
removeAdsPurchased
}
public
func
purchase
(
subscription
:
SKProduct
)
{
storeManager
.
purchase
(
product
:
subscription
)
}
}
OneWeatherCore/OneWeatherCore/ModelObjects/Config/Subscriptions/SubscriptionsListConfig.swift
View file @
e5ea3459
...
...
@@ -27,7 +27,7 @@ public struct SubscriptionsListConfig {
self
.
subscriptionsEnabled
=
true
self
.
monthly
=
SubscriptionConfig
(
productId
:
"com.onelouder.oneweather.subscription.premium"
)
self
.
yearly
=
SubscriptionConfig
(
productId
:
"com.onelouder.oneweather.subscription.premium.yearly"
)
self
.
monthlyDiscounted
=
SubscriptionConfig
(
productId
:
"com.onelouder.oneweather.subscription.premium.for
p
ro"
)
self
.
monthlyDiscounted
=
SubscriptionConfig
(
productId
:
"com.onelouder.oneweather.subscription.premium.for
P
ro"
)
self
.
yearlyDiscounted
=
SubscriptionConfig
(
productId
:
"com.onelouder.oneweather.subscription.premium.yearly.forPro"
)
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment