[InApp] shopView and InAppProductsManager updated to support inapp + linphonerc

This commit is contained in:
Brieuc Viel 2016-09-30 15:28:51 +02:00
parent 236d8a19a3
commit 09793142af
8 changed files with 123 additions and 291 deletions

View file

@ -80,16 +80,22 @@ typedef NSString *IAPPurchaseNotificationStatus;
// TRUE if accountActivate was started but we did not receive response from server yet
@property(readonly) BOOL accountActivationInProgress;
// TRUE if accountActivate activated
@property(readonly) BOOL accountActivated;
- (BOOL)isPurchasedWithID:(NSString *)productId;
// Purchase an account. You should not use this if manager is not available yet.
- (BOOL)purchaseAccount:(NSString *)phoneNumber
/*- (BOOL)purchaseAccount:(NSString *)phoneNumber
withPassword:(NSString *)password
andEmail:(NSString *)email
monthly:(BOOL)monthly;
*/
// Purchase a product. You should not use this if manager is not available yet.
- (BOOL)purchaseWithID:(NSString *)productID;
// Activate purchased account.
- (BOOL)activateAccount:(NSString *)phoneNumber;
//- (BOOL)activateAccount:(NSString *)phoneNumber;
// Check if account is activated.
//- (BOOL)checkAccountActivated:(NSString *)phoneNumber;
// restore user purchases. You should not use this if manager is not available yet. Must be at a user action ONLY.
- (BOOL)restore;
@ -101,6 +107,6 @@ typedef NSString *IAPPurchaseNotificationStatus;
- (void)check;
// deal with xmlrpc response
- (void)dealWithXmlRpcResponse:(LinphoneXmlRpcRequest *)request;
//- (void)dealWithXmlRpcResponse:(LinphoneXmlRpcRequest *)request;
@end

View file

