From fff920920656555fbd40137a2a9397edb46f96f1 Mon Sep 17 00:00:00 2001 From: Luca Garbolino <387293+lgarbo@users.noreply.github.com> Date: Tue, 6 Jun 2023 10:18:22 +0200 Subject: [PATCH] Release v1.4.0 --- Glassfy.podspec | 2 +- Glassfy.xcodeproj/project.pbxproj | 90 +++-- README.md | 2 +- Source/GYAPIManager.m | 2 +- Source/GYAPIPaywallResponse.h | 12 +- Source/GYAPIPaywallResponse.m | 41 ++- Source/GYInternalType.h | 22 ++ Source/GYManager.h | 9 +- Source/GYManager.m | 92 +++-- Source/GYPaywall+Private.h | 29 ++ Source/GYPaywall.m | 96 +++++ Source/GYPaywallHtmlJsonProvider.h | 17 + Source/GYPaywallHtmlJsonProvider.m | 274 +++++++++++++++ Source/GYPaywallJsonProvider.h | 15 + Source/GYPaywallJsonProvider.m | 44 +++ Source/GYPaywallNoCodeJsonProvider.h | 17 + Source/GYPaywallNoCodeJsonProvider.m | 267 ++++++++++++++ Source/GYPaywallViewController+Private.h | 6 +- Source/GYPaywallViewController.m | 428 ++++++----------------- Source/GYSysInfo.h | 6 + Source/GYSysInfo.m | 11 - Source/Glassfy.m | 34 +- Source/Public/GYPaywall.h | 28 ++ Source/Public/GYTypes.h | 14 +- Source/Public/Glassfy.h | 90 +++-- 25 files changed, 1209 insertions(+), 439 deletions(-) create mode 100644 Source/GYInternalType.h create mode 100644 Source/GYPaywall+Private.h create mode 100644 Source/GYPaywall.m create mode 100644 Source/GYPaywallHtmlJsonProvider.h create mode 100644 Source/GYPaywallHtmlJsonProvider.m create mode 100644 Source/GYPaywallJsonProvider.h create mode 100644 Source/GYPaywallJsonProvider.m create mode 100644 Source/GYPaywallNoCodeJsonProvider.h create mode 100644 Source/GYPaywallNoCodeJsonProvider.m create mode 100644 Source/Public/GYPaywall.h diff --git a/Glassfy.podspec b/Glassfy.podspec index d1baa3d..57e7040 100644 --- a/Glassfy.podspec +++ b/Glassfy.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Glassfy" - s.version = "1.3.8" + s.version = "1.4.0" s.summary = "Subscription and in-app-purchase service." s.license = { :type => 'MIT', :file => 'LICENSE' } s.source = { :git => "https://github.com/glassfy/ios-sdk.git", :tag => s.version.to_s } diff --git a/Glassfy.xcodeproj/project.pbxproj b/Glassfy.xcodeproj/project.pbxproj index 2afb19f..42934b2 100644 --- a/Glassfy.xcodeproj/project.pbxproj +++ b/Glassfy.xcodeproj/project.pbxproj @@ -7,6 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 6478E62C2A0B6D8E006FE38E /* GYPaywall+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 6478E62A2A0B6D8E006FE38E /* GYPaywall+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6478E62D2A0B6D8E006FE38E /* GYPaywall.m in Sources */ = {isa = PBXBuildFile; fileRef = 6478E62B2A0B6D8E006FE38E /* GYPaywall.m */; }; + 6478E62F2A0B6DDD006FE38E /* GYPaywall.h in Headers */ = {isa = PBXBuildFile; fileRef = 6478E62E2A0B6DDD006FE38E /* GYPaywall.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6478E63A2A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 6478E6342A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6478E63B2A0B80AB006FE38E /* GYPaywallJsonProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 6478E6352A0B80AB006FE38E /* GYPaywallJsonProvider.m */; }; + 6478E63C2A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 6478E6362A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.m */; }; + 6478E63D2A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 6478E6372A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6478E63E2A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 6478E6382A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.m */; }; + 6478E63F2A0B80AB006FE38E /* GYPaywallJsonProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 6478E6392A0B80AB006FE38E /* GYPaywallJsonProvider.h */; settings = {ATTRIBUTES = (Private, ); }; }; 81010F292590C1AB00B07DE4 /* GYAPIOfferingsResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 81010F252590C1AB00B07DE4 /* GYAPIOfferingsResponse.h */; settings = {ATTRIBUTES = (Private, ); }; }; 81010F2A2590C1AB00B07DE4 /* GYAPIOfferingsResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 81010F262590C1AB00B07DE4 /* GYAPIOfferingsResponse.m */; }; 8104537725909F320015A728 /* GYAPIInitResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 8104537525909F320015A728 /* GYAPIInitResponse.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -32,7 +41,7 @@ 812C842229B5F51F00A3F736 /* GYPurchasesHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = 812C842029B5F51F00A3F736 /* GYPurchasesHistory.m */; }; 812C842429B5F55200A3F736 /* GYPurchasesHistory.h in Headers */ = {isa = PBXBuildFile; fileRef = 812C842329B5F55200A3F736 /* GYPurchasesHistory.h */; settings = {ATTRIBUTES = (Public, ); }; }; 812C842629B5F58800A3F736 /* GYPurchasesHistory+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 812C842529B5F58800A3F736 /* GYPurchasesHistory+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 812C842929B5FAD400A3F736 /* GYAPIPurchaseHistoryResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 812C842729B5FAD400A3F736 /* GYAPIPurchaseHistoryResponse.h */; }; + 812C842929B5FAD400A3F736 /* GYAPIPurchaseHistoryResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 812C842729B5FAD400A3F736 /* GYAPIPurchaseHistoryResponse.h */; settings = {ATTRIBUTES = (Private, ); }; }; 812C842A29B5FAD400A3F736 /* GYAPIPurchaseHistoryResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 812C842829B5FAD400A3F736 /* GYAPIPurchaseHistoryResponse.m */; }; 812C842D29B5FD9200A3F736 /* GYPurchaseHistory+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 812C842B29B5FD9200A3F736 /* GYPurchaseHistory+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 812C842E29B5FD9200A3F736 /* GYPurchaseHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = 812C842C29B5FD9200A3F736 /* GYPurchaseHistory.m */; }; @@ -130,6 +139,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 6478E62A2A0B6D8E006FE38E /* GYPaywall+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GYPaywall+Private.h"; sourceTree = ""; }; + 6478E62B2A0B6D8E006FE38E /* GYPaywall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GYPaywall.m; sourceTree = ""; }; + 6478E62E2A0B6DDD006FE38E /* GYPaywall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GYPaywall.h; path = Public/GYPaywall.h; sourceTree = ""; }; + 6478E6342A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GYPaywallHtmlJsonProvider.h; sourceTree = ""; }; + 6478E6352A0B80AB006FE38E /* GYPaywallJsonProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GYPaywallJsonProvider.m; sourceTree = ""; }; + 6478E6362A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GYPaywallNoCodeJsonProvider.m; sourceTree = ""; }; + 6478E6372A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GYPaywallNoCodeJsonProvider.h; sourceTree = ""; }; + 6478E6382A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GYPaywallHtmlJsonProvider.m; sourceTree = ""; }; + 6478E6392A0B80AB006FE38E /* GYPaywallJsonProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GYPaywallJsonProvider.h; sourceTree = ""; }; 81010F252590C1AB00B07DE4 /* GYAPIOfferingsResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GYAPIOfferingsResponse.h; sourceTree = ""; }; 81010F262590C1AB00B07DE4 /* GYAPIOfferingsResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GYAPIOfferingsResponse.m; sourceTree = ""; }; 8104537525909F320015A728 /* GYAPIInitResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GYAPIInitResponse.h; sourceTree = ""; }; @@ -334,50 +352,52 @@ 816830A1258B6D9C00565968 /* Source */ = { isa = PBXGroup; children = ( + 816830B3258B6EA100565968 /* Core */, + 8104537B25909F380015A728 /* Data */, 81951CD026443F65003901F1 /* Glassfy.h */, 817A1C75258CE0A200D1BA10 /* Glassfy.m */, + 8166504128DB6B2700E5B985 /* GYAccountableSku.h */, + 8166503C28DB671200E5B985 /* GYAccountableSku.m */, 81E5B8952912C88400D134E9 /* GYAttributionItem.h */, 81E5B8932912C87600D134E9 /* GYAttributionItem.m */, - 81951CD526443F94003901F1 /* GYOfferings.h */, - 810F6FB225E50CFF00D3009E /* GYOfferings.m */, + 812767FD29CCA7F800498889 /* GYInitializeOptions.h */, + 812767FA29CC990200498889 /* GYInitializeOptions.m */, 81951CD426443F94003901F1 /* GYOffering.h */, 817A1C3C258CC9EA00D1BA10 /* GYOffering.m */, - 811DB39E280975C900021880 /* GYSkuBase.h */, - 811DB39B280972C900021880 /* GYSkuBase.m */, - 81951CD926443FAA003901F1 /* GYSku.h */, - 817A1C48258CCA7000D1BA10 /* GYSku.m */, - 8166504128DB6B2700E5B985 /* GYAccountableSku.h */, - 8166503C28DB671200E5B985 /* GYAccountableSku.m */, - 81D18D7A2812AF6200C68558 /* GYSkuPaddle.h */, - 81D18D782812AF5700C68558 /* GYSkuPaddle.m */, - 81951CD826443FAA003901F1 /* GYTransaction.h */, - 8161C4972594DD2F009CA563 /* GYTransaction.m */, + 81951CD526443F94003901F1 /* GYOfferings.h */, + 810F6FB225E50CFF00D3009E /* GYOfferings.m */, + 6478E62E2A0B6DDD006FE38E /* GYPaywall.h */, + 6478E62B2A0B6D8E006FE38E /* GYPaywall.m */, + 818880BB27171DA900128A10 /* GYPaywallViewController.h */, + 818880B827171D7A00128A10 /* GYPaywallViewController.m */, 81951CDD26443FB8003901F1 /* GYPermission.h */, 81EFB54925A772F200706FCB /* GYPermission.m */, 81951CDC26443FB8003901F1 /* GYPermissions.h */, 81C2816125E3F58C0048189D /* GYPermissions.m */, - 8191E86A26690C850077BDD7 /* GYUserProperties.h */, - 816EF2882661161200513FB1 /* GYUserProperties.m */, - 818880BB27171DA900128A10 /* GYPaywallViewController.h */, - 818880B827171D7A00128A10 /* GYPaywallViewController.m */, + 81FCCE8527C3CE0A00DF3D1A /* GYPurchaseDelegate.h */, 812C842F29B5FDA600A3F736 /* GYPurchaseHistory.h */, 812C842C29B5FD9200A3F736 /* GYPurchaseHistory.m */, 812C842329B5F55200A3F736 /* GYPurchasesHistory.h */, 812C842029B5F51F00A3F736 /* GYPurchasesHistory.m */, - 812767FD29CCA7F800498889 /* GYInitializeOptions.h */, - 812767FA29CC990200498889 /* GYInitializeOptions.m */, - 81C9AF5D2805BB82004A36A1 /* GYStoresInfo.h */, - 81C9AF5A2805BB5B004A36A1 /* GYStoresInfo.m */, + 81951CD926443FAA003901F1 /* GYSku.h */, + 817A1C48258CCA7000D1BA10 /* GYSku.m */, + 811DB39E280975C900021880 /* GYSkuBase.h */, + 811DB39B280972C900021880 /* GYSkuBase.m */, + 81D18D7A2812AF6200C68558 /* GYSkuPaddle.h */, + 81D18D782812AF5700C68558 /* GYSkuPaddle.m */, 81C9AF5E2805BB82004A36A1 /* GYStoreInfo.h */, 81C9AF562805BAB1004A36A1 /* GYStoreInfo.m */, 81C9AF652805C386004A36A1 /* GYStoreInfoPaddle.h */, 81C9AF622805C36B004A36A1 /* GYStoreInfoPaddle.m */, + 81C9AF5D2805BB82004A36A1 /* GYStoresInfo.h */, + 81C9AF5A2805BB5B004A36A1 /* GYStoresInfo.m */, + 81951CD826443FAA003901F1 /* GYTransaction.h */, + 8161C4972594DD2F009CA563 /* GYTransaction.m */, 81951CD226443F82003901F1 /* GYTypes.h */, - 81FCCE8527C3CE0A00DF3D1A /* GYPurchaseDelegate.h */, - 816830B3258B6EA100565968 /* Core */, - 8104537B25909F380015A728 /* Data */, - 817A1CC7258CE9C700D1BA10 /* Utils */, + 8191E86A26690C850077BDD7 /* GYUserProperties.h */, + 816EF2882661161200513FB1 /* GYUserProperties.m */, 816830C0258B6F2800565968 /* Supporting Files */, + 817A1CC7258CE9C700D1BA10 /* Utils */, ); path = Source; sourceTree = ""; @@ -391,6 +411,12 @@ 817A1CF4258CF63600D1BA10 /* GYAPIManager.m */, 817A1CE5258CF46600D1BA10 /* GYCacheManager.h */, 817A1CE6258CF46600D1BA10 /* GYCacheManager.m */, + 6478E6392A0B80AB006FE38E /* GYPaywallJsonProvider.h */, + 6478E6352A0B80AB006FE38E /* GYPaywallJsonProvider.m */, + 6478E6342A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.h */, + 6478E6382A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.m */, + 6478E6372A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.h */, + 6478E6362A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.m */, 817A1CFD258CF7EE00D1BA10 /* GYStoreRequest.h */, 817A1CFE258CF7EE00D1BA10 /* GYStoreRequest.m */, 811F322D2631CC6C00777020 /* Glassfy+Private.h */, @@ -398,6 +424,7 @@ 81E5B8972912CE9700D134E9 /* GYAttributionItem+Private.h */, 81359D7025B1DBFC009B1A4B /* GYOffering+Private.h */, 810F6FB725E50D2000D3009E /* GYOfferings+Private.h */, + 6478E62A2A0B6D8E006FE38E /* GYPaywall+Private.h */, 818880BD27171E1F00128A10 /* GYPaywallViewController+Private.h */, 81EFB55025A7736B00706FCB /* GYPermission+Private.h */, 81C2816825E3F6AC0048189D /* GYPermissions+Private.h */, @@ -475,6 +502,7 @@ 817A1CE7258CF46600D1BA10 /* GYCacheManager.h in Headers */, 81E1DC3225933FC7003B5EBD /* SKPayment+GYEncode.h in Headers */, 818880BC27171DA900128A10 /* GYPaywallViewController.h in Headers */, + 6478E63D2A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.h in Headers */, 8138BA9B25924C8F005CB44E /* SKProductDiscount+GYEncode.h in Headers */, 81C684652602633500223D3E /* GYAPISignatureResponse.h in Headers */, 812C842629B5F58800A3F736 /* GYPurchasesHistory+Private.h in Headers */, @@ -488,6 +516,7 @@ 816EF29026611CF700513FB1 /* GYUserProperties+Private.h in Headers */, 81C9AF602805BB82004A36A1 /* GYStoreInfo.h in Headers */, 811F31B226317EC800777020 /* GYOffering+Private.h in Headers */, + 6478E63F2A0B80AB006FE38E /* GYPaywallJsonProvider.h in Headers */, 81951CD326443F82003901F1 /* GYTypes.h in Headers */, 81951CD126443F65003901F1 /* Glassfy.h in Headers */, 816EF28D26611C5900513FB1 /* GYAPIPropertiesResponse.h in Headers */, @@ -514,8 +543,11 @@ 812C843029B5FDA600A3F736 /* GYPurchaseHistory.h in Headers */, 81951CDA26443FAA003901F1 /* GYTransaction.h in Headers */, 81951CDE26443FB8003901F1 /* GYPermissions.h in Headers */, + 6478E63A2A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.h in Headers */, + 6478E62C2A0B6D8E006FE38E /* GYPaywall+Private.h in Headers */, 812C842D29B5FD9200A3F736 /* GYPurchaseHistory+Private.h in Headers */, 8138BAA225924CB4005CB44E /* SKPaymentTransaction+GYEncode.h in Headers */, + 6478E62F2A0B6DDD006FE38E /* GYPaywall.h in Headers */, 8138BA5F25923E78005CB44E /* GYCodableProtocol.h in Headers */, 818880BE27171E1F00128A10 /* GYPaywallViewController+Private.h in Headers */, 81C9AF662805C386004A36A1 /* GYStoreInfoPaddle.h in Headers */, @@ -621,6 +653,7 @@ buildActionMask = 2147483647; files = ( 8104538D2590A15E0015A728 /* GYAPIBaseResponse.m in Sources */, + 6478E62D2A0B6D8E006FE38E /* GYPaywall.m in Sources */, 8104537825909F320015A728 /* GYAPIInitResponse.m in Sources */, 81C2816325E3F58C0048189D /* GYPermissions.m in Sources */, 812C842229B5F51F00A3F736 /* GYPurchasesHistory.m in Sources */, @@ -635,6 +668,7 @@ 81D18D792812AF5700C68558 /* GYSkuPaddle.m in Sources */, 8161C4992594DD2F009CA563 /* GYTransaction.m in Sources */, 8138BA8A25924B42005CB44E /* SKProduct+GYEncode.m in Sources */, + 6478E63C2A0B80AB006FE38E /* GYPaywallNoCodeJsonProvider.m in Sources */, 817A1C4A258CCA7000D1BA10 /* GYSku.m in Sources */, 812C842E29B5FD9200A3F736 /* GYPurchaseHistory.m in Sources */, 811DB39D280972C900021880 /* GYSkuBase.m in Sources */, @@ -649,6 +683,7 @@ 817A1CE8258CF46600D1BA10 /* GYCacheManager.m in Sources */, 810F6FB425E50CFF00D3009E /* GYOfferings.m in Sources */, 817A1D00258CF7EE00D1BA10 /* GYStoreRequest.m in Sources */, + 6478E63B2A0B80AB006FE38E /* GYPaywallJsonProvider.m in Sources */, 8166503E28DB671200E5B985 /* GYAccountableSku.m in Sources */, 818880BA27171D7A00128A10 /* GYPaywallViewController.m in Sources */, 818880B62717153A00128A10 /* GYAPIPaywallResponse.m in Sources */, @@ -664,6 +699,7 @@ 816EF28E26611C5900513FB1 /* GYAPIPropertiesResponse.m in Sources */, 81EFB54B25A772F200706FCB /* GYPermission.m in Sources */, 81010F2A2590C1AB00B07DE4 /* GYAPIOfferingsResponse.m in Sources */, + 6478E63E2A0B80AB006FE38E /* GYPaywallHtmlJsonProvider.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -829,7 +865,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.3.8; + MARKETING_VERSION = 1.4.0; PRODUCT_BUNDLE_IDENTIFIER = net.glassfy.sdk; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -857,7 +893,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.3.8; + MARKETING_VERSION = 1.4.0; PRODUCT_BUNDLE_IDENTIFIER = net.glassfy.sdk; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/README.md b/README.md index 7546d4d..f537177 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Our SDK can be easly integrated through Cocoapods or Swift Package Manager #### Integrate using Cocoapods Add the pod to your Podfile: -```pod 'Glassfy', '~> 1.3.8'``` +```pod 'Glassfy', '~> 1.4.0'``` Then, run the following command: diff --git a/Source/GYAPIManager.m b/Source/GYAPIManager.m index 3717f00..8b8b81b 100644 --- a/Source/GYAPIManager.m +++ b/Source/GYAPIManager.m @@ -451,7 +451,7 @@ - (void)getPropertiesWithCompletion:(GYGetPropertiesCompletion _Nullable)block - (void)getPaywall:(NSString *)paywallId locale:(NSString *)locale completion:(GYGetPaywallCompletion _Nullable)block { - NSURLComponents *url = [self baseURLV0]; + NSURLComponents *url = [self baseURLV1]; url.path = [url.path stringByAppendingPathComponent:@"paywall"]; NSMutableArray *queryItems = [(url.queryItems ?: @[]) mutableCopy]; diff --git a/Source/GYAPIPaywallResponse.h b/Source/GYAPIPaywallResponse.h index e87d1f3..20aad37 100644 --- a/Source/GYAPIPaywallResponse.h +++ b/Source/GYAPIPaywallResponse.h @@ -2,20 +2,28 @@ // GYAPIPaywallResponse.h // Glassfy // -// Created by Luca Garbolino on 13/10/21. +// Created by Luca Garbolino on 27/06/22. // #import #import "GYAPIBaseResponse.h" +#import "GYTypes.h" +#import "GYInternalType.h" @class GYSku; NS_ASSUME_NONNULL_BEGIN @interface GYAPIPaywallResponse: GYAPIBaseResponse -@property(nonatomic, strong) NSString *content; + +@property(nullable, nonatomic, strong) NSURL *contentUrl; +@property(nullable, nonatomic, strong) NSString *version; +@property(nonatomic, assign) GYPaywallType type; @property(nullable, nonatomic, strong) NSString *locale; @property(nullable, nonatomic, strong) NSString *pwid; @property(nonatomic, strong) NSArray *skus; + ++ (GYPaywallType)paywallTypeFromString:(NSString*)typeStr; + @end NS_ASSUME_NONNULL_END diff --git a/Source/GYAPIPaywallResponse.m b/Source/GYAPIPaywallResponse.m index dddeab5..e446d29 100644 --- a/Source/GYAPIPaywallResponse.m +++ b/Source/GYAPIPaywallResponse.m @@ -2,15 +2,29 @@ // GYAPIPaywallResponse.m // Glassfy // -// Created by Luca Garbolino on 13/10/21. +// Created by Luca Garbolino on 27/06/22. // #import "GYAPIPaywallResponse.h" -#import "GYAPIBaseResponse.h" -#import "GYError.h" #import "GYSku+Private.h" +#import "GYError.h" + +#define PAYWALL_TYPE_HTML @"html" +#define PAYWALL_TYPE_NOCODE @"nocode" @implementation GYAPIPaywallResponse + ++ (GYPaywallType)paywallTypeFromString:(NSString*)typeStr +{ + GYPaywallType type = GYPaywallTypeNoCode; + if ([typeStr isKindOfClass:NSString.class]) { + if ([PAYWALL_TYPE_HTML isEqualToString:typeStr]) { + type = GYPaywallTypeHTML; + } + } + return type; +} + - (instancetype _Nullable)initWithObject:(NSDictionary *)obj error:(NSError **)error { self = [super initWithObject:obj error:error]; @@ -21,9 +35,17 @@ - (instancetype _Nullable)initWithObject:(NSDictionary *)obj error:(NSError **)e if (self) { NSDictionary *paywall = obj[@"paywall"]; if ([paywall isKindOfClass:NSDictionary.class]) { - NSString *content = paywall[@"content"]; - if ([content isKindOfClass:NSString.class]) { - self.content = content; + + NSString *version = paywall[@"version"]; + if ([version isKindOfClass:NSString.class]) { + self.version = version; + } + + self.type = [GYAPIPaywallResponse paywallTypeFromString:paywall[@"type"]]; + + NSString *contentUrl = paywall[@"url"]; + if ([contentUrl isKindOfClass:NSString.class]) { + self.contentUrl = [NSURL URLWithString:contentUrl]; } NSString *locale = paywall[@"locale"]; @@ -56,10 +78,13 @@ - (instancetype _Nullable)initWithObject:(NSDictionary *)obj error:(NSError **)e // verify - if (!self.content) { - *error = [GYError serverError:GYErrorCodeUnknow description:@"Unexpected data format"]; + if (!self.contentUrl) { + if (error) { + *error = [GYError serverError:GYErrorCodeUnknow description:@"Unexpected data format"]; + } } } return self; } + @end diff --git a/Source/GYInternalType.h b/Source/GYInternalType.h new file mode 100644 index 0000000..09a6b91 --- /dev/null +++ b/Source/GYInternalType.h @@ -0,0 +1,22 @@ +// +// GYInternalType.h +// Glassfy +// +// Created by Luca Garbolino on 25/10/22. +// + +#ifndef GYInternalType_h +#define GYInternalType_h + +typedef NS_ENUM(NSUInteger, GYSubplatform) { + GYSubplatformUnknown = 0, + GYSubplatformiOS = 1, + GYSubplatformCatalyst = 2, + GYSubplatformTV = 3, + GYSubplatformWatch = 4, + GYSubplatformOSx = 5, + GYSubplatformDrive = 6, + GYSubplatformSimulator = 7 +}; + +#endif /* GYInternalType_h */ diff --git a/Source/GYManager.h b/Source/GYManager.h index 5712895..900a756 100644 --- a/Source/GYManager.h +++ b/Source/GYManager.h @@ -66,8 +66,13 @@ API_AVAILABLE(ios(12.2), macos(10.14.4), watchos(6.2)); - (void)connectCustomSubscriber:(NSString *_Nullable)customId completion:(GYErrorCompletion)block; -- (void)getPaywall:(NSString *)paywallId - completion:(GYPaywallCompletion)block +- (void)paywallWithRemoteConfigurationId:(NSString *)remoteConfigId + completion:(GYPaywallCompletion)block +API_UNAVAILABLE(macos, watchos); + +- (void)paywallViewControllerWithRemoteConfigurationId:(NSString *)remoteConfigId + awaitLoading:(BOOL)awaitLoading + completion:(GYPaywallViewControllerCompletion)block API_UNAVAILABLE(macos, watchos); - (void)restorePurchasesWithCompletion:(GYPermissionsCompletion)block; diff --git a/Source/GYManager.m b/Source/GYManager.m index 3abef74..6a4975f 100644 --- a/Source/GYManager.m +++ b/Source/GYManager.m @@ -26,6 +26,7 @@ #import "GYStoresInfo+Private.h" #import "Glassfy+Private.h" #import "GYPurchasesHistory+Private.h" +#import "GYPaywall+Private.h" #import "GYSysInfo.h" #import "GYInitializeOptions.h" @@ -350,32 +351,83 @@ - (void)getUserProperties:(GYUserPropertiesCompletion)block }]; } -- (void)getPaywall:(NSString *)paywallId completion:(GYPaywallCompletion)block +- (void)paywallWithRemoteConfigurationId:(NSString *)remoteConfigId completion:(GYPaywallCompletion)block { - NSString *lang = [[[NSBundle mainBundle] preferredLocalizations] firstObject]; - [self.api getPaywall:paywallId locale:lang completion:^(GYAPIPaywallResponse *res, NSError *paywallErr) { - [self.store productWithSkus:res.skus completion:^(NSArray *products, NSError *storeErr) { - res.skus = [GYSku matchSkus:res.skus withProducts:products ?: @[]]; - - typeof(block) __strong completion = block; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ #if TARGET_OS_IPHONE - - NSError *err = paywallErr ?: storeErr; - GYPaywallViewController *vc = nil; - if (!err) { - vc = [GYPaywallViewController paywallWithResponse:res]; - } - - err ? completion(nil, err) : completion(vc, nil); + NSString *lang = [[[NSBundle mainBundle] preferredLocalizations] firstObject]; + [self.api getPaywall:remoteConfigId locale:lang completion:^(GYAPIPaywallResponse *res, NSError *paywallErr) { + GYPaywall *paywall; + + // Create the dispatch group + dispatch_group_t serviceGroup = dispatch_group_create(); + + __block NSError *storeError = nil; + if (!paywallErr) { + paywall = [GYPaywall paywallWithResponse:res]; + + // Start sku request with appstore + dispatch_group_enter(serviceGroup); + [self.store productWithSkus:res.skus completion:^(NSArray *products, NSError *storeErr) { + storeError = storeErr; + if (!storeErr && (res.skus.count != products.count)) { + storeError = GYError.storeProductNotFound; + } + res.skus = [GYSku matchSkus:res.skus withProducts:products ?: @[]]; + dispatch_group_leave(serviceGroup); + }]; + } + + dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{ + NSError *overallError = paywallErr ?: storeError; + if (overallError) { + block(nil, overallError); + return; + } + block(paywall, nil); + }); + }]; #else - completion(nil, GYError.notSupported); + dispatch_async(dispatch_get_main_queue(), ^{ + block(nil, GYError.notSupported); + }); #endif +} + +- (void)paywallViewControllerWithRemoteConfigurationId:(NSString *)remoteConfigId + awaitLoading:(BOOL)awaitLoading + completion:(GYPaywallViewControllerCompletion)block +{ +#if TARGET_OS_IPHONE + [self paywallWithRemoteConfigurationId:remoteConfigId completion:^(GYPaywall * _Nullable somePaywall, NSError * _Nullable error) { + if (!somePaywall || error) { + block(nil, error); + return; + } + + __block GYPaywall *paywall = somePaywall; + if (awaitLoading) { + [paywall setContentAvailableHandler:^(GYPaywallViewController * _Nullable viewController, NSError * _Nullable error) { + if (error) { + block(nil, error); + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + block(viewController, nil); + paywall = nil; }); - } - }]; + }]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block([paywall viewController], nil); + paywall = nil; + }); + } }]; +#else + dispatch_async(dispatch_get_main_queue(), ^{ + block(nil, GYError.notSupported); + }); +#endif } - (void)connectPaddleLicenseKey:(NSString *)licenseKey force:(BOOL)force completion:(GYErrorCompletion)block diff --git a/Source/GYPaywall+Private.h b/Source/GYPaywall+Private.h new file mode 100644 index 0000000..7d6fab5 --- /dev/null +++ b/Source/GYPaywall+Private.h @@ -0,0 +1,29 @@ +// +// GYPaywall+Private.h +// Glassfy +// +// Created by Luca Garbolino on 27/06/22. +// + +#import "GYPaywall.h" +@class GYAPIPaywallResponse; + +NS_ASSUME_NONNULL_BEGIN + +@interface GYPaywall (Private) + +@property(nullable, nonatomic, strong) NSString *preloadedContent; +@property(nullable, nonatomic, strong) NSURL *contentUrl; +@property(nullable, nonatomic, strong) NSString *version; +@property(nullable, nonatomic, strong) NSString *pwid; +@property(nullable, nonatomic, strong) NSLocale *locale; +@property(nullable, nonatomic, strong) NSArray *skus; +@property(nonatomic, assign) GYPaywallType type; + ++ (instancetype)paywallWithResponse:(GYAPIPaywallResponse *)res; + +- (NSDictionary *)config; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/GYPaywall.m b/Source/GYPaywall.m new file mode 100644 index 0000000..e8f20bc --- /dev/null +++ b/Source/GYPaywall.m @@ -0,0 +1,96 @@ +// +// GYPaywall.m +// Glassfy +// +// Created by Luca Garbolino on 27/06/22. +// + +#import "GYPaywall+Private.h" +#import "Glassfy+Private.h" +#import "GYSku+Private.h" +#import "GYAPIPaywallResponse.h" +#import "GYPaywallJsonProvider.h" +#import "GYPaywallViewController+Private.h" + +@interface GYPaywall () +@property(nullable, nonatomic, strong) NSString *preloadedContent; +@property(nullable, nonatomic, strong) NSURL *contentUrl; +@property(nullable, nonatomic, strong) NSString *version; +@property(nullable, nonatomic, strong) NSString *pwid; +@property(nullable, nonatomic, strong) NSLocale *locale; +@property(nullable, nonatomic, strong) NSArray *skus; +@property(nonatomic, assign) GYPaywallType type; +@property(nonatomic, copy) GYPaywallViewControllerCompletion contentAvailableHandler; +@end + +@implementation GYPaywall (Private) + ++ (instancetype)paywallWithResponse:(GYAPIPaywallResponse *)res +{ + GYPaywall *p = [self new]; + p.contentUrl = res.contentUrl; + p.skus = res.skus; + p.pwid = res.pwid; + p.locale = [NSLocale localeWithLocaleIdentifier:res.locale ?: @"en-US"]; + p.type = res.type; + p.version = res.version; + [p startLoading]; + return p; +} + +- (NSDictionary *)config +{ + return [GYPaywallJsonProvider json:self]; +} + +- (void)startLoading { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSError *err = nil; + self.preloadedContent = [NSString stringWithContentsOfURL:self.contentUrl + encoding:NSUTF8StringEncoding + error:&err]; + if (self.contentAvailableHandler) { + typeof(self) __weak weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + GYPaywallViewControllerCompletion completion = weakSelf.contentAvailableHandler; + if (completion) { + if (err) { + completion(nil, err); + } else { + #if TARGET_OS_IPHONE + completion([self viewController], err); + #else + completion(nil, GYError.notSupported); + #endif + } + } + }); + } + }); +} + +@end + +@implementation GYPaywall + +#if TARGET_OS_IPHONE +- (void)setContentAvailableHandler:(GYPaywallViewControllerCompletion)handler +{ + self->_contentAvailableHandler = handler; + if (self.preloadedContent) { + typeof(self) __weak weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + GYPaywallViewControllerCompletion completion = weakSelf.contentAvailableHandler; + if (completion) { + completion([weakSelf viewController], nil); + } + }); + } +} + +- (GYPaywallViewController *)viewController { + return [GYPaywallViewController instanceWithPaywall:self]; +} +#endif + +@end diff --git a/Source/GYPaywallHtmlJsonProvider.h b/Source/GYPaywallHtmlJsonProvider.h new file mode 100644 index 0000000..bcfa116 --- /dev/null +++ b/Source/GYPaywallHtmlJsonProvider.h @@ -0,0 +1,17 @@ +// +// GYPaywallHtmlJsonProvider.h +// Glassfy +// +// Created by Federico Curzel on 10/05/23. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@interface GYPaywallHtmlJsonProvider: NSObject ++ (NSDictionary *)jsonWithPwid:(NSString *)pwid + locale:(NSLocale * _Nullable)locale + uiStyle:(NSString *)uiStyle + skus:(NSArray *)skus; +@end +NS_ASSUME_NONNULL_END diff --git a/Source/GYPaywallHtmlJsonProvider.m b/Source/GYPaywallHtmlJsonProvider.m new file mode 100644 index 0000000..dba57a1 --- /dev/null +++ b/Source/GYPaywallHtmlJsonProvider.m @@ -0,0 +1,274 @@ +// +// GYPaywallHtmlJsonProvider.h +// Glassfy +// +// Created by Federico Curzel on 10/05/23. +// + +#import +#import +#import "Glassfy+Private.h" +#import "GYFormatter.h" +#import "GYPaywallHtmlJsonProvider.h" +#import "GYSku+Private.h" +#import "GYSysInfo.h" +#import "SKProduct+GYEncode.h" + +@implementation GYPaywallHtmlJsonProvider ++ (NSDictionary *)jsonWithPwid:(NSString *)pwid + locale:(NSLocale * _Nullable)locale + uiStyle:(NSString *)uiStyle + skus:(NSArray *)skus +{ + NSMutableDictionary *skusDetails = [NSMutableDictionary new]; + NSMutableDictionary *commonMsg = [NSMutableDictionary new]; + if (@available(iOS 11.2, *)) { + commonMsg[@"DAY"] = [GYFormatter formatUnit:SKProductPeriodUnitDay locale:locale]; + commonMsg[@"WEEK"] = [GYFormatter formatUnit:SKProductPeriodUnitWeek locale:locale]; + commonMsg[@"MONTH"] = [GYFormatter formatUnit:SKProductPeriodUnitMonth locale:locale]; + commonMsg[@"YEAR"] = [GYFormatter formatUnit:SKProductPeriodUnitYear locale:locale]; + } + + NSMutableArray *priceCorrections = [NSMutableArray array]; + for (GYSku *s in skus) { + SKProduct *p = s.product; + + NSMutableDictionary *msg = [NSMutableDictionary new]; + msg[@"TITLE"] = p.localizedTitle; + msg[@"DESCRIPTION"] = p.localizedDescription; + msg[@"ORIGINAL_PRICE"] = [self formatPrice:p.price locale:p.priceLocale]; + + float priceCorrection = 1.0f; + if (@available(iOS 11.2, *)) { + float k = 1.0f; + switch (p.subscriptionPeriod.unit) { + case (SKProductPeriodUnitDay): + msg[@"ORIGINAL_PERIOD"] = commonMsg[@"DAY"]; + k = 1.0f; + + break; + case (SKProductPeriodUnitWeek): + msg[@"ORIGINAL_PERIOD"] = commonMsg[@"WEEK"]; + k = 1.0f / 7.0f; + + break; + case (SKProductPeriodUnitMonth): + msg[@"ORIGINAL_PERIOD"] = commonMsg[@"MONTH"]; + k = 12.0f / 365.0f; + + break; + case (SKProductPeriodUnitYear): + msg[@"ORIGINAL_PERIOD"] = commonMsg[@"YEAR"]; + k = 1.0f / 365.0f; + + break; + } + priceCorrection = k; + + float priceDaily = p.price.floatValue * k / p.subscriptionPeriod.numberOfUnits; + float priceWeekly = priceDaily * 7.0f; + float priceYearly = priceDaily * 365.0f; + float priceMonthly = priceYearly / 12.0f; + + msg[@"ORIGINAL_DURATION"] = [GYFormatter formatPeriod:p.subscriptionPeriod.numberOfUnits unit:p.subscriptionPeriod.unit locale:locale]; + + msg[@"ORIGINAL_DAILY"] = [self formatPrice:@(priceDaily) locale:p.priceLocale]; + msg[@"ORIGINAL_WEEKLY"] = [self formatPrice:@(priceWeekly) locale:p.priceLocale]; + msg[@"ORIGINAL_MONTHLY"] = [self formatPrice:@(priceMonthly) locale:p.priceLocale]; + msg[@"ORIGINAL_YEARLY"] = [self formatPrice:@(priceYearly) locale:p.priceLocale]; + + if (p.introductoryPrice) { + float k = 1.0f; + switch (p.introductoryPrice.subscriptionPeriod.unit) { + case (SKProductPeriodUnitDay): + msg[@"INTRO_PERIOD"] = commonMsg[@"DAY"]; + k = 1.0f; + + break; + case (SKProductPeriodUnitWeek): + msg[@"INTRO_PERIOD"] = commonMsg[@"WEEK"]; + k = 1.0f / 7.0f; + + break; + case (SKProductPeriodUnitMonth): + msg[@"INTRO_PERIOD"] = commonMsg[@"MONTH"]; + k = 12.0f / 365.0f; + + break; + case (SKProductPeriodUnitYear): + msg[@"INTRO_PERIOD"] = commonMsg[@"YEAR"]; + k = 1.0f / 365.0f; + + break; + } + + float introDaily = p.introductoryPrice.price.floatValue * k / p.introductoryPrice.subscriptionPeriod.numberOfUnits; + float introWeekly = introDaily * 7.0f; + float introYearly = introDaily * 365.0f; + float introMonthly = introYearly / 12.0f; + float introDiscount = introDaily / priceDaily; + + msg[@"INTRO_PRICE"] = [self formatPrice:p.introductoryPrice.price locale:p.introductoryPrice.priceLocale]; + msg[@"INTRO_DURATION"] = [GYFormatter formatPeriod:p.introductoryPrice.subscriptionPeriod.numberOfUnits + unit:p.introductoryPrice.subscriptionPeriod.unit + locale:locale]; + + msg[@"INTRO_DAILY"] = [self formatPrice:@(introDaily) locale:p.introductoryPrice.priceLocale]; + msg[@"INTRO_WEEKLY"] = [self formatPrice:@(introWeekly) locale:p.introductoryPrice.priceLocale]; + msg[@"INTRO_MONTHLY"] = [self formatPrice:@(introMonthly) locale:p.introductoryPrice.priceLocale]; + msg[@"INTRO_YEARLY"] = [self formatPrice:@(introYearly) locale:p.introductoryPrice.priceLocale]; + + msg[@"INTRO_DISCOUNT"] = [GYFormatter formatPercentage:@(introDiscount) locale:locale]; + } + + if (s.promotionalId) { + NSPredicate *p = [NSPredicate predicateWithFormat:@"identifier = %@", s.promotionalId]; + if (@available(iOS 12.2, *)) { + SKProductDiscount *promo = [[s.product.discounts filteredArrayUsingPredicate:p] firstObject]; + + if (promo) { + float k = 1.0f; + switch (promo.subscriptionPeriod.unit) { + case (SKProductPeriodUnitDay): + msg[@"PROMO_PERIOD"] = commonMsg[@"DAY"]; + k = 1.0f; + + break; + case (SKProductPeriodUnitWeek): + msg[@"PROMO_PERIOD"] = commonMsg[@"WEEK"]; + k = 1.0f / 7.0f; + + break; + case (SKProductPeriodUnitMonth): + msg[@"PROMO_PERIOD"] = commonMsg[@"MONTH"]; + k = 12.0f / 365.0f; + + break; + case (SKProductPeriodUnitYear): + msg[@"PROMO_PERIOD"] = commonMsg[@"YEAR"]; + k = 1.0f / 365.0f; + + break; + } + + float promoDaily = promo.price.floatValue * k / promo.subscriptionPeriod.numberOfUnits; + float promoWeekly = promoDaily * 7.0f; + float promoYearly = promoDaily * 365.0f; + float promoMonthly = promoYearly / 12.0f; + float promoDiscount = promoDaily / priceDaily; + + msg[@"PROMO_PRICE"] = [self formatPrice:promo.price locale:promo.priceLocale]; + msg[@"PROMO_DURATION"] = [GYFormatter formatPeriod:promo.subscriptionPeriod.numberOfUnits + unit:promo.subscriptionPeriod.unit + locale:locale]; + + msg[@"PROMO_DAILY"] = [self formatPrice:@(promoDaily) locale:promo.priceLocale]; + msg[@"PROMO_WEEKLY"] = [self formatPrice:@(promoWeekly) locale:promo.priceLocale]; + msg[@"PROMO_MONTHLY"] = [self formatPrice:@(promoMonthly) locale:promo.priceLocale]; + msg[@"PROMO_YEARLY"] = [self formatPrice:@(promoYearly) locale:promo.priceLocale]; + + msg[@"PROMO_DISCOUNT"] = [GYFormatter formatPercentage:@(promoDiscount) locale:locale]; + } + } + } + } + [priceCorrections addObject:@(priceCorrection)]; + + if (s.promotionalEligibility == GYSkuEligibilityEligible && msg[@"PROMO_PRICE"]) { + msg[@"PERIOD"] = msg[@"PROMO_PERIOD"]; + msg[@"PRICE"] = msg[@"PROMO_PRICE"]; + msg[@"DURATION"] = msg[@"PROMO_DURATION"]; + msg[@"DAILY"] = msg[@"PROMO_DAILY"]; + msg[@"WEEKLY"] = msg[@"PROMO_WEEKLY"]; + msg[@"MONTHLY"] = msg[@"PROMO_MONTHLY"]; + msg[@"YEARLY"] = msg[@"PROMO_YEARLY"]; + msg[@"DISCOUNT"] = msg[@"PROMO_DISCOUNT"]; + } else if (s.introductoryEligibility == GYSkuEligibilityEligible && msg[@"INTRO_PRICE"]) { + msg[@"PERIOD"] = msg[@"INTRO_PERIOD"]; + msg[@"PRICE"] = msg[@"INTRO_PRICE"]; + msg[@"DURATION"] = msg[@"INTRO_DURATION"]; + msg[@"DAILY"] = msg[@"INTRO_DAILY"]; + msg[@"WEEKLY"] = msg[@"INTRO_WEEKLY"]; + msg[@"MONTHLY"] = msg[@"INTRO_MONTHLY"]; + msg[@"YEARLY"] = msg[@"INTRO_YEARLY"]; + msg[@"DISCOUNT"] = msg[@"INTRO_DISCOUNT"]; + } else { + msg[@"PERIOD"] = msg[@"ORIGINAL_PERIOD"]; + msg[@"PRICE"] = msg[@"ORIGINAL_PRICE"]; + msg[@"DURATION"] = msg[@"ORIGINAL_DURATION"]; + msg[@"DAILY"] = msg[@"ORIGINAL_DAILY"]; + msg[@"WEEKLY"] = msg[@"ORIGINAL_WEEKLY"]; + msg[@"MONTHLY"] = msg[@"ORIGINAL_MONTHLY"]; + msg[@"YEARLY"] = msg[@"ORIGINAL_YEARLY"]; + } + + NSMutableDictionary *skusDetail = [NSMutableDictionary new]; + skusDetail[@"product"] = [s.product encodedObject]; + skusDetail[@"msg"] = msg; + skusDetail[@"identifier"] = s.skuId; + skusDetail[@"offeringid"] = s.offeringId; + skusDetail[@"promotionalid"] = s.promotionalId; + skusDetail[@"introductoryeligibility"] = @(s.introductoryEligibility); + skusDetail[@"promotionaleligibility"] = @(s.promotionalEligibility); + skusDetail[@"extravars"] = s.extravars; + + skusDetails[s.skuId] = skusDetail; + } + + // Add discount towards other skus + for (int i = 0; i < skus.count; i++) { + GYSku *sku = skus[i]; + + int units = 1; + if (@available(iOS 11.2, *)) { + if (sku.product.subscriptionPeriod.numberOfUnits != 0) { + units = (int) sku.product.subscriptionPeriod.numberOfUnits; + } + } + float originalSkuPrice = sku.product.price.floatValue * priceCorrections[i].floatValue / units; + + for (int j = 0; j < skus.count; j++) { + GYSku *otherSku = skus[j]; + + units = 1; + if (@available(iOS 11.2, *)) { + if (otherSku.product.subscriptionPeriod.numberOfUnits != 0) { + units = (int) otherSku.product.subscriptionPeriod.numberOfUnits; + } + } + float originalOtherSkuPrice = otherSku.product.price.floatValue * priceCorrections[j].floatValue / units; + + float discount = 0.0f; + if (originalSkuPrice > 0 && originalOtherSkuPrice > 0) { + discount = 1.0f - originalSkuPrice / originalOtherSkuPrice; + } + + NSString *key = [NSString stringWithFormat:@"ORIGINAL_DISCOUNT_%d", j+1]; + skusDetails[sku.skuId][@"msg"][key] = [GYFormatter formatPercentage:@(discount) locale:locale]; + } + } + + NSDictionary *settings = @{ + @"pwid": pwid, + @"locale": locale.languageCode, + @"uiStyle": uiStyle, + @"sdkVersion": Glassfy.sdkVersion, + @"appVersion": GYSysInfo.appVersion, + @"subplatform": @(GYSysInfo.subplatform), + @"store": @(GYStoreAppStore), + @"systemVersion": GYSysInfo.systemVersion, + @"sysInfo": GYSysInfo.sysInfo + }; + + return @{@"gy": @{@"skus": skusDetails, @"msg": commonMsg, @"settings": settings}}; +} + ++ (NSString *)formatPrice:(NSNumber *)price locale:(NSLocale *)locale +{ + if (price.floatValue == 0.0f) { + return @"$GL_FREE"; // will be translated by js + } + return [GYFormatter formatPrice:price locale:locale]; +} + +@end diff --git a/Source/GYPaywallJsonProvider.h b/Source/GYPaywallJsonProvider.h new file mode 100644 index 0000000..21d1299 --- /dev/null +++ b/Source/GYPaywallJsonProvider.h @@ -0,0 +1,15 @@ +// +// GYPaywallJsonProvider.h +// Glassfy +// +// Created by Federico Curzel on 10/05/23. +// + +#import +@class GYPaywall; + +NS_ASSUME_NONNULL_BEGIN +@interface GYPaywallJsonProvider: NSObject ++ (NSDictionary *)json:(GYPaywall *)paywall; +@end +NS_ASSUME_NONNULL_END diff --git a/Source/GYPaywallJsonProvider.m b/Source/GYPaywallJsonProvider.m new file mode 100644 index 0000000..4716be2 --- /dev/null +++ b/Source/GYPaywallJsonProvider.m @@ -0,0 +1,44 @@ +// +// GYPaywallJsonProvider.m +// Glassfy +// +// Created by Federico Curzel on 10/05/23. +// + +#import +#import "Glassfy+Private.h" +#import "GYPaywallJsonProvider.h" +#import "GYPaywallHtmlJsonProvider.h" +#import "GYPaywallNoCodeJsonProvider.h" +#import "GYPaywall+Private.h" + +@implementation GYPaywallJsonProvider: NSObject + ++ (NSDictionary *)json:(GYPaywall *)paywall { + NSString *uiStyle = [self uiStyle]; + + if ([paywall type] == GYPaywallTypeNoCode) { + return [GYPaywallNoCodeJsonProvider jsonWithPwid:paywall.pwid + locale:paywall.locale + uiStyle:uiStyle + skus:paywall.skus]; + } else { + return [GYPaywallHtmlJsonProvider jsonWithPwid:paywall.pwid + locale:paywall.locale + uiStyle:uiStyle + skus:paywall.skus]; + } +} + ++ (NSString *)uiStyle { +#if TARGET_OS_IPHONE + if (@available(iOS 13.0, *)) { + if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + return @"dark"; + } + } +#endif + return @"light"; +} + +@end diff --git a/Source/GYPaywallNoCodeJsonProvider.h b/Source/GYPaywallNoCodeJsonProvider.h new file mode 100644 index 0000000..07cf7de --- /dev/null +++ b/Source/GYPaywallNoCodeJsonProvider.h @@ -0,0 +1,17 @@ +// +// GYPaywallNoCodeJsonProvider.h +// Glassfy +// +// Created by Federico Curzel on 10/05/23. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@interface GYPaywallNoCodeJsonProvider: NSObject ++ (NSDictionary *)jsonWithPwid:(NSString *)pwid + locale:(NSLocale * _Nullable)locale + uiStyle:(NSString *)uiStyle + skus:(NSArray *)skus; +@end +NS_ASSUME_NONNULL_END diff --git a/Source/GYPaywallNoCodeJsonProvider.m b/Source/GYPaywallNoCodeJsonProvider.m new file mode 100644 index 0000000..046dda1 --- /dev/null +++ b/Source/GYPaywallNoCodeJsonProvider.m @@ -0,0 +1,267 @@ +// +// GYPaywallNoCodeJsonProvider.h +// Glassfy +// +// Created by Federico Curzel on 10/05/23. +// + +#import +#import +#import "GYFormatter.h" +#import "GYPaywallNoCodeJsonProvider.h" +#import "GYSku+Private.h" +#import "SKProduct+GYEncode.h" + +@implementation GYPaywallNoCodeJsonProvider ++ (NSDictionary *)jsonWithPwid:(NSString *)pwid + locale:(NSLocale * _Nullable)locale + uiStyle:(NSString *)uiStyle + skus:(NSArray *)skus +{ + NSMutableDictionary *skusDetails = [NSMutableDictionary new]; + NSMutableDictionary *commonMsg = [NSMutableDictionary new]; + if (@available(iOS 11.2, *)) { + commonMsg[@"$DAY"] = [GYFormatter formatUnit:SKProductPeriodUnitDay locale:locale]; + commonMsg[@"$WEEK"] = [GYFormatter formatUnit:SKProductPeriodUnitWeek locale:locale]; + commonMsg[@"$MONTH"] = [GYFormatter formatUnit:SKProductPeriodUnitMonth locale:locale]; + commonMsg[@"$YEAR"] = [GYFormatter formatUnit:SKProductPeriodUnitYear locale:locale]; + } + + NSMutableArray *priceCorrections = [NSMutableArray array]; + for (GYSku *s in skus) { + SKProduct *p = s.product; + + NSMutableDictionary *msg = [NSMutableDictionary new]; + msg[@"$TITLE"] = p.localizedTitle; + msg[@"$DESCRIPTION"] = p.localizedDescription; + msg[@"$ORIGINAL_PRICE"] = [self formatPrice:p.price locale:p.priceLocale]; + + float priceCorrection = 1.0f; + if (@available(iOS 11.2, *)) { + float k = 1.0f; + switch (p.subscriptionPeriod.unit) { + case (SKProductPeriodUnitDay): + msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$DAY"]; + k = 1.0f; + + break; + case (SKProductPeriodUnitWeek): + msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$WEEK"]; + k = 1.0f / 7.0f; + + break; + case (SKProductPeriodUnitMonth): + msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$MONTH"]; + k = 12.0f / 365.0f; + + break; + case (SKProductPeriodUnitYear): + msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$YEAR"]; + k = 1.0f / 365.0f; + + break; + } + priceCorrection = k; + + float priceDaily = p.price.floatValue * k / p.subscriptionPeriod.numberOfUnits; + float priceWeekly = priceDaily * 7.0f; + float priceYearly = priceDaily * 365.0f; + float priceMonthly = priceYearly / 12.0f; + + msg[@"$ORIGINAL_DURATION"] = [GYFormatter formatPeriod:p.subscriptionPeriod.numberOfUnits unit:p.subscriptionPeriod.unit locale:locale]; + + msg[@"$ORIGINAL_DAILY"] = [self formatPrice:@(priceDaily) locale:p.priceLocale]; + msg[@"$ORIGINAL_WEEKLY"] = [self formatPrice:@(priceWeekly) locale:p.priceLocale]; + msg[@"$ORIGINAL_MONTHLY"] = [self formatPrice:@(priceMonthly) locale:p.priceLocale]; + msg[@"$ORIGINAL_YEARLY"] = [self formatPrice:@(priceYearly) locale:p.priceLocale]; + + if (p.introductoryPrice) { + float k = 1.0f; + switch (p.introductoryPrice.subscriptionPeriod.unit) { + case (SKProductPeriodUnitDay): + msg[@"$INTRO_PERIOD"] = commonMsg[@"$DAY"]; + k = 1.0f; + + break; + case (SKProductPeriodUnitWeek): + msg[@"$INTRO_PERIOD"] = commonMsg[@"$WEEK"]; + k = 1.0f / 7.0f; + + break; + case (SKProductPeriodUnitMonth): + msg[@"$INTRO_PERIOD"] = commonMsg[@"$MONTH"]; + k = 12.0f / 365.0f; + + break; + case (SKProductPeriodUnitYear): + msg[@"$INTRO_PERIOD"] = commonMsg[@"$YEAR"]; + k = 1.0f / 365.0f; + + break; + } + + float introDaily = p.introductoryPrice.price.floatValue * k / p.introductoryPrice.subscriptionPeriod.numberOfUnits; + float introWeekly = introDaily * 7.0f; + float introYearly = introDaily * 365.0f; + float introMonthly = introYearly / 12.0f; + float introDiscount = introDaily / priceDaily; + + msg[@"$INTRO_PRICE"] = [self formatPrice:p.introductoryPrice.price locale:p.introductoryPrice.priceLocale]; + msg[@"$INTRO_DURATION"] = [GYFormatter formatPeriod:p.introductoryPrice.subscriptionPeriod.numberOfUnits + unit:p.introductoryPrice.subscriptionPeriod.unit + locale:locale]; + + msg[@"$INTRO_DAILY"] = [self formatPrice:@(introDaily) locale:p.introductoryPrice.priceLocale]; + msg[@"$INTRO_WEEKLY"] = [self formatPrice:@(introWeekly) locale:p.introductoryPrice.priceLocale]; + msg[@"$INTRO_MONTHLY"] = [self formatPrice:@(introMonthly) locale:p.introductoryPrice.priceLocale]; + msg[@"$INTRO_YEARLY"] = [self formatPrice:@(introYearly) locale:p.introductoryPrice.priceLocale]; + + msg[@"$INTRO_DISCOUNT"] = [GYFormatter formatPercentage:@(introDiscount) locale:locale]; + } + + if (s.promotionalId) { + NSPredicate *p = [NSPredicate predicateWithFormat:@"identifier = %@", s.promotionalId]; + if (@available(iOS 12.2, *)) { + SKProductDiscount *promo = [[s.product.discounts filteredArrayUsingPredicate:p] firstObject]; + + if (promo) { + float k = 1.0f; + switch (promo.subscriptionPeriod.unit) { + case (SKProductPeriodUnitDay): + msg[@"$PROMO_PERIOD"] = commonMsg[@"$DAY"]; + k = 1.0f; + + break; + case (SKProductPeriodUnitWeek): + msg[@"$PROMO_PERIOD"] = commonMsg[@"$WEEK"]; + k = 1.0f / 7.0f; + + break; + case (SKProductPeriodUnitMonth): + msg[@"$PROMO_PERIOD"] = commonMsg[@"$MONTH"]; + k = 12.0f / 365.0f; + + break; + case (SKProductPeriodUnitYear): + msg[@"$PROMO_PERIOD"] = commonMsg[@"$YEAR"]; + k = 1.0f / 365.0f; + + break; + } + + float promoDaily = promo.price.floatValue * k / promo.subscriptionPeriod.numberOfUnits; + float promoWeekly = promoDaily * 7.0f; + float promoYearly = promoDaily * 365.0f; + float promoMonthly = promoYearly / 12.0f; + float promoDiscount = promoDaily / priceDaily; + + msg[@"$PROMO_PRICE"] = [self formatPrice:promo.price locale:promo.priceLocale]; + msg[@"$PROMO_DURATION"] = [GYFormatter formatPeriod:promo.subscriptionPeriod.numberOfUnits + unit:promo.subscriptionPeriod.unit + locale:locale]; + + msg[@"$PROMO_DAILY"] = [self formatPrice:@(promoDaily) locale:promo.priceLocale]; + msg[@"$PROMO_WEEKLY"] = [self formatPrice:@(promoWeekly) locale:promo.priceLocale]; + msg[@"$PROMO_MONTHLY"] = [self formatPrice:@(promoMonthly) locale:promo.priceLocale]; + msg[@"$PROMO_YEARLY"] = [self formatPrice:@(promoYearly) locale:promo.priceLocale]; + + msg[@"$PROMO_DISCOUNT"] = [GYFormatter formatPercentage:@(promoDiscount) locale:locale]; + } + } + } + } + [priceCorrections addObject:@(priceCorrection)]; + + if (s.promotionalEligibility == GYSkuEligibilityEligible && msg[@"$PROMO_PRICE"]) { + msg[@"$PERIOD"] = msg[@"$PROMO_PERIOD"]; + msg[@"$PRICE"] = msg[@"$PROMO_PRICE"]; + msg[@"$DURATION"] = msg[@"$PROMO_DURATION"]; + msg[@"$DAILY"] = msg[@"$PROMO_DAILY"]; + msg[@"$WEEKLY"] = msg[@"$PROMO_WEEKLY"]; + msg[@"$MONTHLY"] = msg[@"$PROMO_MONTHLY"]; + msg[@"$YEARLY"] = msg[@"$PROMO_YEARLY"]; + msg[@"$DISCOUNT"] = msg[@"$PROMO_DISCOUNT"]; + } else if (s.introductoryEligibility == GYSkuEligibilityEligible && msg[@"$INTRO_PRICE"]) { + msg[@"$PERIOD"] = msg[@"$INTRO_PERIOD"]; + msg[@"$PRICE"] = msg[@"$INTRO_PRICE"]; + msg[@"$DURATION"] = msg[@"$INTRO_DURATION"]; + msg[@"$DAILY"] = msg[@"$INTRO_DAILY"]; + msg[@"$WEEKLY"] = msg[@"$INTRO_WEEKLY"]; + msg[@"$MONTHLY"] = msg[@"$INTRO_MONTHLY"]; + msg[@"$YEARLY"] = msg[@"$INTRO_YEARLY"]; + msg[@"$DISCOUNT"] = msg[@"$INTRO_DISCOUNT"]; + } else { + msg[@"$PERIOD"] = msg[@"$ORIGINAL_PERIOD"]; + msg[@"$PRICE"] = msg[@"$ORIGINAL_PRICE"]; + msg[@"$DURATION"] = msg[@"$ORIGINAL_DURATION"]; + msg[@"$DAILY"] = msg[@"$ORIGINAL_DAILY"]; + msg[@"$WEEKLY"] = msg[@"$ORIGINAL_WEEKLY"]; + msg[@"$MONTHLY"] = msg[@"$ORIGINAL_MONTHLY"]; + msg[@"$YEARLY"] = msg[@"$ORIGINAL_YEARLY"]; + } + + NSMutableDictionary *skusDetail = [NSMutableDictionary new]; + skusDetail[@"product"] = [s.product encodedObject]; + skusDetail[@"msg"] = msg; + skusDetail[@"identifier"] = s.skuId; + skusDetail[@"offeringid"] = s.offeringId; + skusDetail[@"promotionalid"] = s.promotionalId; + skusDetail[@"introductoryeligibility"] = @(s.introductoryEligibility); + skusDetail[@"promotionaleligibility"] = @(s.promotionalEligibility); + skusDetail[@"extravars"] = s.extravars; + + skusDetails[s.skuId] = skusDetail; + } + + // Add discount towards other skus + for (int i = 0; i < skus.count; i++) { + GYSku *sku = skus[i]; + + int units = 1; + if (@available(iOS 11.2, *)) { + if (sku.product.subscriptionPeriod.numberOfUnits != 0) { + units = (int) sku.product.subscriptionPeriod.numberOfUnits; + } + } + float originalSkuPrice = sku.product.price.floatValue * priceCorrections[i].floatValue / units; + + for (int j = 0; j < skus.count; j++) { + GYSku *otherSku = skus[j]; + + units = 1; + if (@available(iOS 11.2, *)) { + if (otherSku.product.subscriptionPeriod.numberOfUnits != 0) { + units = (int) otherSku.product.subscriptionPeriod.numberOfUnits; + } + } + float originalOtherSkuPrice = otherSku.product.price.floatValue * priceCorrections[j].floatValue / units; + + float discount = 0.0f; + if (originalSkuPrice > 0 && originalOtherSkuPrice > 0) { + discount = 1.0f - originalSkuPrice / originalOtherSkuPrice; + } + + NSString *key = [NSString stringWithFormat:@"$ORIGINAL_DISCOUNT_%d", j+1]; + skusDetails[sku.skuId][@"msg"][key] = [GYFormatter formatPercentage:@(discount) locale:locale]; + } + } + + NSDictionary *settings = @{ + @"pwid": pwid, + @"locale": locale.languageCode, + @"uiStyle": uiStyle + }; + + return @{@"skus": skusDetails, @"msg": commonMsg, @"settings": settings}; +} + ++ (NSString *)formatPrice:(NSNumber *)price locale:(NSLocale *)locale +{ + if (price.floatValue == 0.0f) { + return @"$GL_FREE"; // will be translated by js + } + return [GYFormatter formatPrice:price locale:locale]; +} + +@end + diff --git a/Source/GYPaywallViewController+Private.h b/Source/GYPaywallViewController+Private.h index a7ba087..e939eae 100644 --- a/Source/GYPaywallViewController+Private.h +++ b/Source/GYPaywallViewController+Private.h @@ -4,6 +4,7 @@ // // Created by Luca Garbolino on 13/10/21. // + #if TARGET_OS_IPHONE API_UNAVAILABLE_BEGIN(macos, watchos) @@ -14,11 +15,12 @@ API_UNAVAILABLE_BEGIN(macos, watchos) NS_ASSUME_NONNULL_BEGIN @interface GYPaywallViewController (Private) -+ (instancetype)paywallWithResponse:(GYAPIPaywallResponse *)res; + ++ (GYPaywallViewController*)instanceWithPaywall:(GYPaywall *)paywall; + @end NS_ASSUME_NONNULL_END - API_UNAVAILABLE_END #endif diff --git a/Source/GYPaywallViewController.m b/Source/GYPaywallViewController.m index c6be658..5987748 100644 --- a/Source/GYPaywallViewController.m +++ b/Source/GYPaywallViewController.m @@ -14,34 +14,44 @@ #import "GYSku+Private.h" #import "SKProduct+GYEncode.h" #import "GYFormatter.h" +#import "GYPaywall+Private.h" +#import "GYSysInfo.h" #if TARGET_OS_IPHONE -@interface GYPaywallViewController () -@property(nonatomic, strong) NSString *content; +#define kMessageHandlerName @"GYMessageHandler" + +@interface GYPaywallViewController() +@property(nonatomic, strong, nullable) NSURL *url; @property(nonatomic, strong) NSString *pwid; @property(nonatomic, strong) NSLocale *locale; @property(nonatomic, strong) NSArray *skus; - -@property(nonatomic, weak) UIView *activityView; +@property(nonatomic, strong) NSString *version; +@property(nonatomic, assign) GYPaywallType paywallType; +@property(nonatomic, strong) GYPaywall *paywall; +@property(nonatomic, weak) UIActivityIndicatorView *activityIndicator; @property(nonatomic, unsafe_unretained) WKWebView *webview; - @property(nonatomic, copy) GYPaywallCloseBlock closeHandler; @property(nonatomic, copy) GYPaywallPurchaseBlock purchaseHandler; @property(nonatomic, copy) GYPaywallLinkBlock linkHandler; @property(nonatomic, copy) GYPaywallRestoreBlock restoreHandler; + +- (void)loadView; +- (void)startLoadingPaywall; @end @implementation GYPaywallViewController (Private) -+ (instancetype)paywallWithResponse:(GYAPIPaywallResponse *)res ++ (GYPaywallViewController*)instanceWithPaywall:(GYPaywall *)paywall { GYPaywallViewController *vc = [self new]; - vc.content = res.content; - vc.skus = res.skus; - vc.pwid = res.pwid; - vc.locale = [NSLocale localeWithLocaleIdentifier:res.locale ?: @"en-US"]; - + vc.paywall = paywall; + vc.url = paywall.contentUrl; + vc.version = paywall.version; + vc.paywallType = paywall.type; + vc.skus = paywall.skus ?: @[]; + vc.pwid = paywall.pwid; + vc.locale = paywall.locale; return vc; } @@ -54,22 +64,20 @@ - (instancetype)init self = [super init]; if (self) { // default handlers - __weak typeof(self) weakSelf = self; + typeof(self) __weak weakSelf = self; _closeHandler = ^(GYTransaction *t, NSError *err) { GYLogInfo(@"PAYWALL Close default handler..."); - [weakSelf dismissViewControllerAnimated:YES completion:nil]; }; - + _purchaseHandler = ^(GYSku *sku) { GYLogInfo(@"PAYWALL Purchase default handler..."); - [weakSelf.activityView removeFromSuperview]; - - UIView *activityView = [weakSelf buildActivityView:weakSelf.view.bounds]; - [weakSelf.view addSubview:activityView]; - weakSelf.activityView = activityView; + [weakSelf.activityIndicator startAnimating]; [Glassfy purchaseSku:sku completion:^(GYTransaction *t, NSError *err) { - [weakSelf.activityView removeFromSuperview]; - weakSelf.closeHandler(t, err); + [weakSelf.activityIndicator stopAnimating]; + GYPaywallCloseBlock handler = weakSelf.closeHandler; + if (handler) { + handler(t, err); + } }]; }; @@ -80,45 +88,77 @@ - (instancetype)init _restoreHandler = ^{ GYLogInfo(@"PAYWALL Restore default handler..."); - [weakSelf.activityView removeFromSuperview]; - - UIView *activityView = [weakSelf buildActivityView:weakSelf.view.bounds]; - [weakSelf.view addSubview:activityView]; - weakSelf.activityView = activityView; - + [weakSelf.activityIndicator startAnimating]; [Glassfy restorePurchasesWithCompletion:^(GYPermissions *p, NSError *err) { - [weakSelf.activityView removeFromSuperview]; - weakSelf.closeHandler(nil, err); + [weakSelf.activityIndicator stopAnimating]; + [weakSelf closeAndDismiss:YES]; }]; }; } return self; } +- (void)closeAndDismiss:(BOOL)dismiss +{ + typeof(self) __weak weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + GYPaywallCloseBlock handler = weakSelf.closeHandler; + if (handler) { + handler(nil, nil); + } + [weakSelf setCloseHandler:^(GYTransaction * _Nullable t, NSError * _Nullable e) {}]; + if (dismiss) { + [weakSelf dismissViewControllerAnimated:YES completion:nil]; + } + }); +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + self.activityIndicator.center = self.view.center; + [self startLoadingPaywall]; +} + - (void)loadView { UIView *view = [UIView new]; view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + if (@available(iOS 13.0, *)) { + view.backgroundColor = UIColor.systemBackgroundColor; + } else { + view.backgroundColor = UIColor.whiteColor; + } - // webview WKWebView *webview = [GYPaywallViewController buildWebView]; [view addSubview:webview]; + UIActivityIndicatorView *activityIndicator = [self buildActivityIndicator]; + [view addSubview:activityIndicator]; + self.view = view; self.webview = webview; + self.activityIndicator = activityIndicator; } -- (void)viewWillAppear:(BOOL)animated +- (UIActivityIndicatorView*)buildActivityIndicator { - [super viewWillAppear:animated]; - - [self startLoadingPaywall]; + UIActivityIndicatorViewStyle activityIndicatorStyle; + if (@available(iOS 13.0, *)) { + activityIndicatorStyle = UIActivityIndicatorViewStyleLarge; + } else { + activityIndicatorStyle = UIActivityIndicatorViewStyleGray; + } + UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:activityIndicatorStyle]; + activityIndicator.hidesWhenStopped = YES; + [activityIndicator startAnimating]; + return activityIndicator; } - (void)viewWillDisappear:(BOOL)animated { [self stopLoadingPaywall]; - + [self closeAndDismiss:NO]; [super viewWillDisappear:animated]; } @@ -142,35 +182,11 @@ + (WKWebView *)buildWebView webview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; webview.scrollView.showsVerticalScrollIndicator = NO; webview.scrollView.showsHorizontalScrollIndicator = NO; + webview.alpha = 0; return webview; } -- (UIView *)buildActivityView:(CGRect)frame -{ - UIView *activity = [[UIView alloc] initWithFrame:frame];; - activity.backgroundColor = [UIColor colorWithWhite:.3f alpha:.7f]; - activity.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - - UIActivityIndicatorView *activityIndicator; - if (@available(iOS 13, *)) { - activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; - } - else { - activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; - } - activityIndicator.color = UIColor.whiteColor; - activityIndicator.center = activity.center; - activityIndicator.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | - UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; - [activityIndicator startAnimating]; - - [activity addSubview:activityIndicator]; - - return activity; -} - - #pragma mark - handlers - (void)setCloseHandler:(GYPaywallCloseBlock)handler @@ -206,7 +222,7 @@ - (void)setRestoreHandler:(GYPaywallRestoreBlock)handler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { - if (![message.name isEqualToString:@"GYMessageHandler"]) { + if (![message.name isEqualToString:kMessageHandlerName]) { return; } @@ -227,13 +243,10 @@ - (void)userContentController:(WKUserContentController *)userContentController d data = @{}; } - __weak typeof(self) weakSelf = self; + typeof(self) __weak weakSelf = self; if ([action isEqualToString:@"close"]) { dispatch_async(dispatch_get_main_queue(), ^{ - GYPaywallCloseBlock handler = weakSelf.closeHandler; - if (handler) { - handler(nil, nil); - } + [weakSelf closeAndDismiss:YES]; }); } else if ([action isEqualToString:@"purchase"]) { @@ -275,7 +288,6 @@ - (void)userContentController:(WKUserContentController *)userContentController d handler(url); } }); - } else if ([action isEqualToString:@"restore"]) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -293,15 +305,20 @@ - (void)userContentController:(WKUserContentController *)userContentController d } } - #pragma mark - WKNavigationDelegate - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + if ([webView.URL isEqual:_paywall.contentUrl]) { + GYLogDebug(@"PAYWALL Finished loading"); + [UIView animateWithDuration:0.1 animations:^{ + self.webview.alpha = 1; + }]; + [self.activityIndicator stopAnimating]; + } [self updatePaywallTags]; } - #pragma mark - utils - (void)evaluateJs:(nonnull NSString *)action data:(nullable id)obj completion:(void(^_Nullable)(id _Nullable, NSError * _Nullable))completion @@ -327,285 +344,38 @@ - (void)evaluateJs:(nonnull NSString *)action data:(nullable id)obj completion:( - (void)startLoadingPaywall { GYLogInfo(@"PAYWALL Start loading"); - [self.webview.configuration.userContentController addScriptMessageHandler:self name:@"GYMessageHandler"]; + [self.webview.configuration.userContentController addScriptMessageHandler:self name:kMessageHandlerName]; [self.webview setNavigationDelegate:self]; - [self.webview loadHTMLString:self.content baseURL:nil]; + if (_paywall.preloadedContent) { + GYLogInfo(@"PAYWALL Loading preloaded content"); + [self.webview loadHTMLString:_paywall.preloadedContent baseURL:_paywall.contentUrl]; + } else { + GYLogInfo(@"PAYWALL Loading content from url"); + NSURLRequest *request = [NSURLRequest requestWithURL:_paywall.contentUrl]; + [self.webview loadRequest:request]; + } } - (void)stopLoadingPaywall { GYLogInfo(@"PAYWALL Stop loading"); - [self.webview.configuration.userContentController removeScriptMessageHandlerForName:@"GYMessageHandler"]; + [self.webview.configuration.userContentController removeScriptMessageHandlerForName:kMessageHandlerName]; [self.webview setNavigationDelegate:nil]; - [self.webview stopLoading]; } - (void)updatePaywallTags { - NSDictionary *skusDetails = [self skuDetails]; - [self evaluateJs:@"setSkuDetails" data:skusDetails completion:^(id res, NSError *error) { + [self evaluateJs:@"setSkuDetails" + data: [_paywall config] + completion:^(id res, NSError *error) { if (error) { GYLogErr(@"PAYWALL Error evaluating js:\n%@", error); } }]; } -- (NSDictionary *)skuDetails -{ - NSMutableDictionary *skusDetails = [NSMutableDictionary new]; - NSMutableDictionary *commonMsg = [NSMutableDictionary new]; - if (@available(iOS 11.2, *)) { - commonMsg[@"$DAY"] = [GYFormatter formatUnit:SKProductPeriodUnitDay locale:self.locale]; - commonMsg[@"$WEEK"] = [GYFormatter formatUnit:SKProductPeriodUnitWeek locale:self.locale]; - commonMsg[@"$MONTH"] = [GYFormatter formatUnit:SKProductPeriodUnitMonth locale:self.locale]; - commonMsg[@"$YEAR"] = [GYFormatter formatUnit:SKProductPeriodUnitYear locale:self.locale]; - } - - NSMutableArray *priceCorrections = [NSMutableArray array]; - for (GYSku *s in self.skus) { - SKProduct *p = s.product; - - NSMutableDictionary *msg = [NSMutableDictionary new]; - msg[@"$TITLE"] = p.localizedTitle; - msg[@"$DESCRIPTION"] = p.localizedDescription; - msg[@"$ORIGINAL_PRICE"] = [self formatPrice:p.price locale:p.priceLocale]; - - float priceCorrection = 1.0f; - if (@available(iOS 11.2, *)) { - float k = 1.0f; - switch (p.subscriptionPeriod.unit) { - case (SKProductPeriodUnitDay): - msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$DAY"]; - k = 1.0f; - - break; - case (SKProductPeriodUnitWeek): - msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$WEEK"]; - k = 1.0f / 7.0f; - - break; - case (SKProductPeriodUnitMonth): - msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$MONTH"]; - k = 12.0f / 365.0f; - - break; - case (SKProductPeriodUnitYear): - msg[@"$ORIGINAL_PERIOD"] = commonMsg[@"$YEAR"]; - k = 1.0f / 365.0f; - - break; - } - priceCorrection = k; - - float priceDaily = p.price.floatValue * k / p.subscriptionPeriod.numberOfUnits; - float priceWeekly = priceDaily * 7.0f; - float priceYearly = priceDaily * 365.0f; - float priceMonthly = priceYearly / 12.0f; - - msg[@"$ORIGINAL_DURATION"] = [GYFormatter formatPeriod:p.subscriptionPeriod.numberOfUnits unit:p.subscriptionPeriod.unit locale:self.locale]; - - msg[@"$ORIGINAL_DAILY"] = [self formatPrice:@(priceDaily) locale:p.priceLocale]; - msg[@"$ORIGINAL_WEEKLY"] = [self formatPrice:@(priceWeekly) locale:p.priceLocale]; - msg[@"$ORIGINAL_MONTHLY"] = [self formatPrice:@(priceMonthly) locale:p.priceLocale]; - msg[@"$ORIGINAL_YEARLY"] = [self formatPrice:@(priceYearly) locale:p.priceLocale]; - - if (p.introductoryPrice) { - float k = 1.0f; - switch (p.introductoryPrice.subscriptionPeriod.unit) { - case (SKProductPeriodUnitDay): - msg[@"$INTRO_PERIOD"] = commonMsg[@"$DAY"]; - k = 1.0f; - - break; - case (SKProductPeriodUnitWeek): - msg[@"$INTRO_PERIOD"] = commonMsg[@"$WEEK"]; - k = 1.0f / 7.0f; - - break; - case (SKProductPeriodUnitMonth): - msg[@"$INTRO_PERIOD"] = commonMsg[@"$MONTH"]; - k = 12.0f / 365.0f; - - break; - case (SKProductPeriodUnitYear): - msg[@"$INTRO_PERIOD"] = commonMsg[@"$YEAR"]; - k = 1.0f / 365.0f; - - break; - } - - float introDaily = p.introductoryPrice.price.floatValue * k / p.introductoryPrice.subscriptionPeriod.numberOfUnits; - float introWeekly = introDaily * 7.0f; - float introYearly = introDaily * 365.0f; - float introMonthly = introYearly / 12.0f; - float introDiscount = introDaily / priceDaily; - - msg[@"$INTRO_PRICE"] = [self formatPrice:p.introductoryPrice.price locale:p.introductoryPrice.priceLocale]; - msg[@"$INTRO_DURATION"] = [GYFormatter formatPeriod:p.introductoryPrice.subscriptionPeriod.numberOfUnits - unit:p.introductoryPrice.subscriptionPeriod.unit - locale:self.locale]; - - msg[@"$INTRO_DAILY"] = [self formatPrice:@(introDaily) locale:p.introductoryPrice.priceLocale]; - msg[@"$INTRO_WEEKLY"] = [self formatPrice:@(introWeekly) locale:p.introductoryPrice.priceLocale]; - msg[@"$INTRO_MONTHLY"] = [self formatPrice:@(introMonthly) locale:p.introductoryPrice.priceLocale]; - msg[@"$INTRO_YEARLY"] = [self formatPrice:@(introYearly) locale:p.introductoryPrice.priceLocale]; - - msg[@"$INTRO_DISCOUNT"] = [GYFormatter formatPercentage:@(introDiscount) locale:self.locale]; - } - - if (s.promotionalId) { - NSPredicate *p = [NSPredicate predicateWithFormat:@"identifier = %@", s.promotionalId]; - if (@available(iOS 12.2, *)) { - SKProductDiscount *promo = [[s.product.discounts filteredArrayUsingPredicate:p] firstObject]; - - if (promo) { - float k = 1.0f; - switch (promo.subscriptionPeriod.unit) { - case (SKProductPeriodUnitDay): - msg[@"$PROMO_PERIOD"] = commonMsg[@"$DAY"]; - k = 1.0f; - - break; - case (SKProductPeriodUnitWeek): - msg[@"$PROMO_PERIOD"] = commonMsg[@"$WEEK"]; - k = 1.0f / 7.0f; - - break; - case (SKProductPeriodUnitMonth): - msg[@"$PROMO_PERIOD"] = commonMsg[@"$MONTH"]; - k = 12.0f / 365.0f; - - break; - case (SKProductPeriodUnitYear): - msg[@"$PROMO_PERIOD"] = commonMsg[@"$YEAR"]; - k = 1.0f / 365.0f; - - break; - } - - float promoDaily = promo.price.floatValue * k / promo.subscriptionPeriod.numberOfUnits; - float promoWeekly = promoDaily * 7.0f; - float promoYearly = promoDaily * 365.0f; - float promoMonthly = promoYearly / 12.0f; - float promoDiscount = promoDaily / priceDaily; - - msg[@"$PROMO_PRICE"] = [self formatPrice:promo.price locale:promo.priceLocale]; - msg[@"$PROMO_DURATION"] = [GYFormatter formatPeriod:promo.subscriptionPeriod.numberOfUnits - unit:promo.subscriptionPeriod.unit - locale:self.locale]; - - msg[@"$PROMO_DAILY"] = [self formatPrice:@(promoDaily) locale:promo.priceLocale]; - msg[@"$PROMO_WEEKLY"] = [self formatPrice:@(promoWeekly) locale:promo.priceLocale]; - msg[@"$PROMO_MONTHLY"] = [self formatPrice:@(promoMonthly) locale:promo.priceLocale]; - msg[@"$PROMO_YEARLY"] = [self formatPrice:@(promoYearly) locale:promo.priceLocale]; - - msg[@"$PROMO_DISCOUNT"] = [GYFormatter formatPercentage:@(promoDiscount) locale:self.locale]; - } - } - } - } - [priceCorrections addObject:@(priceCorrection)]; - - if (s.promotionalEligibility == GYSkuEligibilityEligible && msg[@"$PROMO_PRICE"]) { - msg[@"$PERIOD"] = msg[@"$PROMO_PERIOD"]; - msg[@"$PRICE"] = msg[@"$PROMO_PRICE"]; - msg[@"$DURATION"] = msg[@"$PROMO_DURATION"]; - msg[@"$DAILY"] = msg[@"$PROMO_DAILY"]; - msg[@"$WEEKLY"] = msg[@"$PROMO_WEEKLY"]; - msg[@"$MONTHLY"] = msg[@"$PROMO_MONTHLY"]; - msg[@"$YEARLY"] = msg[@"$PROMO_YEARLY"]; - msg[@"$DISCOUNT"] = msg[@"$PROMO_DISCOUNT"]; - } else if (s.introductoryEligibility == GYSkuEligibilityEligible && msg[@"$INTRO_PRICE"]) { - msg[@"$PERIOD"] = msg[@"$INTRO_PERIOD"]; - msg[@"$PRICE"] = msg[@"$INTRO_PRICE"]; - msg[@"$DURATION"] = msg[@"$INTRO_DURATION"]; - msg[@"$DAILY"] = msg[@"$INTRO_DAILY"]; - msg[@"$WEEKLY"] = msg[@"$INTRO_WEEKLY"]; - msg[@"$MONTHLY"] = msg[@"$INTRO_MONTHLY"]; - msg[@"$YEARLY"] = msg[@"$INTRO_YEARLY"]; - msg[@"$DISCOUNT"] = msg[@"$INTRO_DISCOUNT"]; - } else { - msg[@"$PERIOD"] = msg[@"$ORIGINAL_PERIOD"]; - msg[@"$PRICE"] = msg[@"$ORIGINAL_PRICE"]; - msg[@"$DURATION"] = msg[@"$ORIGINAL_DURATION"]; - msg[@"$DAILY"] = msg[@"$ORIGINAL_DAILY"]; - msg[@"$WEEKLY"] = msg[@"$ORIGINAL_WEEKLY"]; - msg[@"$MONTHLY"] = msg[@"$ORIGINAL_MONTHLY"]; - msg[@"$YEARLY"] = msg[@"$ORIGINAL_YEARLY"]; - } - - NSMutableDictionary *skusDetail = [NSMutableDictionary new]; - skusDetail[@"product"] = [s.product encodedObject]; - skusDetail[@"msg"] = msg; - skusDetail[@"identifier"] = s.skuId; - skusDetail[@"offeringid"] = s.offeringId; - skusDetail[@"promotionalid"] = s.promotionalId; - skusDetail[@"introductoryeligibility"] = @(s.introductoryEligibility); - skusDetail[@"promotionaleligibility"] = @(s.promotionalEligibility); - skusDetail[@"extravars"] = s.extravars; - - skusDetails[s.skuId] = skusDetail; - } - - // Add discount towards other skus - for (int i = 0; i < self.skus.count; i++) { - GYSku *sku = self.skus[i]; - - int units = 1; - if (@available(iOS 11.2, *)) { - if (sku.product.subscriptionPeriod.numberOfUnits != 0) { - units = (int) sku.product.subscriptionPeriod.numberOfUnits; - } - } - float originalSkuPrice = sku.product.price.floatValue * priceCorrections[i].floatValue / units; - - for (int j = 0; j < self.skus.count; j++) { - GYSku *otherSku = self.skus[j]; - - units = 1; - if (@available(iOS 11.2, *)) { - if (otherSku.product.subscriptionPeriod.numberOfUnits != 0) { - units = (int) otherSku.product.subscriptionPeriod.numberOfUnits; - } - } - float originalOtherSkuPrice = otherSku.product.price.floatValue * priceCorrections[j].floatValue / units; - - float discount = 0.0f; - if (originalSkuPrice > 0 && originalOtherSkuPrice > 0) { - discount = 1.0f - originalSkuPrice / originalOtherSkuPrice; - } - - NSString *key = [NSString stringWithFormat:@"$ORIGINAL_DISCOUNT_%d", j+1]; - skusDetails[sku.skuId][@"msg"][key] = [GYFormatter formatPercentage:@(discount) locale:self.locale]; - } - } - - NSString *uiStyle = @"light"; - if (@available(iOS 13.0, *)) { - if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { - uiStyle = @"dark"; - } - } - - NSDictionary *settings = @{ - @"pwid": self.pwid, - @"locale": self.locale.languageCode, - @"uiStyle": uiStyle - }; - - return @{@"skus": skusDetails, @"msg": commonMsg, @"settings": settings}; -} - -- (NSString *)formatPrice:(NSNumber *)price locale:(NSLocale *)locale -{ - if (price.floatValue == 0.0f) { - return @"$GL_FREE"; // will be translated by js - } - return [GYFormatter formatPrice:price locale:locale]; -} - @end #endif diff --git a/Source/GYSysInfo.h b/Source/GYSysInfo.h index 7c2b8f1..4c46165 100644 --- a/Source/GYSysInfo.h +++ b/Source/GYSysInfo.h @@ -6,11 +6,17 @@ // #import +#import "GYInternalType.h" NS_ASSUME_NONNULL_BEGIN @interface GYSysInfo : NSObject @property(class, nonatomic, strong, readonly) NSString *installationInfo; + +@property(class, nonatomic, assign, readonly) GYSubplatform subplatform; +@property(class, nonatomic, strong, readonly) NSString *appVersion; +@property(class, nonatomic, strong, readonly) NSString *systemVersion; +@property(class, nonatomic, strong, readonly, nullable) NSString *sysInfo; @property(class, nonatomic, strong, readonly, nullable) NSNotificationName applicationDidBecomeActiveNotification; @end diff --git a/Source/GYSysInfo.m b/Source/GYSysInfo.m index ee66f2c..690d2ce 100644 --- a/Source/GYSysInfo.m +++ b/Source/GYSysInfo.m @@ -16,17 +16,6 @@ @implementation GYSysInfo -typedef NS_ENUM(NSUInteger, GYSubplatform) { - GYSubplatformUnknown = 0, - GYSubplatformiOS = 1, - GYSubplatformCatalyst = 2, - GYSubplatformTV = 3, - GYSubplatformWatch = 4, - GYSubplatformOSx = 5, - GYSubplatformDrive = 6, - GYSubplatformSimulator = 7 -}; - + (NSString *)installationInfo { // store : sub-platform : os version : device type : sdkVersion : appVersion diff --git a/Source/Glassfy.m b/Source/Glassfy.m index c9e1556..52143f7 100644 --- a/Source/Glassfy.m +++ b/Source/Glassfy.m @@ -40,7 +40,7 @@ + (Glassfy *)shared + (NSString *)sdkVersion { - return @"1.3.8"; + return @"1.4.0"; } + (void)initializeWithAPIKey:(NSString *)apiKey @@ -161,10 +161,31 @@ + (void)getUserProperties:(GYUserPropertiesCompletion)block }); } -+ (void)paywallWithId:(NSString *)paywallid completion:(GYPaywallCompletion)block ++ (void)paywallWithRemoteConfigurationId:(NSString *)remoteConfigId + completion:(GYPaywallCompletion)block { dispatch_async(Glassfy.shared.glqueue, ^{ - [Glassfy.shared.manager getPaywall:paywallid completion:block]; + [Glassfy.shared.manager paywallWithRemoteConfigurationId:remoteConfigId + completion:block]; + }); +} + ++ (void)paywallViewControllerWithRemoteConfigurationId:(NSString *)remoteConfigId + completion:(GYPaywallViewControllerCompletion)block +{ + [self paywallViewControllerWithRemoteConfigurationId:remoteConfigId + awaitLoading:false + completion:block]; +} + ++ (void)paywallViewControllerWithRemoteConfigurationId:(NSString *)remoteConfigId + awaitLoading:(BOOL)awaitLoading + completion:(GYPaywallViewControllerCompletion)block +{ + dispatch_async(Glassfy.shared.glqueue, ^{ + [Glassfy.shared.manager paywallViewControllerWithRemoteConfigurationId:remoteConfigId + awaitLoading:awaitLoading + completion:block]; }); } @@ -244,7 +265,12 @@ + (void)connectGlassfyUniversalCode:(NSString*)universalCode + (void)skuWithIdentifier:(NSString *)skuid completion:(GYSkuBlock)block { - return [self skuWithId:skuid completion:block]; + [self skuWithId:skuid completion:block]; +} + ++ (void)paywallWithId:(NSString *)paywallid completion:(GYPaywallViewControllerCompletion)block +{ + [self paywallViewControllerWithRemoteConfigurationId:paywallid completion:block]; } @end diff --git a/Source/Public/GYPaywall.h b/Source/Public/GYPaywall.h new file mode 100644 index 0000000..1a741b3 --- /dev/null +++ b/Source/Public/GYPaywall.h @@ -0,0 +1,28 @@ +// +// GYPaywall.h +// Glassfy +// +// Created by Luca Garbolino on 27/06/22. +// + +#import +#if __has_include() +#import +#else +#import "GYTypes.h" +#endif +NS_ASSUME_NONNULL_BEGIN + +#if TARGET_OS_IPHONE +@class GYPaywallViewController; +#endif + +NS_SWIFT_NAME(Glassfy.Paywall) +@interface GYPaywall : NSObject +#if TARGET_OS_IPHONE +- (void)setContentAvailableHandler:(GYPaywallViewControllerCompletion)handler; +- (GYPaywallViewController *)viewController; +#endif +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Public/GYTypes.h b/Source/Public/GYTypes.h index 82d43de..0ba87d7 100644 --- a/Source/Public/GYTypes.h +++ b/Source/Public/GYTypes.h @@ -9,6 +9,8 @@ #define Types_h #import + +NS_ASSUME_NONNULL_BEGIN @class SKPaymentTransaction; @class GYTransaction; @class GYPermissions; @@ -17,10 +19,9 @@ @class GYStoresInfo; @class GYSku; @class GYSkuBase; -@class GYPaywallViewController; +@class GYPaywall; @class GYPurchasesHistory; - -NS_ASSUME_NONNULL_BEGIN +@class GYPaywallViewController; typedef void(^GYSkuBlock)(GYSku* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.SkuBlock); typedef void(^GYSkuBaseCompletion)(GYSkuBase* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.SkuBaseCompletion); @@ -31,13 +32,18 @@ typedef void(^GYBooleanCompletion)(BOOL, NSError* _Nullable) NS_SWIFT_NAME(Glass typedef void(^GYErrorCompletion)(NSError* _Nullable) NS_SWIFT_NAME(Glassfy.ErrorCompletion); typedef void(^GYUserPropertiesCompletion)(GYUserProperties* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.UserPropertiesCompletion); typedef void(^GYStoreCompletion)(GYStoresInfo* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.StoreCompletion); -typedef void(^GYPaywallCompletion)(GYPaywallViewController* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.PaywallCompletion); +typedef void(^GYPaywallCompletion)(GYPaywall* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.PaywallCompletion); typedef void(^GYPaywallCloseBlock)(GYTransaction* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.PaywallCloseBlock); typedef void(^GYPaywallLinkBlock)(NSURL*) NS_SWIFT_NAME(Glassfy.PaywallLinkBlock); typedef void(^GYPaywallPurchaseBlock)(GYSku*) NS_SWIFT_NAME(Glassfy.PaywallPurchaseBlock); typedef void(^GYPaywallRestoreBlock)(void) NS_SWIFT_NAME(Glassfy.PaywallRestoreBlock); typedef void(^GYPurchaseHistoryCompletion)(GYPurchasesHistory* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.PurchaseHistoryCompletion); +typedef void(^GYPaywallViewControllerCompletion)(GYPaywallViewController* _Nullable, NSError* _Nullable) NS_SWIFT_NAME(Glassfy.PaywallViewControllerCompletion); +typedef NS_ENUM(NSUInteger, GYPaywallType) { + GYPaywallTypeNoCode, + GYPaywallTypeHTML +}; typedef NS_ENUM(NSInteger, GYSkuEligibility) { GYSkuEligibilityEligible = 1, diff --git a/Source/Public/Glassfy.h b/Source/Public/Glassfy.h index e3db3cd..fd17c09 100644 --- a/Source/Public/Glassfy.h +++ b/Source/Public/Glassfy.h @@ -18,6 +18,7 @@ #import #import #import +#import #import #import #import @@ -39,6 +40,7 @@ #import "GYPermissions.h" #import "GYOfferings.h" #import "GYUserProperties.h" +#import "GYPaywall.h" #import "GYPaywallViewController.h" #import "GYPurchaseHistory.h" #import "GYPurchasesHistory.h" @@ -178,11 +180,11 @@ NS_ASSUME_NONNULL_BEGIN + (void)setLogLevel:(GYLogLevel)level NS_SWIFT_NAME(log(level:)); /** -Save push notification device token + Save push notification device token -@param deviceToken A globally unique token that identifies this device to APNs -@param block Completion block -*/ + @param deviceToken A globally unique token that identifies this device to APNs + @param block Completion block + */ + (void)setDeviceToken:(NSString *_Nullable)deviceToken completion:(GYErrorCompletion)block NS_SWIFT_NAME(setDeviceToken(_:completion:)); /** @@ -194,11 +196,11 @@ Save push notification device token + (void)setEmailUserProperty:(NSString *_Nullable)email completion:(GYErrorCompletion)block NS_SWIFT_NAME(setUserProperty(email:completion:)); /** -Save extra user properties + Save extra user properties -@param extra Addional user properties -@param block Completion block -*/ + @param extra Addional user properties + @param block Completion block + */ + (void)setExtraUserProperty:(NSDictionary *_Nullable)extra completion:(GYErrorCompletion)block NS_SWIFT_NAME(setUserProperty(extra:completion:)); /** @@ -216,12 +218,41 @@ Save extra user properties + (void)setPurchaseDelegate:(id _Nullable)delegate; /** - Initialize a ViewController to show paywall + Creates a Paywall object which can be used to load a PaywallViewController at a later time. + Automatically fetches paywall contents. + + @param remoteConfigId Remote config identifier + @param block Completion block + */ ++ (void)paywallWithRemoteConfigurationId:(NSString *)remoteConfigId + completion:(GYPaywallCompletion)block +API_UNAVAILABLE(macos, watchos) +NS_SWIFT_NAME(paywall(remoteConfigurationId:completion:)); + +/** + Fetches a paywall configuration and returns a PaywallViewController. + + @param remoteConfigId Remote config identifier + @param block Completion block + */ ++ (void)paywallViewControllerWithRemoteConfigurationId:(NSString *)remoteConfigId + completion:(GYPaywallViewControllerCompletion)block +API_UNAVAILABLE(macos, watchos) +NS_SWIFT_NAME(paywallViewController(remoteConfigurationId:completion:)); + +/** + Fetches a paywall configuration and returns a PaywallViewController. + If `awaitLoading` is `true`, the completion block is invoked only after the paywall content is ready for being shown. - @param paywallid Paywall identifier + @param remoteConfigId Remote config identifier + @param awaitLoading Wait for the paywall content to load before invoking the completion block. @param block Completion block */ -+ (void)paywallWithId:(NSString *)paywallid completion:(GYPaywallCompletion)block API_UNAVAILABLE(macos, watchos) NS_SWIFT_NAME(paywall(id:completion:)); ++ (void)paywallViewControllerWithRemoteConfigurationId:(NSString *)remoteConfigId + awaitLoading:(BOOL)awaitLoading + completion:(GYPaywallViewControllerCompletion)block +API_UNAVAILABLE(macos, watchos) +NS_SWIFT_NAME(paywallViewController(remoteConfigurationId:awaitLoading:completion:)); /** Connect paddle license key @@ -245,13 +276,13 @@ Save extra user properties completion:(GYErrorCompletion)block NS_SWIFT_NAME(connectPaddle(licenseKey:force:completion:)); /** -Connect Glassfy Universal Code + Connect Glassfy Universal Code @param universalCode Glassfy Universal Code @param force Disconnect the code from other subscriber(s) and connect with current subscriber @param block Completion block @note Check error code in GYDomain - GYErrorCodeUniversalCodeAlreadyConnected, GYErrorCodeUniversalCodeNotFound to handle those cases -*/ + */ + (void)connectGlassfyUniversalCode:(NSString *)universalCode force:(BOOL)force withCompletion:(GYErrorCompletion)block NS_SWIFT_NAME(connectGlassfy(universalCode:force:completion:)); @@ -272,35 +303,40 @@ Connect Glassfy Universal Code + (void)storeInfo:(GYStoreCompletion)block NS_SWIFT_NAME(storeInfo(completion:)); /** -Set attribution values + Set attribution values -@param type Attribution identifier -@param value Attribution value -@param block Completion block + @param type Attribution identifier + @param value Attribution value + @param block Completion block */ + (void)setAttributionWithType:(GYAttributionType)type value:(NSString *_Nullable)value completion:(GYErrorCompletion)block NS_SWIFT_NAME(setAttribution(type:value:completion:)); /** -Set attribution values + Set attribution values -@param attributions Array of AttributionItem -@param block Completion block -*/ + @param attributions Array of AttributionItem + @param block Completion block + */ + (void)setAttributions:(NSArray *)attributions completion:(GYErrorCompletion)block NS_SWIFT_NAME(setAttributions(_:completion:)); /** -Purchase history + Purchase history -@param block Completion block -*/ + @param block Completion block + */ + (void)purchaseHistoryWithCompletion:(GYPurchaseHistoryCompletion)block; /// Deprecations /** -@warning Deprecated in favour of `+skuWithId:completion:` -*/ -+ (void)skuWithIdentifier:(NSString *)skuid completion:(GYSkuBlock)block NS_SWIFT_NAME(sku(identifier:completion:)) __attribute__((deprecated("Renamed to +skuWithId:completion:"))); + @warning Deprecated in favour of `+paywallViewControllerWithRemoteConfigurationId:completion:` + */ ++ (void)paywallWithId:(NSString *)paywallid completion:(GYPaywallViewControllerCompletion)block API_UNAVAILABLE(macos, watchos) NS_SWIFT_NAME(paywall(id:completion:)) __attribute__((deprecated("Renamed to +paywallViewController(remoteConfigurationId:awaitLoading:completion:)"))); + +/** + @warning Deprecated in favour of `+skuWithId:completion:` + */ ++ (void)skuWithIdentifier:(NSString *)skuid completion:(GYSkuBlock)block NS_SWIFT_NAME(sku(identifier:completion:)) __attribute__((deprecated("Renamed to +sku(id:completion:)"))); /** @warning Deprecated in favour of `+connectCustomSubscriber:completion:`