This commit is contained in:
Gautier Pelloux-Prayer 2015-04-17 10:51:03 +02:00
parent 35c5adf02a
commit 0909bb2909
9 changed files with 152 additions and 37 deletions

View file

@ -292,6 +292,7 @@ static UICompositeViewDescription *compositeDescription = nil;
LOGE(@"Cannot sent logs: file is NULL");
return;
}
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
NSString *filename = [appName stringByAppendingString:@".gz"];
NSString *mimeType = @"text/plain";

View file

@ -20,15 +20,28 @@
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
extern NSString *const kInAppProductsReady;
extern NSString *const kLinphoneIAPurchaseNotification;
@interface InAppProductsManager : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver> {
@interface InAppProductsManager : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
}
#define IAPAvailableSucceeded @"IAPAvailableSucceeded"
#define IAPAvailableFailed @"IAPAvailableFailed"
#define IAPPurchaseFailed @"IAPPurchaseFailed"
#define IAPPurchaseSucceeded @"IAPPurchaseSucceeded"
#define IAPRestoreFailed @"IAPRestoreFailed"
#define IAPRestoreSucceeded @"IAPRestoreSucceeded"
typedef NSString* IAPPurchaseNotificationStatus;
@property (readonly) NSArray *inAppProducts;
@property (nonatomic, retain) IAPPurchaseNotificationStatus status;
@property (nonatomic, copy) NSString *errlast;
@property (nonatomic, strong) NSMutableArray *productsAvailable;
@property (nonatomic, strong) NSMutableArray *productsPurchased;
@property (nonatomic, strong) NSMutableArray *productsRestored;
- (void)loadProducts;
- (BOOL)isPurchased:(SKProduct*)product;
- (void)purchaseWithID:(NSString*)productId;
- (void)restore;
@end

View file

@ -20,7 +20,7 @@
#import "InAppProductsManager.h"
#import "Utils.h"
NSString *const kInAppProductsReady = @"InAppProductsReady";
NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotification";
@implementation InAppProductsManager {
bool ready;
@ -39,7 +39,7 @@ NSString *const kInAppProductsReady = @"InAppProductsReady";
return;
}
//TODO: move this list elsewhere
NSArray * list = [[NSArray alloc] initWithArray:@[@"test.tunnel"]];
NSArray * list = [[[NSArray alloc] initWithArray:@[@"test.tunnel"]] autorelease];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:[NSSet setWithArray:list]];
@ -49,39 +49,108 @@ NSString *const kInAppProductsReady = @"InAppProductsReady";
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response {
_inAppProducts = [response.products retain];
LOGI(@"Found %lu products purchasable", (unsigned long)_inAppProducts.count);
_productsAvailable= [[NSMutableArray arrayWithArray: response.products] retain];
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
LOGE(@"Product Identifier with invalid ID %@", invalidIdentifier);
LOGI(@"Found %lu products available", (unsigned long)_productsAvailable.count);
if (response.invalidProductIdentifiers.count > 0) {
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
LOGW(@"Found product Identifier with invalid ID '%@'", invalidIdentifier);
}
[self postNotificationforStatus:IAPAvailableFailed];
} else {
[self postNotificationforStatus:IAPAvailableSucceeded];
}
ready = true;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
_inAppProducts, @"products",
nil];
dispatch_async(dispatch_get_main_queue(), ^(void){
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppProductsReady object:self userInfo:dict];
});
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
}
- (BOOL)isPurchased:(SKProduct*)product {
for (SKProduct *prod in _inAppProducts) {
for (SKProduct *prod in _productsPurchased) {
if (prod == product) {
LOGE(@"Is %@ bought? assuming NO", product.localizedTitle);
return false; //todo
bool isBought = true;
LOGE(@"%@ is %s bought.", product.localizedTitle, isBought?"":"NOT");
return isBought;
}
}
return false;
}
- (void)purchaseWithID:(NSString *)productId {
for (SKProduct *product in _productsAvailable) {
if ([product.productIdentifier compare:productId options:NSLiteralSearch] == NSOrderedSame) {
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
return;
}
}
[self postNotificationforStatus:IAPPurchaseFailed];
}
@end
-(void)restore {
_productsRestored = [[NSMutableArray alloc] initWithCapacity:0];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for(SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStatePurchased:
case SKPaymentTransactionStateRestored:
[_productsPurchased addObject:transaction];
[self completeTransaction:transaction forStatus:IAPPurchaseSucceeded];
break;
default:
_errlast = [NSString stringWithFormat:@"Purchase of %@ failed.",transaction.payment.productIdentifier];
[self completeTransaction:transaction forStatus:IAPPurchaseFailed];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions {
for(SKPaymentTransaction * transaction in transactions) {
NSLog(@"%@ was removed from the payment queue.", transaction.payment.productIdentifier);
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
if (error.code != SKErrorPaymentCancelled) {
_errlast = [error localizedDescription];
[self postNotificationforStatus:IAPRestoreFailed];
}
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
LOGI(@"All restorable transactions have been processed by the payment queue.");
// for (SKPayment *payment in queue) {
// [queue transactions]
// [_productsRestored addObject:payment.productIdentifier];
// }
for (SKPaymentTransaction *transaction in queue.transactions) {
NSString *productID = transaction.payment.productIdentifier;
[_productsRestored addObject:productID];
NSLog (@"product id is %@" , productID);
}
}
-(void)postNotificationforStatus:(IAPPurchaseNotificationStatus)status {
_status = status;
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneIAPurchaseNotification object:self];
LOGI(@"Triggering notification for status %@", status);
}
-(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(IAPPurchaseNotificationStatus)status {
if (transaction.error.code != SKErrorPaymentCancelled) {
[self postNotificationforStatus:status];
} else {
_status = status;
}
// Remove the transaction from the queue for purchased and restored statuses
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
@end

View file

@ -20,7 +20,25 @@
- (void)viewWillAppear:(BOOL)animated {
iapm = [[LinphoneManager instance] iapManager];
currentExpanded = -1;
[iapm loadProducts];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onIAPPurchaseNotification:)
name:kLinphoneIAPurchaseNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:kLinphoneIAPurchaseNotification
object:nil];
}
- (void)onIAPPurchaseNotification:(NSNotification*)notif {
if ([[iapm status] isEqual: IAPAvailableSucceeded]) {
[[self tableView] reloadData];
}
}
#pragma mark - Table view data source
@ -30,7 +48,7 @@
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [iapm inAppProducts].count;
return [iapm productsAvailable].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -39,7 +57,7 @@
if (cell == nil) {
cell = [[[InAppProductsCell alloc] initWithIdentifier:kCellId maximized:(currentExpanded == indexPath.row)] autorelease];
}
SKProduct *prod = [[[[LinphoneManager instance] iapManager] inAppProducts] objectAtIndex:indexPath.row];
SKProduct *prod = [[[[LinphoneManager instance] iapManager] productsAvailable] objectAtIndex:indexPath.row];
[cell.ptitle setText: [prod localizedTitle]];
[cell.pdescription setText: [prod localizedDescription]];
[cell.pprice setText: [NSString stringWithFormat:@"%@", [prod price]]];
@ -75,6 +93,7 @@
[alert release];
} else {
//try to purchase item, and if successfull change the switch
LOGI(@"Trying to purchase %@", cell.productID);
[[[LinphoneManager instance] iapManager] purchaseWithID: cell.productID];
}
}

View file

@ -14,5 +14,6 @@
}
@property (nonatomic, retain) IBOutlet InAppProductsTableViewController* tableController;
- (IBAction)onRestoreClicked:(UIButton *)sender;
@end