@ -64,6 +64,7 @@
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[self loadProducts];
}
//[self check];
}
return self;
}
@ -117,95 +118,6 @@
}
}
- (BOOL)purchaseAccount:(NSString *)phoneNumber
withPassword:(NSString *)password
andEmail:(NSString *)email
monthly:(BOOL)monthly {
if (phoneNumber) {
NSString *productID =
[LinphoneManager.instance lpConfigStringForKey:(monthly ? @"paid_account_id_monthly" : @"paid_account_id")
inSection:@"in_app_purchase"];
self.accountCreationData = @{ @"phoneNumber" : phoneNumber, @"password" : password, @"email" : email };
if (![self purchaseWithID:productID]) {
self.accountCreationData = nil;
}
return true;
}
return false;
}
- (BOOL)activateAccount:(NSString *)phoneNumber {
if (phoneNumber) {
NSString *receiptBase64 = [self getReceipt];
if (receiptBase64) {
// const char *URL = [LinphoneManager.instance lpConfigStringForKey:@"receipt_validation_url"
// inSection:@"in_app_purchase"].UTF8String;
// buying for the first time: need to create the account
// if ([transaction.transactionIdentifier
// isEqualToString:transaction.originalTransaction.transactionIdentifier]) {
[XMLRPCHelper.self sendXMLRPCRequestWithParams:@"activate_account"
withParams:@[ @"", phoneNumber, receiptBase64, @"", @"apple" ]
onSuccess:^(NSString *response) {
if (response) {
[self setAccountBiteActivationInProgress:NO];
LOGI(@"Account activated callback - response: %@", response);
}
}];
_accountActivationInProgress = YES;
LOGI(@"XMLRPC query ");
return true;
} else {
LOGE(@"Trying to activate account but no receipt available yet (probably doing it too soon)");
}
}
return false;
}
// onError Callback block
/*- (void)XMLRPCRequest:(LinphoneXmlRpcRequest *)request didFailWithError:(NSError *)error {
if (!_enabled)
return;
_available = true;
if ([[request method] isEqualToString:@"activate_account"]) {
_accountActivationInProgress = NO;
}
LOGE(@"Communication issue (%@)", [error localizedDescription]);
NSString *errorString =
[NSString stringWithFormat:NSLocalizedString(@"Communication issue (%@)", nil), [error localizedDescription]];
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Communication issue", nil)
message:errorString
delegate:nil
cancelButtonTitle:NSLocalizedString(@"Continue", nil)
otherButtonTitles:nil, nil];
[errorView show];
latestReceiptMD5 = nil;
NSDictionary *dict = @{ @"error_msg" : errorString };
[self postNotificationforStatus:kIAPReceiptFailed withDict:dict];
}
*/
- (void)setAccountBiteActivationInProgress:(BOOL)activationInProgress {
_accountActivationInProgress = activationInProgress;
}
- (void)dealWithXmlRpcResponse:(LinphoneXmlRpcRequest *)request {
//[ShopView hideWaitingView:TRUE];
LOGI(@"XMLRPC query ");
const char *requestContent = linphone_xml_rpc_request_get_content(request);
NSString *rContent = [NSString stringWithFormat:@"%s", requestContent];
if ([rContent containsString:@"activate_account"]) {
_accountActivationInProgress = NO;
LOGI(@"Account activated callback");
}
}
- (BOOL)restore {
if (!_enabled || !_initialized || !_available) {
NSDictionary *dict = @{ @"error_msg" : NSLocalizedString(@"In apps not ready yet", nil) };
@ -321,52 +233,59 @@
[req start];
return;
}
// Hide waiting view
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIView *topView = window.rootViewController.view;
UIView *waitview = (UIView *)[topView viewWithTag:288];
[waitview setHidden:TRUE];
// only check the receipt if it has changed
if (latestReceiptMD5 == nil || ![latestReceiptMD5 isEqualToString:[receiptBase64 md5]]) {
// transaction is null when restoring user purchases at application start or if user clicks the "restore" button
// We must validate the receipt on our server
// buying for the first time: need to create the account
// if ([transaction.transactionIdentifier
// isEqualToString:transaction.originalTransaction.transactionIdentifier]) {
if (self.accountCreationData.count == 3) {
NSString *dataPhone = [_accountCreationData objectForKey:@"phoneNumber"];
NSString *dataEmail = [_accountCreationData objectForKey:@"email"];
if ([[self getPhoneNumber] length] > 0) {
NSString *phoneNumber = [self getPhoneNumber];
NSString *password = [self getPassword];
[XMLRPCHelper.self
sendXMLRPCRequestWithParams:@"activate_account"
withParams:@[ dataPhone, receiptBase64, @"", @"apple", dataEmail ]
sendXMLRPCRequestWithParams:@"update_expiration_date"
withParams:@[
phoneNumber,
password,
@"",
receiptBase64
] // keep @"" mandatory for optional domain //, @"", @"apple" ]
onSuccess:^(NSString *response) {
if (response) {
LOGI(@"create and activate_account callback - response: %@", response);
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIView *topView = window.rootViewController.view;
UIView *waitview = (UIView *)[topView viewWithTag:288];
[waitview setHidden:TRUE];
// NSString *productID = [LinphoneManager.instance
// lpConfigStringForKey:@"paid_account_id" inSection:@"in_app_purchase"];
LOGI(@"update_expiration_date callback - response: %@", response);
if ([response containsString:@"ERROR"]) {
LOGE(@"Failed with error %@", response);
NSString *errorMsg;
if ([response isEqualToString:@"ERROR_ACCOUNT_ALREADY_EXISTS"]) {
errorMsg =
NSLocalizedString(@"This account is already registered.", nil);
} else if ([response isEqualToString:@"ERROR_UID_ALREADY_IN_USE"]) {
errorMsg = NSLocalizedString(@"You already own an account.", nil);
} else if ([response isEqualToString:@"ERROR_ACCOUNT_DOESNT_EXIST"]) {
errorMsg = NSLocalizedString(@"You have already purchased an account "
@"but it does not exist anymore.",
nil);
} else if ([response isEqualToString:@"ERROR_PURCHASE_CANCELLED"]) {
errorMsg = NSLocalizedString(@"You cancelled your account.", nil);
} else {
errorMsg = [NSString
stringWithFormat:NSLocalizedString(@"Unknown error (%@).", nil),
response];
}
// NSDictionary *dict = @{ @"product_id" : productID, @"error_msg" :
// errorMsg };
//[self postNotificationforStatus:kIAPPurchaseFailed withDict:dict];
}
}
}];
self.accountCreationData = nil; // otherwise simply renewing
} else {
if ([[self getPhoneNumber] length] > 0) {
NSString *phoneNumber = [self getPhoneNumber];
[XMLRPCHelper.self
sendXMLRPCRequestWithParams:@"update_expiration_date"
withParams:@[ phoneNumber, receiptBase64, @"", @"apple" ]
onSuccess:^(NSString *response) {
if (response) {
LOGI(@"update_expiration_date callback - response: %@", response);
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIView *topView = window.rootViewController.view;
UIView *waitview = (UIView *)[topView viewWithTag:288];
[waitview setHidden:TRUE];
}
}];
} else {
LOGW(@"No SIP URI configured, doing nothing");
_available = true;
return;
}
LOGW(@"No SIP URI configured, doing nothing");
_available = true;
return;
}
latestReceiptMD5 = [receiptBase64 md5];
@ -389,6 +308,22 @@
return phoneNumber;
}
- (NSString *)getPassword {
NSString *pass;
LinphoneProxyConfig *cfg = linphone_core_get_default_proxy_config(LC);
if (cfg && strcmp("sip.linphone.org", linphone_proxy_config_get_domain(cfg)) == 0) {
const LinphoneAuthInfo *info = linphone_proxy_config_find_auth_info(cfg);
const char *tmpPass;
if (linphone_auth_info_get_passwd(info)) {
tmpPass = linphone_auth_info_get_passwd(info);
} else {
tmpPass = linphone_auth_info_get_ha1(info);
}
pass = [NSString stringWithFormat:@"%s", tmpPass];
}
return pass;
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
@ -451,110 +386,17 @@
_status = status;
LOGI(@"Triggering notification for status %@", status);
[NSNotificationCenter.defaultCenter postNotificationName:status object:self userInfo:dict];
if ([status isEqual:kIAPPurchaseFailed] || [status isEqual:kIAPPurchaseCancelled]) {
// Hide waiting view
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIView *topView = window.rootViewController.view;
UIView *waitview = (UIView *)[topView viewWithTag:288];
[waitview setHidden:TRUE];
}
}
/*
#pragma mark expiration notif
- (void)XMLRPCRequest:(LinphoneXmlRpcRequest *)request didReceiveResponse:(XMLRPCResponse *)response {
if (!_enabled)
return;
_available = true;
if ([[request method] isEqualToString:@"activate_account"]) {
_accountActivationInProgress = NO;
}
LOGI(@"XMLRPC response %@: %@", [request method], [response body]);
NSString *productID = [LinphoneManager.instance lpConfigStringForKey:@"paid_account_id"
inSection:@"in_app_purchase"];
// validation succeeded
if (![response isFault] && [response object] != nil) {
if (([[request method] isEqualToString:@"get_account_expiration"]) || ([[request method]
isEqualToString:@"create_account_from_in_app_purchase"])) {
[_productsIDPurchased removeObject:productID];
// response object can either be expiration date (long long number or an error string)
double timeinterval = [[response object] doubleValue];
if (timeinterval != 0.0f) {
self.expirationDate = [NSDate dateWithTimeIntervalSince1970:timeinterval / 1000];
NSDate *now = [[NSDate alloc] init];
NSDictionary *dict = @{ @"product_id" : productID, @"expires_date" : self.expirationDate };
if ([self.expirationDate earlierDate:now] == self.expirationDate) {
LOGW(@"Account has expired");
[self postNotificationforStatus:kIAPPurchaseExpired withDict:dict];
} else {
LOGI(@"Account valid until %@", self.expirationDate);
[_productsIDPurchased addObject:productID];
[self postNotificationforStatus:kIAPPurchaseSucceeded withDict:dict];
}
} else {
self.expirationDate = nil;
NSString *error = [response object];
LOGE(@"Failed with error %@", error);
NSString *errorMsg;
if ([error isEqualToString:@"ERROR_ACCOUNT_ALREADY_EXISTS"]) {
errorMsg = NSLocalizedString(@"This account is already registered.", nil);
} else if ([error isEqualToString:@"ERROR_UID_ALREADY_IN_USE"]) {
errorMsg = NSLocalizedString(@"You already own an account.", nil);
} else if ([error isEqualToString:@"ERROR_ACCOUNT_DOESNT_EXIST"]) {
errorMsg = NSLocalizedString(@"You have already purchased an account but it does not exist
anymore.", nil);
} else if ([error isEqualToString:@"ERROR_PURCHASE_CANCELLED"]) {
errorMsg = NSLocalizedString(@"You cancelled your account.", nil);
} else {
errorMsg = [NSString stringWithFormat:NSLocalizedString(@"Unknown error (%@).", nil), error];
}
NSDictionary *dict = @{ @"product_id" : productID, @"error_msg" : errorMsg };
[self postNotificationforStatus:kIAPPurchaseFailed withDict:dict];
}
}
} else {
NSString *errorString = NSLocalizedString(@"Unknown error", nil);
if ([response isFault]) {
errorString = [NSString stringWithFormat:NSLocalizedString(@"Communication issue (%@)", nil), [response
faultString]];
} else if ([response object] == nil) {
errorString = NSLocalizedString(@"Invalid server response", nil);
}
LOGE(@"Communication issue (%@)", [response faultString]);
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Communication issue", nil)
message:errorString
delegate:nil
cancelButtonTitle:NSLocalizedString(@"Continue", nil)
otherButtonTitles:nil, nil];
[errorView show];
latestReceiptMD5 = nil;
NSDictionary *dict = @{ @"error_msg" : errorString };
[self postNotificationforStatus:kIAPPurchaseFailed withDict:dict];
}
}
- (void)XMLRPCRequest:(LinphoneXmlRpcRequest *)request didFailWithError:(NSError *)error {
if (!_enabled)
return;
_available = true;
if ([[request method] isEqualToString:@"activate_account"]) {
_accountActivationInProgress = NO;
}
LOGE(@"Communication issue (%@)", [error localizedDescription]);
NSString *errorString =
[NSString stringWithFormat:NSLocalizedString(@"Communication issue (%@)", nil), [error localizedDescription]];
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Communication issue", nil)
message:errorString
delegate:nil
cancelButtonTitle:NSLocalizedString(@"Continue", nil)
otherButtonTitles:nil, nil];
[errorView show];
latestReceiptMD5 = nil;
NSDictionary *dict = @{ @"error_msg" : errorString };
[self postNotificationforStatus:kIAPReceiptFailed withDict:dict];
}
*/
- (void) presentNotification:(int64_t) remaining{
if (notificationCategory == nil) return;
int days = (int)remaining / (3600 * 24);
@ -613,41 +455,13 @@
}
}
#pragma mark Other
#else
- (void)postNotificationforStatus:(IAPPurchaseNotificationStatus)status {
_status = status;
[NSNotificationCenter.defaultCenter postNotificationName:status object:self userInfo:nil];
LOGE(@"Not supported, triggering %@", status);
}
- (BOOL)purchaseAccount:(NSString *)phoneNumber
withPassword:(NSString *)password
andEmail:(NSString *)email
monthly:(BOOL)monthly {
[self postNotificationforStatus:kIAPPurchaseFailed];
return false;
}
- (BOOL)restore {
[self postNotificationforStatus:kIAPRestoreFailed];
return false;
}
- (BOOL)retrievePurchases {
[self postNotificationforStatus:kIAPRestoreFailed];
return false;
}
- (BOOL)purchaseWithID:(NSString *)productID {
[self postNotificationforStatus:kIAPPurchaseFailed];
return FALSE;
}
- (BOOL)isPurchasedWithID:(NSString *)productId {
return FALSE;
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
}
- (BOOL)activateAccount:(NSString *)phoneNumber {
return FALSE;
}
#endif
@end

View file

@ -51,8 +51,8 @@
cell.descriptionLabel.minimumScaleFactor = .5;
cell.descriptionLabel.adjustsFontSizeToFitWidth = cell.detailTextLabel.adjustsLetterSpacingToFitWidth = YES;
cell.descriptionLabel.text = [NSString stringWithFormat:@"%@", product.localizedDescription];
LOGE(@"ShopTableView : name = %@ - descr = %@",
[NSString stringWithFormat:@"%@ (%@)", product.localizedTitle, price], product.localizedDescription);
// LOGE(@"ShopTableView : name = %@ - descr = %@",
// [NSString stringWithFormat:@"%@ (%@)", product.localizedTitle, price], product.localizedDescription);
[cell.linphoneImage setImage:[UIImage imageNamed:@"linphone_logo"]];
return cell;

View file

@ -27,6 +27,5 @@
@property(strong, nonatomic) IBOutlet ShopTableView *tableViewController;
@property(weak, nonatomic) IBOutlet UIView *waitingView;
- (IBAction)onDialerBackClick:(id)sender;
- (void)hideWaitingView;
@end

View file

@ -49,19 +49,15 @@ static UICompositeViewDescription *compositeDescription = nil;
[super viewWillAppear:animated];
[_tableViewController.tableView reloadData];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[_waitingView setHidden:TRUE];
}
#pragma mark - Action Functions
- (IBAction)onDialerBackClick:(id)sender {
[PhoneMainView.instance popToView:DialerView.compositeViewDescription];
}
- (IBAction)hideWaitingView {
LOGE(@"====>>> ShopView hideWaitingView - bool = %d");
if (_waitingView.isHidden) {
[_waitingView setHidden:NO];
} else {
[_waitingView setHidden:YES];
}
}
@end

