[iOS] Ads removal subscription

This commit is contained in:
Aleksey Belouosv 2018-09-27 20:27:39 +03:00 committed by Vlad Mihaylenko
parent ead6f849b5
commit 7ba17e82f6
37 changed files with 1474 additions and 60 deletions

View file

@ -1,5 +1,5 @@
class BookmarksLoadedViewController: UIViewController {
private let transitioning = AlertTransitioning()
private let transitioning = FadeTransitioning<AlertPresentationController>()
@objc var onViewBlock: MWMVoidBlock?
@objc var onCancelBlock: MWMVoidBlock?

View file

@ -77,3 +77,4 @@
#import "MWMCatalogCategory.h"
#import "MWMCatalogCommon.h"
#import "MWMEye.h"
#import "MWMPurchaseManager.h"

View file

@ -46,6 +46,7 @@
+ (UIColor *)ratingYellow;
+ (UIColor *)ratingLightGreen;
+ (UIColor *)ratingGreen;
+ (UIColor *)border;
+ (UIColor *)colorWithName:(NSString *)colorName;

View file

@ -50,7 +50,8 @@ NSDictionary<NSString *, UIColor *> * night = @{
@"blackOpaque": [UIColor colorWithWhite:1. alpha:alpha04],
@"toastBackground": [UIColor colorWithWhite:0. alpha:alpha87],
@"statusBarBackground": [UIColor colorWithWhite:0. alpha:alpha32],
@"bannerBackground" : [UIColor colorWithRed:scaled(71) green:scaled(75) blue:scaled(79) alpha:alpha100]
@"bannerBackground" : [UIColor colorWithRed:scaled(71) green:scaled(75) blue:scaled(79) alpha:alpha100],
@"border" : [UIColor colorWithWhite:1. alpha:alpha04]
};
NSDictionary<NSString *, UIColor *> * day = @{
@ -97,7 +98,8 @@ NSDictionary<NSString *, UIColor *> * day = @{
@"blackOpaque" : [UIColor colorWithWhite:0. alpha:alpha04],
@"toastBackground" : [UIColor colorWithWhite:1. alpha:alpha87],
@"statusBarBackground" : [UIColor colorWithWhite:1. alpha:alpha36],
@"bannerBackground" : [UIColor colorWithRed:scaled(249) green:scaled(251) blue:scaled(231) alpha:alpha100]
@"bannerBackground" : [UIColor colorWithRed:scaled(249) green:scaled(251) blue:scaled(231) alpha:alpha100],
@"border" : [UIColor colorWithWhite:0. alpha:alpha04]
};
UIColor * color(SEL cmd)
@ -328,6 +330,11 @@ UIColor * color(SEL cmd)
return color(_cmd);
}
+ (UIColor *)border
{
return color(_cmd);
}
+ (UIColor *)bannerButtonBackground
{
return [UIColor blackDividers];

View file

@ -1,22 +0,0 @@
final class AlertPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toVC = transitionContext.viewController(forKey: .to) else { return }
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toVC)
containerView.addSubview(toVC.view)
toVC.view.alpha = 0
toVC.view.center = containerView.center
toVC.view.frame = finalFrame
toVC.view.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin]
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: {
toVC.view.alpha = 1
}) { transitionContext.completeTransition($0) }
}
}

View file

@ -10,5 +10,18 @@ final class AlertPresentationController: DimmedModalPresentationController {
super.presentationTransitionWillBegin()
presentedViewController.view.layer.cornerRadius = 12
presentedViewController.view.clipsToBounds = true
guard let containerView = containerView, let presentedView = presentedView else { return }
containerView.addSubview(presentedView)
presentedView.center = containerView.center
presentedView.frame = frameOfPresentedViewInContainerView
presentedView.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin]
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.presentationTransitionDidEnd(completed)
guard let presentedView = presentedView else { return }
if completed {
presentedView.removeFromSuperview()
}
}
}

View file

@ -0,0 +1,15 @@
final class FadeInAnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedView = transitionContext.view(forKey: .to) else { return }
presentedView.alpha = 0
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: {
presentedView.alpha = 1
}) { transitionContext.completeTransition($0) }
}
}

View file

@ -1,15 +1,14 @@
final class AlertDismissalAnimator: NSObject, UIViewControllerAnimatedTransitioning {
final class FadeOutAnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from) else { return }
guard let presentedView = transitionContext.view(forKey: .from) else { return }
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: {
fromVC.view.alpha = 0
presentedView.alpha = 0
}) { finished in
fromVC.view.removeFromSuperview()
transitionContext.completeTransition(finished)
}
}

View file

@ -1,17 +1,19 @@
class AlertTransitioning: NSObject, UIViewControllerTransitioningDelegate {
class FadeTransitioning<T: UIPresentationController>: NSObject, UIViewControllerTransitioningDelegate {
let presentedTransitioning = FadeInAnimatedTransitioning()
let dismissedTransitioning = FadeOutAnimatedTransitioning()
func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AlertPresentationAnimator()
return presentedTransitioning
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AlertDismissalAnimator()
return dismissedTransitioning
}
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController? {
return AlertPresentationController(presentedViewController: presented, presenting: presenting)
return T(presentedViewController: presented, presenting: presenting)
}
}

View file

@ -0,0 +1,19 @@
final class RemoveAdsPresentationController: DimmedModalPresentationController {
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
guard let containerView = containerView, let presentedView = presentedView else { return }
presentedView.frame = containerView.bounds
presentedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.addSubview(presentedView)
presentedViewController.modalPresentationCapturesStatusBarAppearance = true
presentedViewController.setNeedsStatusBarAppearanceUpdate()
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.presentationTransitionDidEnd(completed)
guard let presentedView = presentedView else { return }
if completed {
presentedView.removeFromSuperview()
}
}
}

View file

@ -30,6 +30,7 @@
- (void)openCatalogDeeplink:(NSURL * _Nullable)deeplinkUrl animated:(BOOL)animated;
- (void)searchText:(NSString *)text;
- (void)showRemoveAds;
- (void)setPlacePageTopBound:(CGFloat)bound;
- (void)initialize;

View file

@ -78,7 +78,8 @@ BOOL gIsFirstMyPositionMode = YES;
@end
@interface MapViewController ()<MWMFrameworkDrapeObserver, MWMFrameworkStorageObserver,
MWMWelcomePageControllerProtocol, MWMKeyboardObserver>
MWMWelcomePageControllerProtocol, MWMKeyboardObserver,
RemoveAdsViewControllerDelegate>
@property(nonatomic, readwrite) MWMMapViewControlsManager * controlsManager;
@ -492,6 +493,13 @@ BOOL gIsFirstMyPositionMode = YES;
[self.controlsManager searchText:text forInputLocale:[[AppInfo sharedInfo] languageId]];
}
- (void)showRemoveAds
{
auto removeAds = [[RemoveAdsViewController alloc] init];
removeAds.delegate = self;
[self.navigationController presentViewController:removeAds animated:YES completion:nil];
}
- (void)processMyPositionStateModeEvent:(MWMMyPositionMode)mode
{
[MWMLocationManager setMyPositionMode:mode];
@ -531,6 +539,18 @@ BOOL gIsFirstMyPositionMode = YES;
gIsFirstMyPositionMode = NO;
}
#pragma mark - MWMRemoveAdsViewControllerDelegate
- (void)didCompleteSubscribtion:(RemoveAdsViewController *)viewController
{
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
- (void)didCancelSubscribtion:(RemoveAdsViewController *)viewController
{
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - MWMFrameworkDrapeObserver
- (void)processViewportCountryEvent:(TCountryId const &)countryId

View file

@ -390,6 +390,7 @@ using namespace osm_auth_ios;
if (@available(iOS 10, *))
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
[[SubscriptionManager shared] validate];
return YES;
}

View file

@ -0,0 +1,22 @@
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, MWMValidationResult)
{
MWMValidationResultValid,
MWMValidationResultNotValid,
MWMValidationResultError,
};
typedef void (^ValidateReceiptCallback)(NSString * _Nonnull serverId, MWMValidationResult validationResult);
@interface MWMPurchaseManager : NSObject
+ (NSString * _Nonnull)adsRemovalServerId;
+ (NSArray<NSString *> * _Nonnull)productIds;
+ (MWMPurchaseManager * _Nonnull)sharedManager;
- (void)validateReceipt:(NSString * _Nonnull)serverId
callback:(ValidateReceiptCallback _Nonnull)callback;
- (void)setAdsDisabled:(BOOL)disabled;
@end

View file

@ -0,0 +1,136 @@
#import "MWMPurchaseManager.h"
#include "Framework.h"
#include "Private.h"
#import <StoreKit/StoreKit.h>
@interface MWMPurchaseManager() <SKRequestDelegate>
@property (nonatomic) BOOL didRefreshReceipt;
@property (nonatomic, copy) ValidateReceiptCallback callback;
@property (nonatomic) SKReceiptRefreshRequest *receiptRequest;
@property (nonatomic, copy) NSString * serverId;
@end
@implementation MWMPurchaseManager
+ (NSString *)adsRemovalServerId
{
return @(ADS_REMOVAL_SERVER_ID);
}
+ (NSArray *)productIds
{
return @[@(ADS_REMOVAL_WEEKLY_PRODUCT_ID),
@(ADS_REMOVAL_MONTHLY_PRODUCT_ID),
@(ADS_REMOVAL_YEARLY_PRODUCT_ID)];
}
+ (MWMPurchaseManager *)sharedManager
{
static MWMPurchaseManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[MWMPurchaseManager alloc] init];
});
return instance;
}
- (void)refreshReceipt
{
self.receiptRequest = [[SKReceiptRefreshRequest alloc] init];
self.receiptRequest.delegate = self;
[self.receiptRequest start];
self.didRefreshReceipt = YES;
}
- (void)validateReceipt:(NSString *)serverId callback:(ValidateReceiptCallback)callback
{
self.callback = callback;
self.serverId = serverId;
[self validateReceipt];
}
- (void)validateReceipt
{
NSURL * receiptUrl = [NSBundle mainBundle].appStoreReceiptURL;
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
if (!receiptData)
{
[self noReceipt];
return;
}
GetFramework().GetPurchase()->SetValidationCallback([self](auto validationCode, auto const & validationInfo)
{
switch (validationCode)
{
case Purchase::ValidationCode::Verified:
[self validReceipt];
break;
case Purchase::ValidationCode::NotVerified:
[self invalidReceipt];
break;
case Purchase::ValidationCode::ServerError:
[self serverError];
break;
}
});
Purchase::ValidationInfo vi;
vi.m_receiptData = [receiptData base64EncodedStringWithOptions:0].UTF8String;
vi.m_serverId = self.serverId.UTF8String;
vi.m_vendorId = ADS_REMOVAL_VENDOR;
GetFramework().GetPurchase()->Validate(vi, {} /* accessToken */);
}
- (void)validReceipt
{
if (self.callback)
self.callback(self.serverId, MWMValidationResultValid);
}
- (void)noReceipt
{
if (self.callback)
self.callback(self.serverId, MWMValidationResultNotValid);
}
- (void)invalidReceipt
{
if (self.callback)
self.callback(self.serverId, MWMValidationResultNotValid);
}
- (void)serverError
{
if (self.callback)
self.callback(self.serverId, MWMValidationResultError);
}
- (void)appstoreError:(NSError *)error
{
if (self.callback)
self.callback(self.serverId, MWMValidationResultError);
}
- (void)setAdsDisabled:(BOOL)disabled
{
GetFramework().GetPurchase()->SetSubscriptionEnabled(SubscriptionType::RemoveAds, disabled);
}
#pragma mark - SKRequestDelegate
- (void)requestDidFinish:(SKRequest *)request
{
[self validateReceipt];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
[self appstoreError:error];
}
@end