View file

@ -67,4 +67,7 @@ static UICompositeViewDescription *compositeDescription = nil;
return compositeDescription;
}
- (IBAction)onRestoreClicked:(UIButton *)sender {
[[[LinphoneManager instance] iapManager] restore];
}
@end

View file

@ -16,8 +16,8 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="10" sectionFooterHeight="10" id="fwu-cz-Gse" userLabel="productsTable">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="1" sectionFooterHeight="10" id="fwu-cz-Gse" userLabel="productsTable">
<rect key="frame" x="0.0" y="38" width="320" height="422"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<connections>
@ -25,6 +25,16 @@
<outlet property="delegate" destination="FRQ-Fw-iZ8" id="2Dx-aQ-XVB"/>
</connections>
</tableView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="XrD-Mn-6EM" userLabel="restoreButton">
<rect key="frame" x="0.0" y="0.0" width="320" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Restore">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="onRestoreClicked:" destination="-1" eventType="touchUpInside" id="ZYY-WP-1of"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>

View file

@ -271,7 +271,8 @@ struct codec_name_pref_table codec_pref_table[]={
[LinphoneLogger logc:LinphoneLoggerError format:"cannot register route change handler [%ld]",lStatus];
}
_iapManager = [[InAppProductsManager alloc] init];
NSString *path = [[NSBundle mainBundle] pathForResource:@"msg" ofType:@"wav"];
self.messagePlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:path] error:nil] autorelease];
@ -1283,8 +1284,6 @@ static LinphoneCoreVTable linphonec_vtable = {
[_contactSipField release];
_contactSipField = [[self lpConfigStringForKey:@"contact_im_type_value" withDefault:@"SIP"] retain];
_iapManager = [[InAppProductsManager alloc] init];
fastAddressBook = [[FastAddressBook alloc] init];
linphone_core_set_root_ca(theLinphoneCore, lRootCa);

View file

@ -53,7 +53,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<string>2</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationExitsOnSuspend</key>