View file

@ -13,12 +13,35 @@ start_at_boot_preference=1
stun_preference=stun.linphone.org
voiceproc_preference=1
[in_app_purchase]
#set to 1 if in-app purchases are to be shown in the application
enabled=0
#specify here the id of the sip account in-app purchase service to be shown in the shop section of the app
paid_account_id=sipAccount_12m
#the url of the inapp/account management server, for submitting the receipt and validating the account.
receipt_validation_url=https://subscribe.linphone.org:444/inapp.php
#for future use, to specify the full list of paying services to show in the shop view
products_list=sipAccount_12m,sipAccount_6m
#minimum period of time between notifications to the user about his expiring/expired account, in seconds.
expiry_check_period=30
#period of time before account expiry, to start notifying the user about expiration arriving, in seconds.
warn_before_expiry_period=160
#for test only, simulate an account expiry after the specified number of seconds
expiry_time_test=180
[default_values]
reg_expires=1314000
[misc]
file_transfer_server_url=https://www.linphone.org:444/lft.php
max_calls=3
real_early_media=1
[net]
download_bw=380

View file

@ -21,24 +21,6 @@ publish_presence=0
password_length=-1
username_length=-1
#specify here the id of the sip account in-app purchase service to be shown in the shop section of the app
paid_account_id=sipAccount_12m
#the url of the inapp/account management server, for submitting the receipt and validating the account.
receipt_validation_url=https://subscribe.linphone.org:444/inapp.php
#for future use, to specify the full list of paying services to show in the shop view
products_list=sipAccount_12m,sipAccount_6m
#minimum period of time between notifications to the user about his expiring/expired account, in seconds.
expiry_check_period=30
#period of time before account expiry, to start notifying the user about expiration arriving, in seconds.
warn_before_expiry_period=160
#for test only, simulate an account expiry after the specified number of seconds
expiry_time_test=180
[sip]
sip_random_port=0
#whether SIP passwords must be encrypted in configuration storage file