View file

@ -0,0 +1,31 @@
@objc enum SubscriptionPeriod: Int {
case week
case month
case year
case unknown
}
@objc protocol ISubscription {
var productId: String { get }
var price: NSDecimalNumber? { get }
var priceLocale: Locale? { get }
var period: SubscriptionPeriod { get }
}
class Subscription: ISubscription {
public static let productIds = MWMPurchaseManager.productIds()
private static let periodMap: [String: SubscriptionPeriod] = [productIds[0]: .week,
productIds[1]: .month,
productIds[2]: .year]
var productId: String
var period: SubscriptionPeriod
var price: NSDecimalNumber?
var priceLocale: Locale?
init(_ product: SKProduct) {
productId = product.productIdentifier
period = Subscription.periodMap[productId] ?? .unknown
price = product.price
priceLocale = product.priceLocale
}
}

View file

@ -0,0 +1,142 @@
@objc protocol SubscriptionManagerListener: AnyObject {
func didFailToSubscribe(_ subscription: ISubscription, error: Error?)
func didSubsribe(_ subscription: ISubscription)
func didFailToValidate(_ subscription: ISubscription, error: Error?)
func didDefer(_ subscription: ISubscription)
func validationError()
}
class SubscriptionManager: NSObject {
typealias SuscriptionsCompletion = ([ISubscription]?, Error?) -> Void
@objc static var shared: SubscriptionManager = { return SubscriptionManager() }()
private let paymentQueue = SKPaymentQueue.default()
private var productsRequest: SKProductsRequest?
private var subscriptionsComplection: SuscriptionsCompletion?
private var products: [String: SKProduct]?
private var pendingSubscription: ISubscription?
private var listeners = NSHashTable<SubscriptionManagerListener>.weakObjects()
override private init() {
super.init()
paymentQueue.add(self)
}
deinit {
paymentQueue.remove(self)
}
@objc static func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
@objc func getAvailableSubscriptions(_ completion: @escaping SuscriptionsCompletion){
subscriptionsComplection = completion
productsRequest = SKProductsRequest(productIdentifiers: Set(Subscription.productIds))
productsRequest!.delegate = self
productsRequest!.start()
}
@objc func subscribe(to subscription: ISubscription) {
pendingSubscription = subscription
guard let product = products?[subscription.productId] else { return }
paymentQueue.add(SKPayment(product: product))
}
@objc func addListener(_ listener: SubscriptionManagerListener) {
listeners.add(listener)
}
@objc func removeListener(_ listener: SubscriptionManagerListener) {
listeners.remove(listener)
}
@objc func validate() {
MWMPurchaseManager.shared()
.validateReceipt(MWMPurchaseManager.adsRemovalServerId()) { (serverId, validationResult) in
if validationResult == .error {
return
} else {
MWMPurchaseManager.shared().setAdsDisabled(validationResult == .valid)
self.paymentQueue.transactions
.filter { $0.transactionState == .purchased || $0.transactionState == .restored }
.forEach { self.paymentQueue.finishTransaction($0) }
}
}
}
}
extension SubscriptionManager: SKProductsRequestDelegate {
func request(_ request: SKRequest, didFailWithError error: Error) {
subscriptionsComplection?(nil, error)
subscriptionsComplection = nil
productsRequest = nil
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
guard response.products.count == 3 else {
subscriptionsComplection?(nil, NSError(domain: "mapsme.subscriptions", code: -1, userInfo: nil))
subscriptionsComplection = nil
productsRequest = nil
return
}
let subscriptions = response.products.map { Subscription($0) }
.sorted { $0.period.rawValue > $1.period.rawValue }
products = Dictionary(uniqueKeysWithValues: response.products.map { ($0.productIdentifier, $0) })
subscriptionsComplection?(subscriptions, nil)
subscriptionsComplection = nil
productsRequest = nil
}
}
extension SubscriptionManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions
.filter { $0.transactionState == .failed }
.forEach { processFailed($0, error: $0.error) }
if transactions.contains(where: {
$0.transactionState == .purchased || $0.transactionState == .restored
}) {
transactions.filter { $0.transactionState == .purchased }
.forEach { processPurchased($0) }
transactions.filter { $0.transactionState == .restored }
.forEach { processRestored($0) }
validate()
}
transactions.filter { $0.transactionState == .deferred }
.forEach { processDeferred($0) }
}
private func processDeferred(_ transaction: SKPaymentTransaction) {
if let ps = pendingSubscription, transaction.payment.productIdentifier == ps.productId {
listeners.allObjects.forEach { $0.didDefer(ps) }
}
}
private func processPurchased(_ transaction: SKPaymentTransaction) {
MWMPurchaseManager.shared().setAdsDisabled(true)
paymentQueue.finishTransaction(transaction)
if let ps = pendingSubscription, transaction.payment.productIdentifier == ps.productId {
listeners.allObjects.forEach { $0.didSubsribe(ps) }
}
}
private func processRestored(_ transaction: SKPaymentTransaction) {
MWMPurchaseManager.shared().setAdsDisabled(true)
paymentQueue.finishTransaction(transaction)
if let ps = pendingSubscription, transaction.payment.productIdentifier == ps.productId {
listeners.allObjects.forEach { $0.didSubsribe(ps) }
}
}
private func processFailed(_ transaction: SKPaymentTransaction, error: Error?) {
paymentQueue.finishTransaction(transaction)
if let ps = pendingSubscription, transaction.payment.productIdentifier == ps.productId {
listeners.allObjects.forEach { $0.didFailToSubscribe(ps, error: error) }
}
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic16PxClose.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic16PxClose@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic16PxClose@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

(image error) Size: 199 B

Binary file not shown.

After

(image error) Size: 302 B

Binary file not shown.

After

(image error) Size: 404 B

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "mappy-heart.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "mappy-heart@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "mappy-heart@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

(image error) Size: 14 KiB

Binary file not shown.

After

(image error) Size: 32 KiB

Binary file not shown.

After

(image error) Size: 54 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_ad_close.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_ad_close@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_ad_close@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

(image error) Size: 142 B

Binary file not shown.

After

(image error) Size: 215 B

Binary file not shown.

After

(image error) Size: 289 B

View file

@ -351,6 +351,12 @@
470A89FF2134517600D72FBF /* BookmarksTutorialBlur.xib in Resources */ = {isa = PBXBuildFile; fileRef = 470A89FE2134517600D72FBF /* BookmarksTutorialBlur.xib */; };
470A8A012136097000D72FBF /* SubwayTutorialBlur.xib in Resources */ = {isa = PBXBuildFile; fileRef = 470A8A002136073000D72FBF /* SubwayTutorialBlur.xib */; };
471BBD942130390F00EB17C9 /* TutorialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471BBD932130390F00EB17C9 /* TutorialViewController.swift */; };
472E3F472146BCD30020E412 /* SubscriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472E3F462146BCD30020E412 /* SubscriptionManager.swift */; };
472E3F4A2146C4CD0020E412 /* MWMPurchaseManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 472E3F492146C4CD0020E412 /* MWMPurchaseManager.mm */; };
472E3F4C2147D5700020E412 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472E3F4B2147D5700020E412 /* Subscription.swift */; };
474AC76C2139E4F2002F9BF9 /* RemoveAdsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474AC76A2139E4F2002F9BF9 /* RemoveAdsViewController.swift */; };
474AC76D2139E4F2002F9BF9 /* RemoveAdsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 474AC76B2139E4F2002F9BF9 /* RemoveAdsViewController.xib */; };
474C9F5A213FF75800369009 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 474C9F59213FF75800369009 /* StoreKit.framework */; };
474C9F632141896800369009 /* MWMEye.mm in Sources */ = {isa = PBXBuildFile; fileRef = 474C9F622141896800369009 /* MWMEye.mm */; };
4767CD9F20AAD48A00BD8166 /* Checkmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4767CD9E20AAD48A00BD8166 /* Checkmark.swift */; };
4767CDA420AAF66B00BD8166 /* NSAttributedString+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4767CDA320AAF66B00BD8166 /* NSAttributedString+HTML.swift */; };
@ -368,12 +374,13 @@
47E3C72221108E9F008B3B27 /* BookmarksLoadedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47E3C72021108E9F008B3B27 /* BookmarksLoadedViewController.xib */; };
47E3C7252111E41B008B3B27 /* DimmedModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C7242111E41B008B3B27 /* DimmedModalPresentationController.swift */; };
47E3C7272111E5A8008B3B27 /* AlertPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C7262111E5A8008B3B27 /* AlertPresentationController.swift */; };
47E3C7292111E614008B3B27 /* AlertPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C7282111E614008B3B27 /* AlertPresentationAnimator.swift */; };
47E3C72B2111E62A008B3B27 /* AlertDismissalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C72A2111E62A008B3B27 /* AlertDismissalAnimator.swift */; };
47E3C72D2111E6A2008B3B27 /* AlertTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C72C2111E6A2008B3B27 /* AlertTransitioning.swift */; };
47E3C7292111E614008B3B27 /* FadeInAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C7282111E614008B3B27 /* FadeInAnimatedTransitioning.swift */; };
47E3C72B2111E62A008B3B27 /* FadeOutAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C72A2111E62A008B3B27 /* FadeOutAnimatedTransitioning.swift */; };
47E3C72D2111E6A2008B3B27 /* FadeTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C72C2111E6A2008B3B27 /* FadeTransitioning.swift */; };
47E3C72F2111F472008B3B27 /* CoverVerticalModalTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C72E2111F472008B3B27 /* CoverVerticalModalTransitioning.swift */; };
47E3C7312111F4C2008B3B27 /* CoverVerticalPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C7302111F4C2008B3B27 /* CoverVerticalPresentationAnimator.swift */; };
47E3C7332111F4D8008B3B27 /* CoverVerticalDismissalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E3C7322111F4D8008B3B27 /* CoverVerticalDismissalAnimator.swift */; };
47EF05B321504D8F00EAC269 /* RemoveAdsPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EF05B221504D8F00EAC269 /* RemoveAdsPresentationController.swift */; };
47F86CFF20C936FC00FEE291 /* TabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F86CFE20C936FC00FEE291 /* TabView.swift */; };
47F86D0120C93D8D00FEE291 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F86D0020C93D8D00FEE291 /* TabViewController.swift */; };
4A300ED51C6DCFD400140018 /* countries-strings in Resources */ = {isa = PBXBuildFile; fileRef = 4A300ED31C6DCFD400140018 /* countries-strings */; };
@ -1328,6 +1335,13 @@
470A89FE2134517600D72FBF /* BookmarksTutorialBlur.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BookmarksTutorialBlur.xib; sourceTree = "<group>"; };
470A8A002136073000D72FBF /* SubwayTutorialBlur.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SubwayTutorialBlur.xib; sourceTree = "<group>"; };
471BBD932130390F00EB17C9 /* TutorialViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialViewController.swift; sourceTree = "<group>"; };
472E3F462146BCD30020E412 /* SubscriptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionManager.swift; sourceTree = "<group>"; };
472E3F482146C4CD0020E412 /* MWMPurchaseManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMPurchaseManager.h; sourceTree = "<group>"; };
472E3F492146C4CD0020E412 /* MWMPurchaseManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMPurchaseManager.mm; sourceTree = "<group>"; };
472E3F4B2147D5700020E412 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = "<group>"; };
474AC76A2139E4F2002F9BF9 /* RemoveAdsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAdsViewController.swift; sourceTree = "<group>"; };
474AC76B2139E4F2002F9BF9 /* RemoveAdsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RemoveAdsViewController.xib; sourceTree = "<group>"; };
474C9F59213FF75800369009 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
474C9F612141896800369009 /* MWMEye.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMEye.h; sourceTree = "<group>"; };
474C9F622141896800369009 /* MWMEye.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMEye.mm; sourceTree = "<group>"; };
4767CD9E20AAD48A00BD8166 /* Checkmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkmark.swift; sourceTree = "<group>"; };
@ -1347,12 +1361,13 @@
47E3C72021108E9F008B3B27 /* BookmarksLoadedViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BookmarksLoadedViewController.xib; sourceTree = "<group>"; };
47E3C7242111E41B008B3B27 /* DimmedModalPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DimmedModalPresentationController.swift; sourceTree = "<group>"; };
47E3C7262111E5A8008B3B27 /* AlertPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresentationController.swift; sourceTree = "<group>"; };
47E3C7282111E614008B3B27 /* AlertPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresentationAnimator.swift; sourceTree = "<group>"; };
47E3C72A2111E62A008B3B27 /* AlertDismissalAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDismissalAnimator.swift; sourceTree = "<group>"; };
47E3C72C2111E6A2008B3B27 /* AlertTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertTransitioning.swift; sourceTree = "<group>"; };
47E3C7282111E614008B3B27 /* FadeInAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeInAnimatedTransitioning.swift; sourceTree = "<group>"; };
47E3C72A2111E62A008B3B27 /* FadeOutAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeOutAnimatedTransitioning.swift; sourceTree = "<group>"; };
47E3C72C2111E6A2008B3B27 /* FadeTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeTransitioning.swift; sourceTree = "<group>"; };
47E3C72E2111F472008B3B27 /* CoverVerticalModalTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverVerticalModalTransitioning.swift; sourceTree = "<group>"; };
47E3C7302111F4C2008B3B27 /* CoverVerticalPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverVerticalPresentationAnimator.swift; sourceTree = "<group>"; };
47E3C7322111F4D8008B3B27 /* CoverVerticalDismissalAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverVerticalDismissalAnimator.swift; sourceTree = "<group>"; };
47EF05B221504D8F00EAC269 /* RemoveAdsPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAdsPresentationController.swift; sourceTree = "<group>"; };
47F86CFE20C936FC00FEE291 /* TabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabView.swift; sourceTree = "<group>"; };
47F86D0020C93D8D00FEE291 /* TabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = "<group>"; };
4A00DBDE1AB704C400113624 /* drules_proto_dark.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = drules_proto_dark.bin; path = ../../data/drules_proto_dark.bin; sourceTree = "<group>"; };
@ -1897,6 +1912,7 @@
34D1B6F11E95096B0057E9C7 /* libicu.a in Frameworks */,
671E78D31E6A423300B2859B /* librouting_common.a in Frameworks */,
34D808861E793F91002F0584 /* Pushwoosh.framework in Frameworks */,
474C9F5A213FF75800369009 /* StoreKit.framework in Frameworks */,
34D8087B1E793606002F0584 /* Alamofire.framework in Frameworks */,
34D8087D1E79360D002F0584 /* AlamofireImage.framework in Frameworks */,
67B78B551E42333C0018E590 /* AdSupport.framework in Frameworks */,
@ -2050,6 +2066,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
474C9F59213FF75800369009 /* StoreKit.framework */,
3D1958EA213804B6009A83EC /* libmetrics.a */,
4598438521394CFD00F8CAB2 /* MetalPerformanceShaders.framework */,
BB8123CD212C264700ADE512 /* Metal.framework */,
@ -2212,6 +2229,7 @@
isa = PBXGroup;
children = (
474C9F602141894300369009 /* Metrics */,
472E3F4221468AF40020E412 /* Subscriptions */,
3472B5E7200F8CEB00DC6CD5 /* UGC */,
3472B5DD200F868E00DC6CD5 /* Editor */,
3472B5C8200F436700DC6CD5 /* BackgroundFetchScheduler */,
@ -2384,6 +2402,8 @@
children = (
3488B0101E9D0AEC0068AFD8 /* AdBanner.swift */,
3488B0111E9D0AEC0068AFD8 /* AdBanner.xib */,
474AC76A2139E4F2002F9BF9 /* RemoveAdsViewController.swift */,
474AC76B2139E4F2002F9BF9 /* RemoveAdsViewController.xib */,
);
path = Ads;
sourceTree = "<group>";
@ -3188,6 +3208,17 @@
path = TipsAndTricks;
sourceTree = "<group>";
};
472E3F4221468AF40020E412 /* Subscriptions */ = {
isa = PBXGroup;
children = (
472E3F462146BCD30020E412 /* SubscriptionManager.swift */,
472E3F482146C4CD0020E412 /* MWMPurchaseManager.h */,
472E3F492146C4CD0020E412 /* MWMPurchaseManager.mm */,
472E3F4B2147D5700020E412 /* Subscription.swift */,
);
path = Subscriptions;
sourceTree = "<group>";
};
474A450520EBA03900C10B4E /* Layers */ = {
isa = PBXGroup;
children = (
@ -3209,11 +3240,12 @@
47E3C7232111E2F8008B3B27 /* Modal */ = {
isa = PBXGroup;
children = (
47EF05B12150383A00EAC269 /* RemoveAds */,
47E3C7242111E41B008B3B27 /* DimmedModalPresentationController.swift */,
47E3C7262111E5A8008B3B27 /* AlertPresentationController.swift */,
47E3C7282111E614008B3B27 /* AlertPresentationAnimator.swift */,
47E3C72A2111E62A008B3B27 /* AlertDismissalAnimator.swift */,
47E3C72C2111E6A2008B3B27 /* AlertTransitioning.swift */,
47E3C7282111E614008B3B27 /* FadeInAnimatedTransitioning.swift */,
47E3C72A2111E62A008B3B27 /* FadeOutAnimatedTransitioning.swift */,
47E3C72C2111E6A2008B3B27 /* FadeTransitioning.swift */,
47E3C72E2111F472008B3B27 /* CoverVerticalModalTransitioning.swift */,
47E3C7302111F4C2008B3B27 /* CoverVerticalPresentationAnimator.swift */,
47E3C7322111F4D8008B3B27 /* CoverVerticalDismissalAnimator.swift */,
@ -3221,6 +3253,14 @@
path = Modal;
sourceTree = "<group>";
};
47EF05B12150383A00EAC269 /* RemoveAds */ = {
isa = PBXGroup;
children = (
47EF05B221504D8F00EAC269 /* RemoveAdsPresentationController.swift */,
);
path = RemoveAds;
sourceTree = "<group>";
};
47F86CFD20C936B300FEE291 /* TabView */ = {
isa = PBXGroup;
children = (
@ -4244,6 +4284,9 @@
LastSwiftMigration = 0900;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.InAppPurchase = {
enabled = 1;
};
com.apple.Push = {
enabled = 1;
};
@ -4410,6 +4453,7 @@
34D3B02D1E389D05004100F9 /* MWMEditorAdditionalNameTableViewCell.xib in Resources */,
34D3B0331E389D05004100F9 /* MWMEditorCategoryCell.xib in Resources */,
F6E2FDCB1E097BA00083EBEC /* MWMEditorNotesFooter.xib in Resources */,
474AC76D2139E4F2002F9BF9 /* RemoveAdsViewController.xib in Resources */,
34D3B0391E389D05004100F9 /* MWMEditorSelectTableViewCell.xib in Resources */,
34AB663B1FC5AA330078E451 /* RouteManagerViewController.xib in Resources */,
34D3B03F1E389D05004100F9 /* MWMEditorSwitchTableViewCell.xib in Resources */,
@ -4601,6 +4645,7 @@
344BEAF31F66BDC30045DC45 /* RatingSummaryView.swift in Sources */,
6741A9A31BF340DE002C974C /* main.mm in Sources */,
34D3B04F1E38A20C004100F9 /* Bundle+Init.swift in Sources */,
47EF05B321504D8F00EAC269 /* RemoveAdsPresentationController.swift in Sources */,
34AB666E1FC5AA330078E451 /* TransportTransitStepsCollectionView.swift in Sources */,
F6E2FF541E097BA00083EBEC /* MWMHelpController.mm in Sources */,
349FC5481F680DAE00968C9F /* ExpandableTextView.swift in Sources */,
@ -4627,6 +4672,7 @@
340708781F2B5D6C00029ECC /* DimBackground.swift in Sources */,
3490D2DF1CE9DD2500D0B838 /* MWMSideButtons.mm in Sources */,
F6E2FDF81E097BA00083EBEC /* MWMOpeningHoursAllDayTableViewCell.mm in Sources */,
472E3F4C2147D5700020E412 /* Subscription.swift in Sources */,
340B33C61F3AEFDB00A8C1B4 /* MWMRouter+RouteManager.mm in Sources */,
F6E2FE191E097BA00083EBEC /* MWMOpeningHoursTimeSpanTableViewCell.mm in Sources */,
F6E407D01FC45EF5001F7821 /* MWMDiscoveryController.mm in Sources */,
@ -4704,10 +4750,10 @@
F6E2FEE51E097BA00083EBEC /* MWMSearchNoResults.mm in Sources */,
F6E2FF631E097BA00083EBEC /* MWMTTSLanguageViewController.mm in Sources */,
342EE4121C43DAA7009F6A49 /* MWMAuthorizationWebViewLoginViewController.mm in Sources */,
47E3C7292111E614008B3B27 /* AlertPresentationAnimator.swift in Sources */,
47E3C7292111E614008B3B27 /* FadeInAnimatedTransitioning.swift in Sources */,
34AB667D1FC5AA330078E451 /* MWMRoutePreview.mm in Sources */,
B33D21AC20DA515800BAD749 /* MWMCategoryInfoCell.mm in Sources */,
47E3C72D2111E6A2008B3B27 /* AlertTransitioning.swift in Sources */,
47E3C72D2111E6A2008B3B27 /* FadeTransitioning.swift in Sources */,
34845DAF1E1649F6003D55B9 /* DownloaderNoResultsEmbedViewController.swift in Sources */,
474C9F632141896800369009 /* MWMEye.mm in Sources */,
F6791B141C43DF0B007A8A6E /* MWMStartButton.mm in Sources */,
@ -4715,6 +4761,7 @@
F64D9CA01C899C350063FA30 /* MWMEditorViralAlert.mm in Sources */,
34AC8FD11EFC02C000E7F910 /* MWMRoutePoint.mm in Sources */,
6741A9CF1BF340DE002C974C /* MWMLocationAlert.mm in Sources */,
474AC76C2139E4F2002F9BF9 /* RemoveAdsViewController.swift in Sources */,
34ABA62D1C2D57D500FE1BEC /* MWMInputPasswordValidator.mm in Sources */,
F6E2FDA11E097BA00083EBEC /* MWMEditorAdditionalNamesTableViewController.mm in Sources */,
4767CDA620AB1F6200BD8166 /* LeftAlignedIconButton.swift in Sources */,
@ -4876,6 +4923,7 @@
3404755F1E081A4600C92850 /* MWMLocationPredictor.mm in Sources */,
F6E2FE041E097BA00083EBEC /* MWMOpeningHoursDaysSelectorTableViewCell.mm in Sources */,
F6E2FE671E097BA00083EBEC /* MWMPlacePageButtonCell.mm in Sources */,
472E3F472146BCD30020E412 /* SubscriptionManager.swift in Sources */,
F6E2FE131E097BA00083EBEC /* MWMOpeningHoursTimeSelectorTableViewCell.mm in Sources */,
344532311F6FE5880059FBCC /* UGCSummaryRatingStarsCell.swift in Sources */,
F626D52F1C3E83F800C17D15 /* MWMTableViewCell.mm in Sources */,
@ -4899,6 +4947,7 @@
34F1ADD31F6BC09E001CE79D /* PPPReview.swift in Sources */,
F6E2FF4B1E097BA00083EBEC /* SettingsTableViewSwitchCell.swift in Sources */,
F6E2FE791E097BA00083EBEC /* MWMOpeningHoursLayoutHelper.mm in Sources */,
472E3F4A2146C4CD0020E412 /* MWMPurchaseManager.mm in Sources */,
34ABA6211C2D517500FE1BEC /* MWMInputValidator.mm in Sources */,
34BBD6601F8270360070CA50 /* AuthorizationiPadPresentationController.swift in Sources */,
F6E2FD7D1E097BA00083EBEC /* MWMMapDownloaderExtendedDataSource.mm in Sources */,
@ -5021,7 +5070,7 @@
34845DB31E165E24003D55B9 /* SearchNoResultsViewController.swift in Sources */,
34AB660B1FC5AA320078E451 /* MWMNavigationDashboardEntity.mm in Sources */,
F5BD29FF26AD58255766C51A /* DiscoverySpinnerCell.swift in Sources */,
47E3C72B2111E62A008B3B27 /* AlertDismissalAnimator.swift in Sources */,
47E3C72B2111E62A008B3B27 /* FadeOutAnimatedTransitioning.swift in Sources */,
F5BD255A0838E70EC012748E /* DiscoverySearchCell.swift in Sources */,
F5BD2CA4DBEFACBC48195F39 /* DiscoveryCollectionHolderCell.swift in Sources */,
);

View file

@ -54,11 +54,26 @@ final class AdBanner: UITableViewCell {
@IBOutlet private weak var adCallToActionButtonCompact: UIButton!
@IBOutlet private weak var adCallToActionButtonDetailed: UIButton!
@IBOutlet private weak var adCallToActionButtonCustom: UIButton!
@IBOutlet private weak var nativeAdView: UIView!
@IBOutlet private weak var removeAdsSmallButton: UIButton!
@IBOutlet private weak var removeAdsLargeButton: UIButton!
@IBOutlet private weak var removeAdsImage: UIImageView! {
didSet {
removeAdsImage.mwm_coloring = .black
}
}
@IBOutlet private weak var fallbackAdView: UIView!
@IBOutlet private var nativeAdViewBottom: NSLayoutConstraint!
@IBOutlet private var fallbackAdViewBottom: NSLayoutConstraint!
@IBOutlet private var fallbackAdViewHeight: NSLayoutConstraint!
@IBOutlet private var ctaButtonLeftConstraint: NSLayoutConstraint!
@IBOutlet private weak var nativeAdView: UIView! {
didSet {
nativeAdView.layer.borderColor = UIColor.blackDividers().cgColor
}
}
@objc var onRemoveAds: MWMVoidBlock?
@objc static let detailedBannerExcessHeight: Float = 36
enum AdType {
@ -123,6 +138,11 @@ final class AdBanner: UITableViewCell {
UIViewController.topViewController().open(url)
}
@IBAction
private func removeAction() {
onRemoveAds?()
}
override func layoutSubviews() {
super.layoutSubviews()
switch nativeAd {
@ -135,7 +155,7 @@ final class AdBanner: UITableViewCell {
state = .unset
}
@objc func config(ad: MWMBanner, containerType: AdBannerContainerType) {
@objc func config(ad: MWMBanner, containerType: AdBannerContainerType, canRemoveAds: Bool, onRemoveAds: (() -> Void)?) {
reset()
switch containerType {
case .placePage:
@ -153,6 +173,11 @@ final class AdBanner: UITableViewCell {
case let ad as GoogleNativeBanner: configGoogleNativeBanner(ad: ad)
default: assert(false)
}
self.onRemoveAds = onRemoveAds
removeAdsSmallButton.isHidden = !canRemoveAds
removeAdsLargeButton.isHidden = !canRemoveAds
removeAdsImage.isHidden = !canRemoveAds
ctaButtonLeftConstraint.priority = canRemoveAds ? .defaultLow : .defaultHigh
}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
@ -19,6 +19,13 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="109.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ucm-4E-iB7">
<rect key="frame" x="0.0" y="0.0" width="375" height="109.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" priority="999" constant="109" id="9T6-aq-miV"/>
</constraints>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="f76-qn-ne4">
<rect key="frame" x="4" y="4" width="367" height="101.5"/>
<subviews>
@ -120,8 +127,30 @@
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="bannerButtonBackground"/>
</userDefinedRuntimeAttributes>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EIT-s3-8BF">
<rect key="frame" x="12" y="101" width="165.5" height="36"/>
<color key="backgroundColor" red="0.95294117649999999" green="0.92156862750000001" blue="0.85490196080000003" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="Sec-LO-NfY"/>
</constraints>
<state key="normal" title="Remove Ads">
<color key="titleColor" red="0.0" green="0.0" blue="0.0" alpha="0.40000000000000002" colorSpace="calibratedRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="medium14"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="bannerButtonBackground"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorName" value="blackSecondaryText"/>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="2"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="removeAction" destination="WK2-gA-ocn" eventType="touchUpInside" id="dCc-Ww-mvy"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NKM-3R-3g1">
<rect key="frame" x="12" y="101.5" width="343" height="36"/>
<rect key="frame" x="189.5" y="101.5" width="165.5" height="36"/>
<color key="backgroundColor" red="0.95294117647058818" green="0.92156862745098034" blue="0.85490196078431369" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="JMM-je-O9e"/>
@ -150,39 +179,69 @@
<action selector="privacyAction" destination="WK2-gA-ocn" eventType="touchUpInside" id="ma2-uV-7hH"/>
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_ad_close" translatesAutoresizingMaskIntoConstraints="NO" id="D3u-BA-Ixx">
<rect key="frame" x="352" y="2" width="13" height="13"/>
<color key="tintColor" white="0.0" alpha="0.54168450338082197" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="13" id="AuT-Sc-FFo"/>
<constraint firstAttribute="height" constant="13" id="NRm-LU-MWL"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="blackDividers"/>
</userDefinedRuntimeAttributes>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="huL-pj-FQy">
<rect key="frame" x="335" y="0.0" width="32" height="18"/>
<constraints>
<constraint firstAttribute="width" constant="32" id="Bmc-z7-zKC"/>
</constraints>
<connections>
<action selector="removeAction" destination="WK2-gA-ocn" eventType="touchUpInside" id="Jtb-b8-Ihy"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="9qA-JC-fkn" secondAttribute="leading" priority="250" id="5CH-Fo-S70"/>
<constraint firstItem="huL-pj-FQy" firstAttribute="top" secondItem="f76-qn-ne4" secondAttribute="top" id="9xd-8o-CPo"/>
<constraint firstAttribute="bottom" secondItem="Ev3-yY-ql1" secondAttribute="bottom" priority="500" constant="8" id="FJI-xF-QTM"/>
<constraint firstItem="NKM-3R-3g1" firstAttribute="top" relation="greaterThanOrEqual" secondItem="EuF-Rm-DHQ" secondAttribute="bottom" constant="8" id="Fjs-IQ-LQv"/>
<constraint firstItem="Ev3-yY-ql1" firstAttribute="top" secondItem="kIR-cO-v6L" secondAttribute="bottom" priority="250" constant="8" id="HHb-Vh-rIl"/>
<constraint firstItem="EuF-Rm-DHQ" firstAttribute="trailing" secondItem="f76-qn-ne4" secondAttribute="leading" priority="500" id="HLI-Zw-ETh"/>
<constraint firstItem="54O-iN-1Gg" firstAttribute="top" secondItem="f76-qn-ne4" secondAttribute="top" id="JWc-kJ-RaK"/>
<constraint firstItem="EuF-Rm-DHQ" firstAttribute="top" secondItem="f76-qn-ne4" secondAttribute="top" constant="8" id="KEp-1t-yK0"/>
<constraint firstItem="NKM-3R-3g1" firstAttribute="leading" secondItem="f76-qn-ne4" secondAttribute="leading" constant="12" id="Ls8-rz-N1Q"/>
<constraint firstItem="Ev3-yY-ql1" firstAttribute="top" secondItem="kIR-cO-v6L" secondAttribute="bottom" priority="700" constant="4" id="NmE-r2-ZNA"/>
<constraint firstAttribute="trailing" secondItem="kIR-cO-v6L" secondAttribute="trailing" priority="250" constant="16" id="POq-M6-rLU"/>
<constraint firstItem="zWu-Gh-Vf7" firstAttribute="top" secondItem="kIR-cO-v6L" secondAttribute="top" constant="2" id="Q0s-7L-aih"/>
<constraint firstItem="D3u-BA-Ixx" firstAttribute="top" secondItem="huL-pj-FQy" secondAttribute="top" constant="2" id="QV1-rU-D4J"/>
<constraint firstAttribute="trailing" secondItem="54O-iN-1Gg" secondAttribute="trailing" id="S6I-ea-HJN"/>
<constraint firstItem="9qA-JC-fkn" firstAttribute="leading" secondItem="Ev3-yY-ql1" secondAttribute="trailing" priority="500" constant="4" id="YNA-Lu-LXQ"/>
<constraint firstItem="kIR-cO-v6L" firstAttribute="leading" secondItem="zWu-Gh-Vf7" secondAttribute="leading" constant="4" id="ZVP-Gs-m00"/>
<constraint firstAttribute="trailing" secondItem="NKM-3R-3g1" secondAttribute="trailing" constant="12" id="ZiW-Tk-SOB"/>
<constraint firstItem="kIR-cO-v6L" firstAttribute="top" secondItem="f76-qn-ne4" secondAttribute="top" constant="8" id="ajZ-XY-N1h"/>
<constraint firstItem="So8-wM-Cgz" firstAttribute="bottom" secondItem="zWu-Gh-Vf7" secondAttribute="bottom" constant="2" id="akC-Fs-8Iv"/>
<constraint firstItem="NKM-3R-3g1" firstAttribute="leading" secondItem="EIT-s3-8BF" secondAttribute="trailing" priority="500" constant="12" id="b7c-4v-4PG"/>
<constraint firstItem="9qA-JC-fkn" firstAttribute="top" secondItem="huL-pj-FQy" secondAttribute="bottom" id="crO-Dv-apD"/>
<constraint firstAttribute="trailing" secondItem="9qA-JC-fkn" secondAttribute="trailing" priority="500" constant="16" id="dww-N9-1tY"/>
<constraint firstItem="Ev3-yY-ql1" firstAttribute="trailing" secondItem="kIR-cO-v6L" secondAttribute="trailing" id="eM3-Nv-zVj"/>
<constraint firstItem="So8-wM-Cgz" firstAttribute="top" secondItem="zWu-Gh-Vf7" secondAttribute="top" constant="-2" id="fNs-FC-pga"/>
<constraint firstItem="EuF-Rm-DHQ" firstAttribute="leading" secondItem="f76-qn-ne4" secondAttribute="leading" priority="250" constant="16" id="gJI-DA-6rn"/>
<constraint firstItem="54O-iN-1Gg" firstAttribute="leading" secondItem="f76-qn-ne4" secondAttribute="leading" id="ge7-AI-110"/>
<constraint firstItem="EIT-s3-8BF" firstAttribute="width" secondItem="NKM-3R-3g1" secondAttribute="width" id="gvb-Sb-VpY"/>
<constraint firstItem="EIT-s3-8BF" firstAttribute="leading" secondItem="f76-qn-ne4" secondAttribute="leading" constant="12" id="hHP-XI-N3c"/>
<constraint firstItem="NKM-3R-3g1" firstAttribute="top" secondItem="Ev3-yY-ql1" secondAttribute="bottom" constant="8" id="hn7-T4-7Zo"/>
<constraint firstAttribute="bottom" secondItem="NKM-3R-3g1" secondAttribute="bottom" priority="250" constant="12" id="hzn-4Y-A0H"/>
<constraint firstItem="EIT-s3-8BF" firstAttribute="bottom" secondItem="NKM-3R-3g1" secondAttribute="bottom" id="iB4-7e-JCZ"/>
<constraint firstAttribute="trailing" secondItem="huL-pj-FQy" secondAttribute="trailing" id="j1g-TD-yNF"/>
<constraint firstItem="D3u-BA-Ixx" firstAttribute="trailing" secondItem="huL-pj-FQy" secondAttribute="trailing" constant="-2" id="lYj-xp-qLn"/>
<constraint firstItem="So8-wM-Cgz" firstAttribute="leading" secondItem="zWu-Gh-Vf7" secondAttribute="leading" constant="-2" id="sAg-zL-vMW"/>
<constraint firstAttribute="bottom" secondItem="54O-iN-1Gg" secondAttribute="bottom" id="tOj-8H-AsH"/>
<constraint firstItem="Ev3-yY-ql1" firstAttribute="leading" secondItem="zWu-Gh-Vf7" secondAttribute="leading" id="tVH-Tk-6D6"/>
<constraint firstItem="9qA-JC-fkn" firstAttribute="top" secondItem="f76-qn-ne4" secondAttribute="top" constant="18" id="u8g-fp-l2o"/>
<constraint firstItem="zWu-Gh-Vf7" firstAttribute="leading" secondItem="f76-qn-ne4" secondAttribute="leading" priority="600" constant="8" id="vlX-zx-nfP"/>
<constraint firstItem="zWu-Gh-Vf7" firstAttribute="leading" secondItem="EuF-Rm-DHQ" secondAttribute="trailing" priority="250" constant="8" id="w5K-BT-1ED"/>
<constraint firstItem="EIT-s3-8BF" firstAttribute="top" secondItem="Ev3-yY-ql1" secondAttribute="bottom" constant="8" id="wEs-mb-MUF"/>
<constraint firstItem="NKM-3R-3g1" firstAttribute="leading" secondItem="f76-qn-ne4" secondAttribute="leading" priority="250" constant="12" id="ypZ-TK-ao1"/>
<constraint firstItem="So8-wM-Cgz" firstAttribute="trailing" secondItem="zWu-Gh-Vf7" secondAttribute="trailing" id="zGz-Sf-G1b"/>
</constraints>
<userDefinedRuntimeAttributes>
@ -195,13 +254,6 @@
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="bannerBackground"/>
</userDefinedRuntimeAttributes>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ucm-4E-iB7">
<rect key="frame" x="0.0" y="0.0" width="375" height="109.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" priority="999" constant="109" id="9T6-aq-miV"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
@ -231,11 +283,15 @@
<outlet property="adIconImageView" destination="EuF-Rm-DHQ" id="Edf-Ak-VAy"/>
<outlet property="adPrivacyImage" destination="042-9V-wU6" id="JLV-4K-UTc"/>
<outlet property="adTitleLabel" destination="kIR-cO-v6L" id="OOh-tX-yBM"/>
<outlet property="ctaButtonLeftConstraint" destination="ypZ-TK-ao1" id="Qjs-Rw-yKB"/>
<outlet property="fallbackAdView" destination="ucm-4E-iB7" id="dOb-SQ-pex"/>
<outlet property="fallbackAdViewBottom" destination="DdI-tM-5Yy" id="Tyv-gy-gf7"/>
<outlet property="fallbackAdViewHeight" destination="9T6-aq-miV" id="gze-UL-E6b"/>
<outlet property="nativeAdView" destination="f76-qn-ne4" id="9hb-za-zgw"/>
<outlet property="nativeAdViewBottom" destination="7c6-rR-ue5" id="8x7-ar-cxz"/>
<outlet property="removeAdsImage" destination="D3u-BA-Ixx" id="0qo-wO-cBR"/>
<outlet property="removeAdsLargeButton" destination="EIT-s3-8BF" id="Xdb-44-0cc"/>
<outlet property="removeAdsSmallButton" destination="huL-pj-FQy" id="dtr-Nq-1O0"/>
<outletCollection property="detailedModeConstraints" destination="w5K-BT-1ED" collectionClass="NSMutableArray" id="Lv4-E4-dYV"/>
<outletCollection property="detailedModeConstraints" destination="POq-M6-rLU" collectionClass="NSMutableArray" id="rqF-JD-SZe"/>
<outletCollection property="detailedModeConstraints" destination="gJI-DA-6rn" collectionClass="NSMutableArray" id="Bf5-KV-Yq1"/>
@ -247,6 +303,7 @@
</tableViewCell>
</objects>
<resources>
<image name="ic_ad_close" width="8" height="8"/>
<image name="ic_ads_fb" width="20" height="12"/>
<image name="img_ad_light" width="20" height="12"/>
</resources>

View file

@ -0,0 +1,208 @@
@objc protocol RemoveAdsViewControllerDelegate: AnyObject {
func didCompleteSubscribtion(_ viewController: RemoveAdsViewController)
func didCancelSubscribtion(_ viewController: RemoveAdsViewController)
}
@objc class RemoveAdsViewController: MWMViewController {
typealias VC = RemoveAdsViewController
private let transitioning = FadeTransitioning<RemoveAdsPresentationController>()
@IBOutlet weak var loadingView: UIView!
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView! {
didSet {
loadingIndicator.color = .blackPrimaryText()
}
}
@IBOutlet weak var payButton: UIButton!
@IBOutlet weak var monthButton: UIButton!
@IBOutlet weak var weekButton: UIButton!
@IBOutlet weak var whySupportButton: UIButton!
@IBOutlet weak var saveLabel: UILabel!
@IBOutlet weak var productsLoadingIndicator: UIActivityIndicatorView!
@IBOutlet weak var whySupportView: UIView!
@IBOutlet weak var optionsView: UIView! {
didSet {
optionsView.layer.borderColor = UIColor.blackDividers().cgColor
optionsView.layer.borderWidth = 1
}
}
@IBOutlet weak var optionsHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var whySupportConstraint: NSLayoutConstraint!
@objc weak var delegate: RemoveAdsViewControllerDelegate?
var subscriptions: [ISubscription]?
private static func formatPrice(_ price: NSDecimalNumber?, locale: Locale?) -> String {
guard let price = price else { return "" }
guard let locale = locale else { return "\(price)" }
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = locale
return formatter.string(from: price) ?? ""
}
private static func calculateDiscount(_ price: NSDecimalNumber?,
weeklyPrice: NSDecimalNumber?,
period: SubscriptionPeriod) -> NSDecimalNumber? {
guard let price = price, let weeklyPrice = weeklyPrice else { return nil }
switch period {
case .week:
return 0
case .month:
return weeklyPrice.multiplying(by: 4).subtracting(price)
case .year:
return weeklyPrice.multiplying(by: 52).subtracting(price)
case .unknown:
return nil
}
}
override func viewDidLoad() {
super.viewDidLoad()
SubscriptionManager.shared.addListener(self)
SubscriptionManager.shared.getAvailableSubscriptions { (subscriptions, error) in
self.subscriptions = subscriptions
self.productsLoadingIndicator.stopAnimating()
guard let subscriptions = subscriptions else {
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"), text: L("purchase_error_subtitle"))
self.delegate?.didCancelSubscribtion(self)
return
}
self.saveLabel.isHidden = false
self.optionsView.isHidden = false
self.payButton.isEnabled = true
assert(subscriptions.count == 3)
let yearlyPrice = subscriptions[0].price
let monthlyPrice = subscriptions[1].price
let weeklyPrice = subscriptions[2].price
let yearlyDiscount = VC.calculateDiscount(yearlyPrice,
weeklyPrice: weeklyPrice,
period: subscriptions[0].period)
let monthlyDiscount = VC.calculateDiscount(monthlyPrice,
weeklyPrice: weeklyPrice,
period: subscriptions[1].period)
let locale = subscriptions[0].priceLocale
self.payButton.setTitle(String(coreFormat: L("paybtn_title"),
arguments: [VC.formatPrice(yearlyPrice, locale: locale)]), for: .normal)
self.saveLabel.text = String(coreFormat: L("paybtn_subtitle"),
arguments: [VC.formatPrice(yearlyDiscount, locale: locale)])
self.monthButton.setTitle(String(coreFormat: L("options_dropdown_item1"),
arguments: [VC.formatPrice(monthlyPrice ?? 0, locale: locale),
VC.formatPrice(monthlyDiscount, locale: locale)]), for: .normal)
self.weekButton.setTitle(String(coreFormat: L("options_dropdown_item2"),
arguments: [VC.formatPrice(weeklyPrice ?? 0, locale: locale)]), for: .normal)
}
}
override var prefersStatusBarHidden: Bool {
return true
}
deinit {
SubscriptionManager.shared.removeListener(self)
}
@IBAction func onClose(_ sender: Any) {
delegate?.didCancelSubscribtion(self)
}
@IBAction func onPay(_ sender: UIButton) {
subscribe(subscriptions?[0])
}
@IBAction func onMonth(_ sender: UIButton) {
subscribe(subscriptions?[1])
}
@IBAction func onWeek(_ sender: UIButton) {
subscribe(subscriptions?[2])
}
@IBAction func onMoreOptions(_ sender: UIButton) {
view.layoutIfNeeded()
UIView.animate(withDuration: kDefaultAnimationDuration) {
self.optionsHeightConstraint.constant = 109
self.view.layoutIfNeeded()
}
}
@IBAction func onWhySupport(_ sender: UIButton) {
whySupportView.isHidden = false
whySupportView.alpha = 0
whySupportConstraint.priority = .defaultHigh
UIView.animate(withDuration: kDefaultAnimationDuration) {
self.whySupportView.alpha = 1
self.whySupportButton.alpha = 0
}
}
private func subscribe(_ subscription: ISubscription?) {
guard let subscription = subscription else {
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
text: L("purchase_error_subtitle"))
self.delegate?.didCancelSubscribtion(self)
return
}
showPurchaseProgress()
SubscriptionManager.shared.subscribe(to: subscription)
}
private func showPurchaseProgress() {
loadingView.isHidden = false
loadingView.alpha = 0
UIView.animate(withDuration: kDefaultAnimationDuration) {
self.loadingView.alpha = 1
}
}
private func hidePurchaseProgress() {
UIView.animate(withDuration: kDefaultAnimationDuration, animations: {
self.loadingView.alpha = 0
}) { _ in
self.loadingView.isHidden = true
}
}
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
get { return transitioning }
set { }
}
override var modalPresentationStyle: UIModalPresentationStyle {
get { return .custom }
set { }
}
}
extension RemoveAdsViewController: SubscriptionManagerListener {
func validationError() {
hidePurchaseProgress()
delegate?.didCompleteSubscribtion(self)
}
func didSubsribe(_ subscription: ISubscription) {
hidePurchaseProgress()
delegate?.didCompleteSubscribtion(self)
}
func didFailToValidate(_ subscription: ISubscription, error: Error?) {
hidePurchaseProgress()
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
text: L("purchase_error_subtitle"))
}
func didDefer(_ subscription: ISubscription) {
hidePurchaseProgress()
delegate?.didCompleteSubscribtion(self)
}
func didFailToSubscribe(_ subscription: ISubscription, error: Error?) {
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
text: L("purchase_error_subtitle"))
hidePurchaseProgress()
}
}

View file

@ -0,0 +1,595 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_0" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="RemoveAdsViewController" customModule="maps_me" customModuleProvider="target">
<connections>
<outlet property="loadingIndicator" destination="LhO-RQ-vOf" id="mz0-la-VFc"/>
<outlet property="loadingView" destination="7Al-wo-fvb" id="yEn-uh-D7S"/>
<outlet property="monthButton" destination="MPP-Wb-dwQ" id="NcO-qU-wu4"/>
<outlet property="optionsHeightConstraint" destination="u0h-Qx-wo9" id="boS-EC-IfC"/>
<outlet property="optionsView" destination="moF-VX-t4N" id="YDj-y7-vKN"/>
<outlet property="payButton" destination="yaj-2x-NKs" id="hqz-4o-zQJ"/>
<outlet property="productsLoadingIndicator" destination="nA0-bV-sRz" id="1tu-UM-7MC"/>
<outlet property="saveLabel" destination="2o9-EE-vAM" id="ZfB-p3-baM"/>
<outlet property="view" destination="i5T-Pr-FkT" id="sfx-zR-JGt"/>
<outlet property="weekButton" destination="XY6-tA-B1U" id="jqz-Re-PDN"/>
<outlet property="whySupportButton" destination="3V5-Dx-w2P" id="FLP-sb-E4d"/>
<outlet property="whySupportConstraint" destination="gIY-Uk-WiX" id="ghr-ye-kPf"/>
<outlet property="whySupportView" destination="pAe-qE-h95" id="9QL-Ib-JS3"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5T-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="T82-qa-a8r">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<connections>
<outletCollection property="gestureRecognizers" destination="cab-7b-e1x" appends="YES" id="8QU-8o-aZD"/>
</connections>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="raA-Qo-8vn" userLabel="Container View" customClass="MWMTouchOpaqueView">
<rect key="frame" x="0.0" y="20" width="320" height="548"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aRL-MT-ZBH">
<rect key="frame" x="0.0" y="0.0" width="320" height="548"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="320" height="548"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="752" verticalCompressionResistancePriority="752" text="Remove all Ads and support MAPS.ME" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vCa-Yl-SkC">
<rect key="frame" x="24" y="24" width="272" height="57.5"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="remove_ads_title"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular24"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" image="mappy_heart" translatesAutoresizingMaskIntoConstraints="NO" id="AmL-7Q-Kas">
<rect key="frame" x="98.5" y="105.5" width="124" height="142"/>
<constraints>
<constraint firstAttribute="height" id="E4W-ij-UBq"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="E4W-ij-UBq"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=compact">
<mask key="constraints">
<include reference="E4W-ij-UBq"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=regular">
<mask key="constraints">
<include reference="E4W-ij-UBq"/>
</mask>
</variation>
</imageView>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pAe-qE-h95">
<rect key="frame" x="0.0" y="24" width="320" height="165"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="752" verticalCompressionResistancePriority="752" text="Why support MAPS.ME?" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YQ4-oq-7XN">
<rect key="frame" x="24" y="0.0" width="272" height="29"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="why_support"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular24"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Jhy-EZ-HaR">
<rect key="frame" x="35" y="45" width="245" height="18"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="•" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SqJ-F7-qg1">
<rect key="frame" x="0.0" y="0.0" width="7" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular15"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="linkBlue"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="We will remove all ads for you" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4wA-Tm-jmm">
<rect key="frame" x="15" y="0.0" width="230" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="why_support_item1"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular15"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="SqJ-F7-qg1" firstAttribute="top" secondItem="Jhy-EZ-HaR" secondAttribute="top" id="82H-S2-p97"/>
<constraint firstAttribute="bottom" secondItem="4wA-Tm-jmm" secondAttribute="bottom" id="88j-gG-Ahr"/>
<constraint firstAttribute="trailing" secondItem="4wA-Tm-jmm" secondAttribute="trailing" id="ILk-3h-RPH"/>
<constraint firstItem="4wA-Tm-jmm" firstAttribute="top" secondItem="Jhy-EZ-HaR" secondAttribute="top" id="Niw-UQ-UzV"/>
<constraint firstItem="SqJ-F7-qg1" firstAttribute="leading" secondItem="Jhy-EZ-HaR" secondAttribute="leading" id="aYQ-f5-oiE"/>
<constraint firstItem="4wA-Tm-jmm" firstAttribute="leading" secondItem="SqJ-F7-qg1" secondAttribute="trailing" constant="8" id="qWW-ca-iyL"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="N1P-1l-fOT">
<rect key="frame" x="35" y="79" width="245" height="18"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="•" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3CU-Qm-qr9">
<rect key="frame" x="0.0" y="0.0" width="7" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular15"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="linkBlue"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="You help us improve MAPS.ME" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WMR-ho-FUS">
<rect key="frame" x="15" y="0.0" width="230" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="why_support_item2"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular15"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="WMR-ho-FUS" firstAttribute="leading" secondItem="3CU-Qm-qr9" secondAttribute="trailing" constant="8" id="GRU-ab-thF"/>
<constraint firstItem="3CU-Qm-qr9" firstAttribute="top" secondItem="N1P-1l-fOT" secondAttribute="top" id="K3I-Go-jJe"/>
<constraint firstAttribute="trailing" secondItem="WMR-ho-FUS" secondAttribute="trailing" id="Mg8-iu-i0B"/>
<constraint firstItem="WMR-ho-FUS" firstAttribute="top" secondItem="N1P-1l-fOT" secondAttribute="top" id="l18-Cp-31b"/>
<constraint firstAttribute="bottom" secondItem="WMR-ho-FUS" secondAttribute="bottom" id="tEv-Rh-6ix"/>
<constraint firstItem="3CU-Qm-qr9" firstAttribute="leading" secondItem="N1P-1l-fOT" secondAttribute="leading" id="xhb-bc-kaB"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Q0y-IM-H43">
<rect key="frame" x="35" y="113" width="245" height="36"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="•" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PXD-OM-5Xd">
<rect key="frame" x="0.0" y="0.0" width="7" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular15"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="linkBlue"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="You help us improve open maps OpenStreetMap.org" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Yos-eN-bY1">
<rect key="frame" x="15" y="0.0" width="230" height="36"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="why_support_item3"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular15"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="Yos-eN-bY1" firstAttribute="top" secondItem="Q0y-IM-H43" secondAttribute="top" id="2Tv-qh-XIE"/>
<constraint firstAttribute="trailing" secondItem="Yos-eN-bY1" secondAttribute="trailing" id="3qq-y9-dI8"/>
<constraint firstItem="PXD-OM-5Xd" firstAttribute="leading" secondItem="Q0y-IM-H43" secondAttribute="leading" id="UPJ-Dv-6LK"/>
<constraint firstItem="Yos-eN-bY1" firstAttribute="leading" secondItem="PXD-OM-5Xd" secondAttribute="trailing" constant="8" id="bJa-tw-GVh"/>
<constraint firstItem="PXD-OM-5Xd" firstAttribute="top" secondItem="Q0y-IM-H43" secondAttribute="top" id="bdg-dj-Qdd"/>
<constraint firstAttribute="bottom" secondItem="Yos-eN-bY1" secondAttribute="bottom" id="zDD-Sg-9Ug"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Q0y-IM-H43" firstAttribute="top" secondItem="N1P-1l-fOT" secondAttribute="bottom" constant="16" id="2Lb-kh-iZg"/>
<constraint firstItem="Jhy-EZ-HaR" firstAttribute="leading" secondItem="pAe-qE-h95" secondAttribute="leading" constant="35" id="5sS-j1-mUW"/>
<constraint firstAttribute="trailing" secondItem="Q0y-IM-H43" secondAttribute="trailing" constant="40" id="AOG-kC-apb"/>
<constraint firstItem="N1P-1l-fOT" firstAttribute="top" secondItem="Jhy-EZ-HaR" secondAttribute="bottom" constant="16" id="AbP-zo-gh2"/>
<constraint firstItem="YQ4-oq-7XN" firstAttribute="top" secondItem="pAe-qE-h95" secondAttribute="top" id="EkV-BK-L8z"/>
<constraint firstAttribute="bottom" secondItem="Q0y-IM-H43" secondAttribute="bottom" priority="500" constant="16" id="G7R-WQ-rqa"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Q0y-IM-H43" secondAttribute="bottom" constant="16" id="HnK-lo-A7U"/>
<constraint firstItem="N1P-1l-fOT" firstAttribute="leading" secondItem="pAe-qE-h95" secondAttribute="leading" constant="35" id="a4q-Oc-xpJ"/>
<constraint firstItem="Jhy-EZ-HaR" firstAttribute="top" secondItem="YQ4-oq-7XN" secondAttribute="bottom" constant="16" id="dnF-54-ldh"/>
<constraint firstAttribute="trailing" secondItem="YQ4-oq-7XN" secondAttribute="trailing" constant="24" id="lC1-ZR-7pr"/>
<constraint firstAttribute="trailing" secondItem="Jhy-EZ-HaR" secondAttribute="trailing" constant="40" id="s5X-wf-eO0"/>
<constraint firstItem="YQ4-oq-7XN" firstAttribute="leading" secondItem="pAe-qE-h95" secondAttribute="leading" constant="24" id="uwK-dG-yPs"/>
<constraint firstAttribute="trailing" secondItem="N1P-1l-fOT" secondAttribute="trailing" constant="40" id="vf7-yc-aSD"/>
<constraint firstItem="Q0y-IM-H43" firstAttribute="leading" secondItem="pAe-qE-h95" secondAttribute="leading" constant="35" id="xC8-Qq-IYT"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="white"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yaj-2x-NKs">
<rect key="frame" x="32.5" y="259.5" width="256" height="72"/>
<color key="backgroundColor" red="0.14000108506944445" green="0.60983615451388884" blue="0.94859483506944442" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="72" id="GcQ-JZ-VF4"/>
<constraint firstAttribute="width" constant="256" id="TWS-OZ-AhB"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<inset key="contentEdgeInsets" minX="0.0" minY="-18" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="clipsToBounds" value="YES"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="linkBlue"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundHighlightedColorName" value="linkBlueHighlighted"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onPay:" destination="-1" eventType="touchUpInside" id="Q4Z-5u-bTt"/>
</connections>
</button>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="save $46.49/year" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2o9-EE-vAM">
<rect key="frame" x="103.5" y="301.5" width="113" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="1" alpha="0.79757063356164382" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="nA0-bV-sRz">
<rect key="frame" x="141.5" y="277.5" width="37" height="37"/>
</activityIndicatorView>
<view hidden="YES" clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="moF-VX-t4N">
<rect key="frame" x="52.5" y="346.5" width="215" height="36"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="1" translatesAutoresizingMaskIntoConstraints="NO" id="zJa-b8-ObU">
<rect key="frame" x="0.0" y="0.0" width="215" height="109"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7KI-mu-vBe">
<rect key="frame" x="0.0" y="0.0" width="215" height="35.5"/>
<color key="backgroundColor" white="0.92827492952346802" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="0.0" maxY="0.0"/>
<state key="normal" title="MORE OPTIONS">
<color key="titleColor" white="0.0" alpha="0.59813784243013701" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="options_dropdown_title"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular12"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorName" value="blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorHighlightedName" value="blackHintText"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="border"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundHighlightedColorName" value="blackDividers"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onMoreOptions:" destination="-1" eventType="touchUpInside" id="XWb-f3-MaZ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MPP-Wb-dwQ">
<rect key="frame" x="0.0" y="36.5" width="215" height="36"/>
<color key="backgroundColor" white="0.92827492952346802" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="0.0" maxY="0.0"/>
<state key="normal" title="$1.99/month (save 18.89/year)">
<color key="titleColor" white="0.0" alpha="0.59813784243013701" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="options_dropdown_title"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular12"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorName" value="blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorHighlightedName" value="blackHintText"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="border"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundHighlightedColorName" value="blackDividers"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onMonth:" destination="-1" eventType="touchUpInside" id="4ir-Kn-VPR"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XY6-tA-B1U">
<rect key="frame" x="0.0" y="73.5" width="215" height="35.5"/>
<color key="backgroundColor" white="0.92827492952346802" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="0.0" maxY="0.0"/>
<state key="normal" title="$0.99/week (no savings)">
<color key="titleColor" white="0.0" alpha="0.59813784243013701" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="options_dropdown_title"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular12"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorName" value="blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorHighlightedName" value="blackHintText"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="border"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundHighlightedColorName" value="blackDividers"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onWeek:" destination="-1" eventType="touchUpInside" id="8da-h3-KW6"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="109" id="dSc-YD-4Cn"/>
</constraints>
</stackView>
</subviews>
<constraints>
<constraint firstItem="zJa-b8-ObU" firstAttribute="top" secondItem="moF-VX-t4N" secondAttribute="top" id="AES-b6-bzk"/>
<constraint firstAttribute="bottom" secondItem="zJa-b8-ObU" secondAttribute="bottom" priority="750" id="AhK-22-qtU"/>
<constraint firstItem="zJa-b8-ObU" firstAttribute="leading" secondItem="moF-VX-t4N" secondAttribute="leading" id="NhF-ji-9zZ"/>
<constraint firstAttribute="width" constant="215" id="YE2-kk-Dwr"/>
<constraint firstAttribute="trailing" secondItem="zJa-b8-ObU" secondAttribute="trailing" id="jZ7-p4-ilg"/>
<constraint firstAttribute="height" constant="36" id="u0h-Qx-wo9"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="9"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="blackDividers"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3V5-Dx-w2P">
<rect key="frame" x="81.5" y="503" width="158" height="29"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" title="Why support MAPS.ME?">
<color key="titleColor" white="0.0" alpha="0.53486194349315064" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="why_support"/>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular14"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorName" value="blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorHighlightedName" value="blackHintText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onWhySupport:" destination="-1" eventType="touchUpInside" id="lgd-75-mK8"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="pAe-qE-h95" secondAttribute="trailing" id="1CQ-Ze-Hbl"/>
<constraint firstAttribute="bottom" secondItem="3V5-Dx-w2P" secondAttribute="bottom" constant="16" id="2GH-M9-SCD"/>
<constraint firstItem="pAe-qE-h95" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="2kl-Qa-qav"/>
<constraint firstItem="2o9-EE-vAM" firstAttribute="centerX" secondItem="yaj-2x-NKs" secondAttribute="centerX" id="2w3-v0-HNI"/>
<constraint firstItem="vCa-Yl-SkC" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="24" id="AWJ-t8-Obh"/>
<constraint firstItem="yaj-2x-NKs" firstAttribute="bottom" secondItem="2o9-EE-vAM" secondAttribute="bottom" constant="13" id="Eoo-Qq-0Vz"/>
<constraint firstItem="3V5-Dx-w2P" firstAttribute="top" relation="greaterThanOrEqual" secondItem="yaj-2x-NKs" secondAttribute="bottom" constant="141" id="G65-q9-khr"/>
<constraint firstItem="moF-VX-t4N" firstAttribute="top" secondItem="yaj-2x-NKs" secondAttribute="bottom" constant="15" id="KU1-p6-eNP"/>
<constraint firstItem="pAe-qE-h95" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="24" id="Pyc-Gg-tON"/>
<constraint firstItem="moF-VX-t4N" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="TvR-G1-Y0U"/>
<constraint firstItem="yaj-2x-NKs" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="WhB-Qb-HvZ"/>
<constraint firstItem="yaj-2x-NKs" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="AmL-7Q-Kas" secondAttribute="bottom" id="a9c-Yc-rJP"/>
<constraint firstItem="AmL-7Q-Kas" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="e6f-2Z-brM"/>
<constraint firstItem="3V5-Dx-w2P" firstAttribute="top" secondItem="yaj-2x-NKs" secondAttribute="bottom" priority="250" constant="141" id="en1-E7-FAF"/>
<constraint firstItem="yaj-2x-NKs" firstAttribute="top" secondItem="pAe-qE-h95" secondAttribute="bottom" priority="250" id="gIY-Uk-WiX"/>
<constraint firstItem="yaj-2x-NKs" firstAttribute="top" secondItem="AmL-7Q-Kas" secondAttribute="bottom" priority="750" constant="12" id="hdg-bJ-61l"/>
<constraint firstItem="3V5-Dx-w2P" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="idr-4f-Z6r"/>
<constraint firstAttribute="trailing" secondItem="vCa-Yl-SkC" secondAttribute="trailing" constant="24" id="lbT-SI-3pb"/>
<constraint firstItem="vCa-Yl-SkC" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="24" id="rBS-o0-mCS"/>
<constraint firstItem="nA0-bV-sRz" firstAttribute="centerX" secondItem="yaj-2x-NKs" secondAttribute="centerX" id="srt-v2-pus"/>
<constraint firstItem="nA0-bV-sRz" firstAttribute="centerY" secondItem="yaj-2x-NKs" secondAttribute="centerY" id="tsA-M8-hFS"/>
<constraint firstItem="AmL-7Q-Kas" firstAttribute="top" secondItem="vCa-Yl-SkC" secondAttribute="bottom" constant="24" id="vT7-2H-bmK"/>
<constraint firstItem="3V5-Dx-w2P" firstAttribute="top" secondItem="moF-VX-t4N" secondAttribute="bottom" priority="250" constant="16" id="z3U-uz-oiR"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="white"/>
</userDefinedRuntimeAttributes>
<variation key="default">
<mask key="constraints">
<exclude reference="G65-q9-khr"/>
<exclude reference="en1-E7-FAF"/>
<exclude reference="z3U-uz-oiR"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=compact">
<mask key="constraints">
<include reference="z3U-uz-oiR"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=regular">
<mask key="constraints">
<include reference="z3U-uz-oiR"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=compact">
<mask key="constraints">
<include reference="G65-q9-khr"/>
<include reference="en1-E7-FAF"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="G65-q9-khr"/>
<include reference="en1-E7-FAF"/>
</mask>
</variation>
</view>
</subviews>
<constraints>
<constraint firstItem="i5M-Pr-FkT" firstAttribute="top" secondItem="aRL-MT-ZBH" secondAttribute="top" id="4U4-89-oKL"/>
<constraint firstItem="i5M-Pr-FkT" firstAttribute="height" relation="greaterThanOrEqual" secondItem="aRL-MT-ZBH" secondAttribute="height" id="90I-og-xrF"/>
<constraint firstAttribute="trailing" secondItem="i5M-Pr-FkT" secondAttribute="trailing" id="Ob4-QQ-FMt"/>
<constraint firstItem="i5M-Pr-FkT" firstAttribute="leading" secondItem="aRL-MT-ZBH" secondAttribute="leading" id="Tkh-ZG-DyV"/>
<constraint firstAttribute="bottom" secondItem="i5M-Pr-FkT" secondAttribute="bottom" id="qPG-U5-are"/>
</constraints>
</scrollView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sW4-fw-4Az">
<rect key="frame" x="276" y="0.0" width="44" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="Jsn-5x-Xxq"/>
<constraint firstAttribute="height" constant="44" id="Z3Z-BM-iFO"/>
</constraints>
<color key="tintColor" white="0.697509765625" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" image="ic_ads_remove_close"/>
<connections>
<action selector="onClose:" destination="-1" eventType="touchUpInside" id="SD3-LB-6gg"/>
</connections>
</button>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Al-wo-fvb">
<rect key="frame" x="0.0" y="0.0" width="320" height="548"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="LhO-RQ-vOf">
<rect key="frame" x="141.5" y="255.5" width="37" height="37"/>
<color key="color" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</activityIndicatorView>
</subviews>
<color key="backgroundColor" white="1" alpha="0.89998929794520544" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="LhO-RQ-vOf" firstAttribute="centerX" secondItem="7Al-wo-fvb" secondAttribute="centerX" id="01M-FO-l2E"/>
<constraint firstItem="LhO-RQ-vOf" firstAttribute="centerY" secondItem="7Al-wo-fvb" secondAttribute="centerY" id="AAS-WB-BvS"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="toastBackground"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="sW4-fw-4Az" secondAttribute="trailing" id="Mla-FC-OUr"/>
<constraint firstItem="sW4-fw-4Az" firstAttribute="top" secondItem="raA-Qo-8vn" secondAttribute="top" id="ODl-nm-Oxd"/>
<constraint firstItem="7Al-wo-fvb" firstAttribute="top" secondItem="raA-Qo-8vn" secondAttribute="top" id="QlH-hB-LQN"/>
<constraint firstItem="i5M-Pr-FkT" firstAttribute="height" secondItem="raA-Qo-8vn" secondAttribute="height" id="TDj-Js-St4"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="320" id="VdN-qU-lhk"/>
<constraint firstAttribute="bottom" secondItem="7Al-wo-fvb" secondAttribute="bottom" id="adv-pq-MZl"/>
<constraint firstAttribute="trailing" secondItem="aRL-MT-ZBH" secondAttribute="trailing" id="bB0-8N-f7V"/>
<constraint firstItem="aRL-MT-ZBH" firstAttribute="top" secondItem="raA-Qo-8vn" secondAttribute="top" id="ccC-bj-dyo"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="999" constant="570" id="eN2-eT-svS"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="320" id="gMV-LL-t8v"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" priority="999" constant="570" id="iyr-JZ-opr"/>
<constraint firstItem="7Al-wo-fvb" firstAttribute="leading" secondItem="raA-Qo-8vn" secondAttribute="leading" id="oIx-qA-TCk"/>
<constraint firstAttribute="trailing" secondItem="7Al-wo-fvb" secondAttribute="trailing" id="sOy-jl-HBx"/>
<constraint firstItem="i5M-Pr-FkT" firstAttribute="width" secondItem="raA-Qo-8vn" secondAttribute="width" id="wjG-bY-7G0"/>
<constraint firstItem="aRL-MT-ZBH" firstAttribute="leading" secondItem="raA-Qo-8vn" secondAttribute="leading" id="y4C-Aq-a7t"/>
<constraint firstAttribute="bottom" secondItem="aRL-MT-ZBH" secondAttribute="bottom" id="yQ4-7g-M0F"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="9"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<variation key="default">
<mask key="constraints">
<exclude reference="VdN-qU-lhk"/>
<exclude reference="eN2-eT-svS"/>
<exclude reference="gMV-LL-t8v"/>
<exclude reference="iyr-JZ-opr"/>
<exclude reference="TDj-Js-St4"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=compact">
<mask key="constraints">
<exclude reference="VdN-qU-lhk"/>
<include reference="gMV-LL-t8v"/>
<include reference="iyr-JZ-opr"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=regular">
<mask key="constraints">
<include reference="VdN-qU-lhk"/>
<include reference="gMV-LL-t8v"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=compact">
<mask key="constraints">
<include reference="VdN-qU-lhk"/>
<include reference="eN2-eT-svS"/>
<include reference="TDj-Js-St4"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="VdN-qU-lhk"/>
<exclude reference="eN2-eT-svS"/>
<exclude reference="TDj-Js-St4"/>
</mask>
</variation>
</view>
</subviews>
<gestureRecognizers/>
<constraints>
<constraint firstItem="raA-Qo-8vn" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="b8x-4n-fzO" secondAttribute="leading" id="0rU-jZ-bS6"/>
<constraint firstItem="raA-Qo-8vn" firstAttribute="top" secondItem="b8x-4n-fzO" secondAttribute="top" priority="750" constant="56" id="3Ma-5r-TRl"/>
<constraint firstItem="b8x-4n-fzO" firstAttribute="trailing" secondItem="raA-Qo-8vn" secondAttribute="trailing" priority="750" constant="56" id="B9y-4c-fIB"/>
<constraint firstItem="raA-Qo-8vn" firstAttribute="centerX" secondItem="b8x-4n-fzO" secondAttribute="centerX" id="HG8-Kv-tqa"/>
<constraint firstItem="b8x-4n-fzO" firstAttribute="bottom" secondItem="raA-Qo-8vn" secondAttribute="bottom" priority="750" constant="24" id="InF-Ya-BKi"/>
<constraint firstItem="raA-Qo-8vn" firstAttribute="top" secondItem="b8x-4n-fzO" secondAttribute="top" priority="750" constant="24" id="TC5-ex-ABv"/>
<constraint firstItem="b8x-4n-fzO" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="raA-Qo-8vn" secondAttribute="trailing" id="VKq-gf-OxM"/>
<constraint firstItem="raA-Qo-8vn" firstAttribute="leading" secondItem="b8x-4n-fzO" secondAttribute="leading" priority="750" constant="56" id="WK6-fS-Ffr"/>
<constraint firstItem="raA-Qo-8vn" firstAttribute="leading" secondItem="b8x-4n-fzO" secondAttribute="leading" priority="750" constant="24" id="apq-su-Uak"/>
<constraint firstItem="b8x-4n-fzO" firstAttribute="bottom" secondItem="raA-Qo-8vn" secondAttribute="bottom" priority="750" constant="56" id="cbM-5A-LWe"/>
<constraint firstAttribute="bottom" secondItem="T82-qa-a8r" secondAttribute="bottom" id="fVu-JZ-I8h"/>
<constraint firstItem="b8x-4n-fzO" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="raA-Qo-8vn" secondAttribute="bottom" id="hj9-dv-Y64"/>
<constraint firstItem="raA-Qo-8vn" firstAttribute="top" relation="greaterThanOrEqual" secondItem="b8x-4n-fzO" secondAttribute="top" id="iko-gJ-NsP"/>
<constraint firstItem="b8x-4n-fzO" firstAttribute="trailing" secondItem="raA-Qo-8vn" secondAttribute="trailing" priority="750" constant="24" id="iwp-8k-tG4"/>
<constraint firstAttribute="trailing" secondItem="T82-qa-a8r" secondAttribute="trailing" id="kgC-1q-FUB"/>
<constraint firstItem="T82-qa-a8r" firstAttribute="leading" secondItem="i5T-Pr-FkT" secondAttribute="leading" id="maf-fm-wty"/>
<constraint firstItem="T82-qa-a8r" firstAttribute="top" secondItem="i5T-Pr-FkT" secondAttribute="top" id="tox-cK-3YS"/>
<constraint firstItem="raA-Qo-8vn" firstAttribute="centerY" secondItem="b8x-4n-fzO" secondAttribute="centerY" id="xOQ-NQ-T1d"/>
</constraints>
<viewLayoutGuide key="safeArea" id="b8x-4n-fzO"/>
<variation key="default">
<mask key="constraints">
<exclude reference="B9y-4c-fIB"/>
<exclude reference="InF-Ya-BKi"/>
<exclude reference="cbM-5A-LWe"/>
<exclude reference="iwp-8k-tG4"/>
<exclude reference="3Ma-5r-TRl"/>
<exclude reference="TC5-ex-ABv"/>
<exclude reference="WK6-fS-Ffr"/>
<exclude reference="apq-su-Uak"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=compact">
<mask key="constraints">
<include reference="B9y-4c-fIB"/>
<include reference="InF-Ya-BKi"/>
<include reference="TC5-ex-ABv"/>
<include reference="WK6-fS-Ffr"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=regular">
<mask key="constraints">
<include reference="B9y-4c-fIB"/>
<include reference="InF-Ya-BKi"/>
<include reference="TC5-ex-ABv"/>
<include reference="WK6-fS-Ffr"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=compact">
<mask key="constraints">
<include reference="cbM-5A-LWe"/>
<include reference="iwp-8k-tG4"/>
<include reference="3Ma-5r-TRl"/>
<include reference="apq-su-Uak"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="B9y-4c-fIB"/>
<include reference="InF-Ya-BKi"/>
<include reference="TC5-ex-ABv"/>
<include reference="WK6-fS-Ffr"/>
</mask>
</variation>
<point key="canvasLocation" x="33" y="53"/>
</view>
<tapGestureRecognizer enabled="NO" id="cab-7b-e1x">
<connections>
<action selector="onClose:" destination="-1" id="uz7-J3-tJZ"/>
</connections>
</tapGestureRecognizer>
</objects>
<resources>
<image name="ic_ads_remove_close" width="16" height="16"/>
<image name="mappy_heart" width="124" height="142"/>
</resources>
</document>

View file

@ -279,7 +279,13 @@ std::array<Class, 9> const kPreviewCells = {{[_MWMPPPTitle class],
return c;
case PreviewRows::Banner:
auto bannerCell = static_cast<MWMAdBanner *>(c);
[bannerCell configWithAd:data.nativeAd containerType:MWMAdBannerContainerTypePlacePage];
[bannerCell configWithAd:data.nativeAd
containerType:MWMAdBannerContainerTypePlacePage
canRemoveAds:[SubscriptionManager canMakePayments]
onRemoveAds: ^{
[[MapViewController sharedController] showRemoveAds];
}];
self.cachedBannerCell = bannerCell;
return bannerCell;
}

View file

@ -124,7 +124,12 @@ NSString * GetLocalizedTypeName(search::Result const & result)
fallbackAd.cellIndexPath = indexPath;
fallbackAd.dynamicSizeDelegate = self;
}
[cell configWithAd:ad containerType:MWMAdBannerContainerTypeSearch];
[cell configWithAd:ad
containerType:MWMAdBannerContainerTypeSearch
canRemoveAds:[SubscriptionManager canMakePayments]
onRemoveAds: ^{
[[MapViewController sharedController] showRemoveAds];
}];
return cell;
}
case MWMSearchItemTypeSuggestion: