2017-11-09 18:43:37 -08:00
//
// PremiumViewController . m
// NewsBlur
//
// Created by Samuel Clay on 11 / 9 / 17.
// Copyright © 2017 NewsBlur . All rights reserved .
//
# import "PremiumViewController.h"
2017-11-10 17:57:50 -08:00
# import "NewsBlur-Swift.h"
2017-11-09 18:43:37 -08:00
# define kPremium24ProductIdentifier @ "newsblur_premium_auto_renew_24"
# define kPremium36ProductIdentifier @ "newsblur_premium_auto_renew_36"
@ interface PremiumViewController ( )
@ end
@ implementation PremiumViewController
@ synthesize appDelegate ;
@ synthesize productsTable ;
@ synthesize reasonsTable ;
@ synthesize spinner ;
@ synthesize navigationBar ;
@ synthesize doneButton ;
@ synthesize restoreButton ;
2017-11-10 17:37:31 -08:00
@ synthesize freeView ;
@ synthesize premiumView ;
@ synthesize confettiView ;
2017-11-14 21:46:21 -08:00
@ synthesize productsHeight ;
2017-11-15 10:47:57 -08:00
@ synthesize labelTitle ;
@ synthesize labelSubtitle ;
2017-11-15 11:17:55 -08:00
@ synthesize labelPremiumTitle ;
2017-11-15 11:05:24 -08:00
@ synthesize labelPremiumExpire ;
2017-11-09 18:43:37 -08:00
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
products = [ NSArray array ] ;
reasons = @ [ @ [ @ "Enable every site by going premium" , @ "g_icn_buffer" ] ,
@ [ @ "Sites updated up to 10x more often" , @ "g_icn_lightning" ] ,
@ [ @ "River of News (reading by folder)" , @ "g_icn_folder_black" ] ,
@ [ @ "Search sites and folders" , @ "g_icn_search_black" ] ,
@ [ @ "Save stories with searchable tags" , @ "g_icn_tag_black" ] ,
@ [ @ "Privacy options for your blurblog" , @ "g_icn_privacy" ] ,
@ [ @ "Custom RSS feeds for folders and saved stories" , @ "g_icn_folder_black" ] ,
@ [ @ "Text view conveniently extracts the story" , @ "g_icn_textview_black" ] ,
2017-11-14 21:46:21 -08:00
@ [ @ "You feed Shiloh, my poor, hungry dog, for a month" , @ "g_icn_eating" ] ,
2017-11-09 18:43:37 -08:00
] ;
2017-11-10 17:37:31 -08:00
UIBarButtonItem * cancelButton = [ [ UIBarButtonItem alloc ] initWithTitle : @ "Done"
style : UIBarButtonItemStylePlain
target : self
action : @ selector ( closeDialog : ) ] ;
[ self . navigationItem setLeftBarButtonItem : cancelButton ] ;
2017-11-14 21:46:21 -08:00
self . productsTable . tableFooterView = [ UIView new ] ;
2017-11-14 21:57:41 -08:00
self . reasonsTable . tableFooterView = [ self makeShilohCell ] ;
2017-11-14 21:46:21 -08:00
self . productsTable . separatorColor = [ UIColor clearColor ] ;
2017-11-09 18:43:37 -08:00
}
- ( void ) viewWillAppear : ( BOOL ) animated {
[ super viewWillAppear : animated ] ;
2017-11-16 16:47:01 -08:00
UIBarButtonItem * restoreButton = [ [ UIBarButtonItem alloc ] initWithTitle : @ "Restore"
style : UIBarButtonItemStylePlain
target : self
action : @ selector ( restorePurchase : ) ] ;
[ self . navigationItem setRightBarButtonItem : restoreButton ] ;
2017-11-28 16:22:46 -08:00
self . navigationItem . title = @ "NewsBlur Premium" ;
2017-11-09 18:43:37 -08:00
[ self loadProducts ] ;
2017-11-15 10:47:57 -08:00
[ self updateTheme ] ;
2017-11-16 16:47:01 -08:00
[ confettiView setNeedsLayout ] ;
[ confettiView startConfetti ] ;
}
- ( void ) viewDidAppear : ( BOOL ) animated {
[ super viewDidAppear : animated ] ;
[ confettiView setNeedsLayout ] ;
}
- ( void ) viewDidDisappear : ( BOOL ) animated {
[ super viewDidDisappear : animated ] ;
[ confettiView stopConfetti ] ;
2017-11-09 18:43:37 -08:00
}
- ( void ) closeDialog : ( id ) sender {
[ self dismissViewControllerAnimated : YES completion : nil ] ;
}
2017-11-15 10:47:57 -08:00
- ( void ) updateTheme {
[ super updateTheme ] ;
self . productsTable . backgroundColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
self . reasonsTable . backgroundColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
self . view . backgroundColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
self . labelTitle . textColor = UIColorFromRGB ( 0 x0c0c0c ) ;
self . labelSubtitle . textColor = UIColorFromRGB ( 0 x0c0c0c ) ;
2017-11-15 11:17:55 -08:00
self . labelPremiumExpire . textColor = UIColorFromRGB ( 0 x0c0c0c ) ;
self . labelPremiumTitle . textColor = UIColorFromRGB ( 0 x0c0c0c ) ;
self . labelPremiumExpire . shadowColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
self . labelPremiumTitle . shadowColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
2017-11-15 10:47:57 -08:00
[ self . productsTable reloadData ] ;
[ self . reasonsTable reloadData ] ;
2017-11-14 21:46:21 -08:00
}
2017-11-09 18:43:37 -08:00
# pragma mark - StoreKit
- ( void ) loadProducts {
[ spinner startAnimating ] ;
productsTable . hidden = YES ;
if ( [ SKPaymentQueue canMakePayments ] ) {
SKProductsRequest * productsRequest = [ [ SKProductsRequest alloc ]
2017-11-10 17:37:31 -08:00
initWithProductIdentifiers : [ NSSet setWithObjects :
kPremium24ProductIdentifier ,
2017-11-09 18:43:37 -08:00
kPremium36ProductIdentifier , nil ] ] ;
productsRequest . delegate = self ;
2017-11-10 17:37:31 -08:00
request = productsRequest ;
2017-11-09 18:43:37 -08:00
[ productsRequest start ] ;
} else {
NSLog ( @ "User cannot make payments due to parental controls" ) ;
}
2017-11-10 17:37:31 -08:00
if ( appDelegate . isPremium ) {
freeView . hidden = YES ;
premiumView . hidden = NO ;
2017-11-16 16:47:01 -08:00
self . navigationItem . rightBarButtonItem = nil ;
2017-11-15 11:05:24 -08:00
2017-11-15 11:14:05 -08:00
if ( appDelegate . premiumExpire ! = 0 ) {
NSDate * date = [ NSDate dateWithTimeIntervalSince1970 : appDelegate . premiumExpire ] ;
NSDateFormatter * dateFormatter = [ [ NSDateFormatter alloc ] init ] ;
[ dateFormatter setDateFormat : @ "MMMM d, yyyy" ] ;
labelPremiumExpire . text = [ NSString stringWithFormat : @ "Your premium subscription will renew on %@" , [ dateFormatter stringFromDate : date ] ] ;
2017-11-15 11:05:24 -08:00
} else {
labelPremiumExpire . text = @ "Your premium subscription is set to never expire. Whoa!" ;
}
2017-11-10 17:37:31 -08:00
} else {
freeView . hidden = NO ;
premiumView . hidden = YES ;
}
2017-11-09 18:43:37 -08:00
}
- ( void ) productsRequest : ( SKProductsRequest * ) request didReceiveResponse : ( SKProductsResponse * ) response {
SKProduct * validProduct = nil ;
NSUInteger count = [ response . products count ] ;
if ( count > 0 ) {
products = response . products ;
spinner . hidden = YES ;
productsTable . hidden = NO ;
[ productsTable reloadData ] ;
} else if ( ! validProduct ) {
NSLog ( @ "No products available" ) ;
// this is called if your product id is not valid , this shouldn ' t be called unless that happens .
}
}
- ( void ) purchase : ( SKProduct * ) product {
SKPayment * payment = [ SKPayment paymentWithProduct : product ] ;
[ [ SKPaymentQueue defaultQueue ] addTransactionObserver : self ] ;
[ [ SKPaymentQueue defaultQueue ] addPayment : payment ] ;
2017-11-10 17:37:31 -08:00
productsTable . hidden = YES ;
spinner . hidden = NO ;
2017-11-09 18:43:37 -08:00
}
- ( IBAction ) restorePurchase : ( id ) sender {
2017-11-16 16:47:01 -08:00
productsTable . hidden = YES ;
spinner . hidden = NO ;
2017-11-09 18:43:37 -08:00
[ [ SKPaymentQueue defaultQueue ] addTransactionObserver : self ] ;
[ [ SKPaymentQueue defaultQueue ] restoreCompletedTransactions ] ;
}
- ( void ) paymentQueueRestoreCompletedTransactionsFinished : ( SKPaymentQueue * ) queue {
NSLog ( @ "received restored transactions: %lu" , ( unsigned long ) queue . transactions . count ) ;
for ( SKPaymentTransaction * transaction in queue . transactions ) {
if ( transaction . transactionState = = SKPaymentTransactionStateRestored ) {
NSLog ( @ "Transaction state -> Restored" ) ;
// NSString * productID = transaction . payment . productIdentifier ;
[ [ SKPaymentQueue defaultQueue ] finishTransaction : transaction ] ;
2017-11-10 17:37:31 -08:00
2017-11-15 17:26:27 -08:00
[ self finishTransaction : transaction ] ;
2017-11-16 16:47:01 -08:00
return ;
2017-11-09 18:43:37 -08:00
}
}
2017-11-16 16:47:01 -08:00
2017-11-09 18:43:37 -08:00
}
- ( void ) paymentQueue : ( SKPaymentQueue * ) queue updatedTransactions : ( NSArray * ) transactions {
for ( SKPaymentTransaction * transaction in transactions ) {
switch ( transaction . transactionState ) {
case SKPaymentTransactionStatePurchasing : NSLog ( @ "Transaction state -> Purchasing" ) ;
// called when the user is in the process of purchasing , do not add any of your own code here .
break ;
case SKPaymentTransactionStatePurchased :
// this is called when the user has successfully purchased the package ( Cha - Ching ! )
[ [ SKPaymentQueue defaultQueue ] finishTransaction : transaction ] ;
NSLog ( @ "Transaction state -> Purchased" ) ;
2017-11-10 17:37:31 -08:00
2017-11-15 17:26:27 -08:00
[ self finishTransaction : transaction ] ;
2017-11-09 18:43:37 -08:00
break ;
case SKPaymentTransactionStateRestored :
NSLog ( @ "Transaction state -> Restored" ) ;
// add the same code as you did from SKPaymentTransactionStatePurchased here
[ [ SKPaymentQueue defaultQueue ] finishTransaction : transaction ] ;
2017-11-10 17:37:31 -08:00
2017-11-15 17:26:27 -08:00
[ self finishTransaction : transaction ] ;
2017-11-09 18:43:37 -08:00
break ;
case SKPaymentTransactionStateDeferred :
2017-11-15 17:45:38 -08:00
NSLog ( @ "Transaction state -> Deferred" ) ;
case SKPaymentTransactionStateFailed :
NSLog ( @ "Transaction state -> Failed" ) ;
2017-11-09 18:43:37 -08:00
// called when the transaction does not finish
if ( transaction . error . code = = SKErrorPaymentCancelled ) {
NSLog ( @ "Transaction state -> Cancelled" ) ;
// the user cancelled the payment ; (
}
2017-11-16 16:47:01 -08:00
[ self informError : @ "Transaction failed!" ] ;
2017-11-14 21:46:21 -08:00
productsTable . hidden = NO ;
spinner . hidden = YES ;
2017-11-09 18:43:37 -08:00
[ [ SKPaymentQueue defaultQueue ] finishTransaction : transaction ] ;
break ;
}
}
}
2017-11-15 17:26:27 -08:00
- ( void ) finishTransaction : ( SKPaymentTransaction * ) transaction {
2017-11-10 17:37:31 -08:00
productsTable . hidden = YES ;
spinner . hidden = NO ;
NSURL * receiptURL = [ [ NSBundle mainBundle ] appStoreReceiptURL ] ;
NSData * receipt = [ NSData dataWithContentsOfURL : receiptURL ] ;
if ( ! receipt ) {
NSLog ( @ " No receipt found!" ) ;
[ self informError : @ "No receipt found" ] ;
return ;
}
NSString * urlString = [ NSString stringWithFormat : @ "%@/profile/save_ios_receipt/" ,
appDelegate . url ] ;
NSDictionary * params = @ {
2018-07-22 17:18:22 -04:00
// @ "receipt" : [ receipt base64EncodedStringWithOptions : 0 ] ,
2017-11-16 16:47:01 -08:00
@ "transaction_identifier" : transaction . originalTransaction . transactionIdentifier ,
2017-11-15 17:26:27 -08:00
@ "product_identifier" : transaction . payment . productIdentifier ,
2017-11-10 17:37:31 -08:00
} ;
[ appDelegate . networkManager POST : urlString parameters : params progress : nil success : ^ ( NSURLSessionDataTask * _Nonnull task , id _Nullable responseObject ) {
NSLog ( @ "Sent iOS receipt: %@" , params ) ;
productsTable . hidden = NO ;
spinner . hidden = YES ;
NSDictionary * results = ( NSDictionary * ) responseObject ;
appDelegate . isPremium = [ [ results objectForKey : @ "is_premium" ] integerValue ] = = 1 ;
2017-11-15 11:14:05 -08:00
id premiumExpire = [ results objectForKey : @ "premium_expire" ] ;
if ( premiumExpire && ! [ premiumExpire isKindOfClass : [ NSNull class ] ] && premiumExpire ! = 0 ) {
appDelegate . premiumExpire = [ premiumExpire integerValue ] ;
2017-11-10 17:37:31 -08:00
}
2017-11-10 17:57:50 -08:00
[ self loadProducts ] ;
2017-11-16 16:47:01 -08:00
[ appDelegate reloadFeedsView : YES ] ;
2017-11-10 17:37:31 -08:00
} failure : ^ ( NSURLSessionDataTask * _Nullable task , NSError * _Nonnull error ) {
NSLog ( @ "Failed to send receipt: %@" , params ) ;
productsTable . hidden = NO ;
spinner . hidden = YES ;
NSHTTPURLResponse * httpResponse = ( NSHTTPURLResponse * ) task . response ;
[ self informError : error statusCode : httpResponse . statusCode ] ;
2017-11-10 17:57:50 -08:00
[ self loadProducts ] ;
2017-11-10 17:37:31 -08:00
} ] ;
}
2017-11-09 18:43:37 -08:00
# pragma mark - Table Delegate
- ( NSInteger ) numberOfSectionsInTableView : ( UITableView * ) tableView {
return 1 ;
}
- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section {
if ( tableView = = reasonsTable ) {
return [ reasons count ] ;
} else if ( tableView = = productsTable ) {
return [ products count ] ;
}
return 0 ;
}
- ( UITableViewCell * ) tableView : ( UITableView * ) tableView cellForRowAtIndexPath : ( NSIndexPath * ) indexPath {
2017-11-14 21:46:21 -08:00
UITableViewCell * cell ;
2017-11-09 18:43:37 -08:00
if ( tableView = = reasonsTable ) {
2017-11-14 21:46:21 -08:00
static NSString * ReasonsCellIndentifier = @ "PremiumReasonsCell" ;
cell = [ tableView dequeueReusableCellWithIdentifier : ReasonsCellIndentifier ] ;
if ( ! cell ) {
cell = [ [ UITableViewCell alloc ] initWithStyle : UITableViewCellStyleDefault reuseIdentifier : ReasonsCellIndentifier ] ;
}
2017-11-15 10:47:57 -08:00
cell . backgroundColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
2017-11-09 18:43:37 -08:00
cell . selectionStyle = UITableViewCellSelectionStyleNone ;
cell . textLabel . text = reasons [ indexPath . row ] [ 0 ] ;
2017-11-10 17:37:31 -08:00
cell . textLabel . font = [ UIFont systemFontOfSize : 14. f weight : UIFontWeightLight ] ;
2017-11-15 10:47:57 -08:00
cell . textLabel . textColor = UIColorFromRGB ( 0 x0c0c0c ) ;
2017-11-10 17:37:31 -08:00
cell . textLabel . numberOfLines = 2 ;
2017-11-14 21:46:21 -08:00
CGSize itemSize = CGSizeMake ( 18 , 18 ) ;
2017-11-09 18:43:37 -08:00
cell . imageView . image = [ UIImage imageNamed : reasons [ indexPath . row ] [ 1 ] ] ;
2017-11-14 21:46:21 -08:00
cell . imageView . contentMode = UIViewContentModeScaleAspectFill ;
cell . imageView . clipsToBounds = NO ;
2017-11-09 18:43:37 -08:00
UIGraphicsBeginImageContextWithOptions ( itemSize , NO , UIScreen . mainScreen . scale ) ;
CGRect imageRect = CGRectMake ( 0.0 , 0.0 , itemSize . width , itemSize . height ) ;
[ cell . imageView . image drawInRect : imageRect ] ;
cell . imageView . image = UIGraphicsGetImageFromCurrentImageContext ( ) ;
UIGraphicsEndImageContext ( ) ;
2017-11-14 21:46:21 -08:00
} else { // } if ( tableView = = productsTable ) {
static NSString * CellIndentifier = @ "PremiumCell" ;
cell = [ tableView dequeueReusableCellWithIdentifier : CellIndentifier ] ;
if ( ! cell ) {
cell = [ [ UITableViewCell alloc ] initWithStyle : UITableViewCellStyleSubtitle reuseIdentifier : CellIndentifier ] ;
}
SKProduct * product = products [ indexPath . row ] ;
2017-11-09 18:43:37 -08:00
cell . selectionStyle = UITableViewCellSelectionStyleBlue ;
2017-11-15 10:47:57 -08:00
cell . backgroundColor = UIColorFromRGB ( 0 xf4f4f4 ) ;
cell . textLabel . textColor = UIColorFromRGB ( 0 x203070 ) ;
2017-11-15 11:05:24 -08:00
cell . textLabel . numberOfLines = 2 ;
2017-11-15 10:47:57 -08:00
cell . detailTextLabel . textColor = UIColorFromRGB ( 0 x0c0c0c ) ;
2017-11-14 21:46:21 -08:00
NSNumberFormatter * formatter = [ [ NSNumberFormatter alloc ] init ] ;
[ formatter setFormatterBehavior : NSNumberFormatterBehavior10_4 ] ;
[ formatter setNumberStyle : NSNumberFormatterCurrencyStyle ] ;
[ formatter setLocale : product . priceLocale ] ;
2017-11-30 15:24:26 -08:00
if ( ! product . localizedTitle ) {
cell . textLabel . text = [ NSString stringWithFormat : @ "NewsBlur Premium Subscription" ] ;
} else {
cell . textLabel . text = [ NSString stringWithFormat : @ "%@" , product . localizedTitle ] ;
}
2017-11-28 16:22:46 -08:00
cell . detailTextLabel . text = [ NSString stringWithFormat : @ "%@ per year (%@/month)" , [ formatter stringFromNumber : product . price ] , [ formatter stringFromNumber : @ ( round ( [ product . price doubleValue ] / 12. f ) ) ] ] ;;
2017-11-14 21:46:21 -08:00
UILabel * label = [ [ UILabel alloc ] init ] ;
label . text = @ "👉🏽" ;
label . opaque = NO ;
label . backgroundColor = UIColor . clearColor ;
label . font = [ UIFont systemFontOfSize : 18 ] ;
CGSize measuredSize = [ label . text sizeWithAttributes : @ { NSFontAttributeName : label . font } ] ;
label . frame = CGRectMake ( 0 , 0 , measuredSize . width , measuredSize . height ) ;
UIGraphicsBeginImageContextWithOptions ( label . bounds . size , label . opaque , 0.0 ) ;
[ label . layer renderInContext : UIGraphicsGetCurrentContext ( ) ] ;
cell . imageView . image = UIGraphicsGetImageFromCurrentImageContext ( ) ;
}
return cell ;
}
2017-11-14 21:57:41 -08:00
- ( CGFloat ) tableView : ( UITableView * ) tableView heightForFooterInSection : ( NSInteger ) section {
return 0 ;
}
2017-11-14 21:46:21 -08:00
2017-11-14 21:57:41 -08:00
- ( UIView * ) makeShilohCell {
UIView * view = [ [ UIView alloc ] initWithFrame : CGRectMake ( 0 , 0 , self . view . frame . size . width , 96 + 12 + 12 ) ] ;
UIImageView * imgView = [ [ UIImageView alloc ] init ] ;
imgView . translatesAutoresizingMaskIntoConstraints = NO ;
imgView . tag = 1 ;
imgView . contentMode = UIViewContentModeScaleAspectFit ;
[ view addSubview : imgView ] ;
2017-11-09 18:43:37 -08:00
2017-11-14 21:57:41 -08:00
[ view addConstraint : [ NSLayoutConstraint constraintWithItem : imgView attribute : NSLayoutAttributeCenterX relatedBy : NSLayoutRelationEqual toItem : view attribute : NSLayoutAttributeCenterX multiplier : 1.0 constant : 0 ] ] ;
[ view addConstraint : [ NSLayoutConstraint constraintWithItem : imgView attribute : NSLayoutAttributeTop relatedBy : NSLayoutRelationEqual toItem : view attribute : NSLayoutAttributeTop multiplier : 1.0 constant : 12 ] ] ;
2017-11-14 22:21:21 -08:00
[ imgView addConstraint : [ NSLayoutConstraint constraintWithItem : imgView attribute : NSLayoutAttributeHeight relatedBy : NSLayoutRelationEqual toItem : nil attribute : NSLayoutAttributeNotAnAttribute multiplier : 1.0 constant : 96 ] ] ;
2017-11-14 21:57:41 -08:00
UIImageView * _imgView = ( UIImageView * ) [ view viewWithTag : 1 ] ;
2017-11-14 21:46:21 -08:00
_imgView . image = [ UIImage imageNamed : @ "Shiloh.jpg" ] ;
2017-11-14 21:57:41 -08:00
return view ;
2017-11-09 18:43:37 -08:00
}
2017-11-10 17:37:31 -08:00
- ( void ) tableView : ( UITableView * ) tableView didSelectRowAtIndexPath : ( NSIndexPath * ) indexPath {
if ( tableView = = productsTable ) {
[ self purchase : products [ indexPath . row ] ] ;
}
[ tableView deselectRowAtIndexPath : indexPath animated : YES ] ;
}
2017-11-09 18:43:37 -08:00
@ end