View file

@ -34,6 +34,8 @@
22D1B68112A3E0BE001AE361 /* libresolv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 22D1B68012A3E0BE001AE361 /* libresolv.dylib */; };
22E0A822111C44E100B04932 /* AboutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22E0A81C111C44E100B04932 /* AboutView.m */; };
22F2508E107141E100AC9B3F /* DialerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F2508C107141E100AC9B3F /* DialerView.m */; };
24A3459E1D95797700881A5C /* UIShopTableCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 24A3459D1D95797700881A5C /* UIShopTableCell.xib */; };
24A345A61D95798A00881A5C /* UIShopTableCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A345A51D95798A00881A5C /* UIShopTableCell.m */; };
288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; };
340751971506459A00B89C47 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 340751961506459A00B89C47 /* CoreTelephony.framework */; };
340751E7150F38FD00B89C47 /* UIVideoButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 340751E6150F38FD00B89C47 /* UIVideoButton.m */; };
@ -919,6 +921,9 @@
22E0A81D111C44E100B04932 /* AboutView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutView.h; sourceTree = "<group>"; };
22F2508B107141E100AC9B3F /* DialerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DialerView.h; sourceTree = "<group>"; };
22F2508C107141E100AC9B3F /* DialerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = DialerView.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
24A3459D1D95797700881A5C /* UIShopTableCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UIShopTableCell.xib; sourceTree = "<group>"; };
24A345A51D95798A00881A5C /* UIShopTableCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIShopTableCell.m; sourceTree = "<group>"; };
24A345A71D95799900881A5C /* UIShopTableCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIShopTableCell.h; sourceTree = "<group>"; };
288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
32CA4F630368D1EE00C91783 /* linphone_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = linphone_Prefix.pch; sourceTree = "<group>"; };
@ -2127,6 +2132,9 @@
D3A55FBA15877E5E003FD403 /* UIContactCell.h */,
D3A55FBB15877E5E003FD403 /* UIContactCell.m */,
F088488D19FF8C41007FFCF3 /* UIContactCell.xib */,
24A345A71D95799900881A5C /* UIShopTableCell.h */,
24A345A51D95798A00881A5C /* UIShopTableCell.m */,
24A3459D1D95797700881A5C /* UIShopTableCell.xib */,
D3C6526515AC1A8F0092A874 /* UIContactDetailsCell.h */,
D3C6526615AC1A8F0092A874 /* UIContactDetailsCell.m */,
639E9CAE1C0DB80300019A75 /* UIContactDetailsCell.xib */,
@ -3163,6 +3171,10 @@
ja,
nl,
zh_TW,
pl,
sv,
tr,
zh_CN,
);
mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
projectDirPath = "";
@ -3572,7 +3584,7 @@
633FEE741D3CD5590014B822 /* history_all_disabled.png in Resources */,
633FEE081D3CD5590014B822 /* chat_add_disabled.png in Resources */,
633FEF1D1D3CD55A0014B822 /* presence_offline@2x.png in Resources */,
8C5D1B9D1D9BC48100DC6539 /* UIShopTableCell.xib in Resources */,
24A3459E1D95797700881A5C /* UIShopTableCell.xib in Resources */,
633FEE231D3CD5590014B822 /* chat_start_body_over@2x.png in Resources */,
633FEEBE1D3CD55A0014B822 /* numpad_4_over@2x.png in Resources */,
633FEF471D3CD55A0014B822 /* speaker_disabled@2x.png in Resources */,
@ -3855,7 +3867,7 @@
D3ED3E871586291E006C0DE4 /* TabBarView.m in Sources */,
D3ED3EA71587334E006C0DE4 /* HistoryListTableView.m in Sources */,
D3ED3EB81587392C006C0DE4 /* HistoryListView.m in Sources */,
8C5D1B9C1D9BC48100DC6539 /* UIShopTableCell.m in Sources */,
24A345A61D95798A00881A5C /* UIShopTableCell.m in Sources */,
D35497FE15875372000081D8 /* ContactsListView.m in Sources */,
635173F91BA082A40095EB0A /* UIChatBubblePhotoCell.m in Sources */,
D3549816158761D0000081D8 /* ContactsListTableView.m in Sources */,