├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ ├── bug_template.md │ └── feature_template.md ├── actions │ ├── build_and_test │ │ └── action.yml │ └── upload_test_coverage_report │ │ └── action.yml ├── dependabot.yml └── workflows │ ├── danger.yml │ ├── flare.yml │ ├── flare_ui.yml │ ├── publish-pages.yml │ └── swiftlint.yml ├── .gitignore ├── .swiftformat ├── .swiftlint.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ ├── Flare-Package.xcscheme │ ├── Flare.xcscheme │ ├── FlareTests.xcscheme │ ├── FlareUI.xcscheme │ └── FlareUITests.xcscheme ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dangerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── Mintfile ├── Package.swift ├── Package@swift-5.10.swift ├── Package@swift-5.7.swift ├── Package@swift-5.8.swift ├── Package@swift-5.9.swift ├── README.md ├── Resources └── flare.png ├── SECURITY.md ├── Sources ├── Flare │ ├── Classes │ │ ├── Common │ │ │ ├── Logger.swift │ │ │ └── Types.swift │ │ ├── DI │ │ │ ├── FlareDependencies.swift │ │ │ └── IFlareDependencies.swift │ │ ├── Extensions │ │ │ ├── Bundle+IAppStoreReceiptProvider.swift │ │ │ ├── FileManager+IFileManager.swift │ │ │ ├── Formatters │ │ │ │ └── NumberFormatter+.swift │ │ │ ├── Locale │ │ │ │ └── Locale+CurrencyCode.swift │ │ │ ├── Product.SubscriptionInfo.Status+ISubscriptionInfoStatus.swift │ │ │ ├── ProductType+.swift │ │ │ ├── SKProduct+.swift │ │ │ ├── SKRequest+IReceiptRefreshRequest.swift │ │ │ └── SKRequest+Identifier.swift │ │ ├── Flare.swift │ │ ├── Foundation │ │ │ └── UserDefaults │ │ │ │ ├── IUserDefaults.swift │ │ │ │ └── UserDefaults.swift │ │ ├── Generated │ │ │ └── Strings.swift │ │ ├── Helpers │ │ │ ├── Async │ │ │ │ └── AsyncHandler.swift │ │ │ ├── FileManager │ │ │ │ └── IFileManager.swift │ │ │ ├── PaymentQueue │ │ │ │ ├── PaymentQueue.swift │ │ │ │ └── SKPaymentQueue+PaymentQueue.swift │ │ │ ├── PaymentTransaction │ │ │ │ └── PaymentTransaction.swift │ │ │ ├── ProcessInfo │ │ │ │ └── ProcessInfo+.swift │ │ │ └── ScenesHolder │ │ │ │ └── IScenesHolder.swift │ │ ├── IFlare.swift │ │ ├── Listeners │ │ │ └── TransactionListener │ │ │ │ ├── ITransactionListener.swift │ │ │ │ ├── TransactionListener.swift │ │ │ │ └── TransactionListenerDelegate.swift │ │ ├── Models │ │ │ ├── Configuration.swift │ │ │ ├── DiscountType.swift │ │ │ ├── ExpirationReason.swift │ │ │ ├── FetchCachePolicy.swift │ │ │ ├── IAPError.swift │ │ │ ├── Internal │ │ │ │ ├── ProductsRequest.swift │ │ │ │ ├── Protocols │ │ │ │ │ ├── IRenewalInfo.swift │ │ │ │ │ ├── ISKProduct.swift │ │ │ │ │ ├── ISKRequest.swift │ │ │ │ │ ├── IStorePayment.swift │ │ │ │ │ ├── IStoreProductDiscount.swift │ │ │ │ │ ├── IStoreTransaction.swift │ │ │ │ │ ├── ISubscriptionInfo.swift │ │ │ │ │ └── ISubscriptionInfoStatus.swift │ │ │ │ ├── SK1StorePayment.swift │ │ │ │ ├── SK1StoreProduct.swift │ │ │ │ ├── SK1StoreProductDiscount.swift │ │ │ │ ├── SK1StoreTransaction.swift │ │ │ │ ├── SK2RenewalInfo.swift │ │ │ │ ├── SK2StoreProduct.swift │ │ │ │ ├── SK2StoreProductDiscount.swift │ │ │ │ ├── SK2StoreTransaction.swift │ │ │ │ ├── SK2SubscriptionInfo.swift │ │ │ │ ├── SK2SubscriptionInfoStatus.swift │ │ │ │ └── StoreEnvironment.swift │ │ │ ├── PaymentMode.swift │ │ │ ├── PriceIncreaseStatus.swift │ │ │ ├── ProductCategory.swift │ │ │ ├── ProductType.swift │ │ │ ├── PromotionalOffer.swift │ │ │ ├── RefundError.swift │ │ │ ├── RefundRequestStatus.swift │ │ │ ├── RenewalInfo.swift │ │ │ ├── RenewalState.swift │ │ │ ├── StoreProduct.swift │ │ │ ├── StoreProductDiscount.swift │ │ │ ├── StoreTransaction.swift │ │ │ ├── SubscriptionEligibility.swift │ │ │ ├── SubscriptionInfo.swift │ │ │ ├── SubscriptionInfoStatus.swift │ │ │ ├── SubscriptionPeriod.swift │ │ │ ├── VerificationError.swift │ │ │ └── VerificationResult.swift │ │ └── Providers │ │ │ ├── AppStoreReceiptProvider │ │ │ └── IAppStoreReceiptProvider.swift │ │ │ ├── CacheProvider │ │ │ ├── CacheProvider.swift │ │ │ └── ICacheProvider.swift │ │ │ ├── ConfigurationProvider │ │ │ ├── ConfigurationProvider.swift │ │ │ └── IConfigurationProvider.swift │ │ │ ├── EligibilityProvider │ │ │ ├── EligibilityProvider.swift │ │ │ └── IEligibilityProvider.swift │ │ │ ├── IAPProvider │ │ │ ├── IAPProvider.swift │ │ │ └── IIAPProvider.swift │ │ │ ├── PaymentProvider │ │ │ ├── IPaymentProvider.swift │ │ │ └── PaymentProvider.swift │ │ │ ├── ProductProvider │ │ │ ├── Decorators │ │ │ │ ├── CachingProductsProviderDecorator │ │ │ │ │ ├── CachingProductsProviderDecorator.swift │ │ │ │ │ └── ICachingProductsProviderDecorator.swift │ │ │ │ └── SortingProductsProviderDecorator │ │ │ │ │ ├── ISortingProductsProviderDecorator.swift │ │ │ │ │ └── SortingProductsProviderDecorator.swift │ │ │ ├── IProductProvider.swift │ │ │ └── ProductProvider.swift │ │ │ ├── PurchaseProvider │ │ │ ├── IPurchaseProvider.swift │ │ │ └── PurchaseProvider.swift │ │ │ ├── ReceiptRefreshProvider │ │ │ ├── Factories │ │ │ │ ├── IReceiptRefreshRequest.swift │ │ │ │ └── ReceiptRefreshRequestFactory │ │ │ │ │ ├── IReceiptRefreshRequestFactory.swift │ │ │ │ │ └── ReceiptRefreshRequestFactory.swift │ │ │ ├── IReceiptRefreshProvider.swift │ │ │ └── ReceiptRefreshProvider.swift │ │ │ ├── RedeemCodeProvider │ │ │ ├── IRedeemCodeProvider.swift │ │ │ └── RedeemCodeProvider.swift │ │ │ ├── RefundProvider │ │ │ ├── IRefundProvider.swift │ │ │ └── RefundProvider.swift │ │ │ ├── RefundRequestProvider │ │ │ ├── IRefundRequestProvider.swift │ │ │ └── RefundRequestProvider.swift │ │ │ └── SystemInfoProvider │ │ │ ├── ISystemInfoProvider.swift │ │ │ └── SystemInfoProvider.swift │ ├── Flare.docc │ │ ├── Articles │ │ │ ├── caching.md │ │ │ ├── logging.md │ │ │ ├── perform-purchase.md │ │ │ ├── promotional-offers.md │ │ │ ├── refund-purchase.md │ │ │ └── restore-purchase.md │ │ └── Flare.md │ ├── Makefile │ ├── Resources │ │ └── Localizable.strings │ └── swiftgen.yml ├── FlareMock │ ├── Fakes │ │ ├── StoreProduct+Fake.swift │ │ └── StoreTransaction+Fake.swift │ └── Mocks │ │ ├── PaymentTransactionMock.swift │ │ └── ProductMock.swift ├── FlareUI │ ├── Classes │ │ ├── Core │ │ │ ├── EnvironmentKey │ │ │ │ ├── AnyProductStyle.swift │ │ │ │ ├── AnySubscriptionControlStyle.swift │ │ │ │ ├── Assemblies │ │ │ │ │ ├── ProductAssemblyKey.swift │ │ │ │ │ └── StoreButtonsAssemblyKey.swift │ │ │ │ ├── BlurEffectStyleKey.swift │ │ │ │ ├── PoliciesButtonStyleKey.swift │ │ │ │ ├── ProductStyleKey.swift │ │ │ │ ├── PurchaseCompletionKey.swift │ │ │ │ ├── PurchaseOptionKey.swift │ │ │ │ ├── StoreButtonKey.swift │ │ │ │ ├── StoreButtonViewFontWeightKey.swift │ │ │ │ ├── SubscriptionBackgroundKey.swift │ │ │ │ ├── SubscriptionControlStyleKey.swift │ │ │ │ ├── SubscriptionHeaderContentBackgroundKey.swift │ │ │ │ ├── SubscriptionMarketingContentKey.swift │ │ │ │ ├── SubscriptionPickerItemBackgroundKey.swift │ │ │ │ ├── SubscriptionPrivacyPolicyDestinationKey.swift │ │ │ │ ├── SubscriptionPrivacyPolicyURLKey.swift │ │ │ │ ├── SubscriptionStoreButtonLabelKey.swift │ │ │ │ ├── SubscriptionTermsOfServiceDestinationKey.swift │ │ │ │ ├── SubscriptionTermsOfServiceURLKey.swift │ │ │ │ ├── SubscriptionViewTintKey.swift │ │ │ │ ├── SubscriptionsWrapperViewStyleKey.swift │ │ │ │ └── TintColorKey.swift │ │ │ ├── Extensions │ │ │ │ ├── Array+RemoveDuplicates.swift │ │ │ │ ├── String+SubSequence.swift │ │ │ │ ├── StringProtocol+Words.swift │ │ │ │ └── View+EraseToAnyView.swift │ │ │ ├── Formatters │ │ │ │ ├── DateComponentsFormatter+Full.swift │ │ │ │ └── IDateComponentsFormatter.swift │ │ │ ├── Helpers │ │ │ │ ├── Array+StoreProduct.swift │ │ │ │ ├── Color+UIColor.swift │ │ │ │ ├── Error+IAP.swift │ │ │ │ └── Value.swift │ │ │ ├── Models │ │ │ │ ├── Internal │ │ │ │ │ ├── PriceDisplayFormat.swift │ │ │ │ │ └── ProductStyle.swift │ │ │ │ ├── PaywallType.swift │ │ │ │ ├── PurchaseOptions.swift │ │ │ │ ├── SubscriptionStatusVerifierType.swift │ │ │ │ ├── SubscriptionStoreButtonLabel.swift │ │ │ │ └── UIConfiguration.swift │ │ │ ├── Providers │ │ │ │ ├── ConfigurationProvider │ │ │ │ │ ├── ConfigurationProvider.swift │ │ │ │ │ └── IConfigurationProvider.swift │ │ │ │ ├── SubscriptionStatusProvider │ │ │ │ │ ├── ISubscriptionStatusVerifierProvider.swift │ │ │ │ │ └── SubscriptionStatusVerifierProvider.swift │ │ │ │ └── SubscriptionStatusVerifier │ │ │ │ │ ├── ISubscriptionStatusVerifier.swift │ │ │ │ │ └── SubscriptionStatusVerifier.swift │ │ │ └── Resolvers │ │ │ │ └── SubscriptionStatusVerifierTypeResolver │ │ │ │ ├── ISubscriptionStatusVerifierTypeResolver.swift │ │ │ │ └── SubscriptionStatusVerifierTypeResolver.swift │ │ ├── DI │ │ │ ├── Dependencies │ │ │ │ ├── FlareDependencies.swift │ │ │ │ └── IFlareDependencies.swift │ │ │ └── PresentationAssembly │ │ │ │ ├── IPresentationAssembly.swift │ │ │ │ └── PresentationAssembly.swift │ │ ├── FlareUI.swift │ │ ├── Generated │ │ │ ├── Colors.swift │ │ │ ├── Media.swift │ │ │ └── Strings.swift │ │ ├── IFlareUI.swift │ │ └── Presentation │ │ │ ├── Components │ │ │ ├── Controllers │ │ │ │ ├── BaseHostingController │ │ │ │ │ └── BaseHostingController.swift │ │ │ │ ├── Helpers │ │ │ │ │ ├── ColorRepresentation.swift │ │ │ │ │ └── SUIViewWrapper.swift │ │ │ │ ├── ProductViewController │ │ │ │ │ ├── ProductViewController.swift │ │ │ │ │ └── ProductViewControllerViewModel.swift │ │ │ │ ├── ProductsViewController │ │ │ │ │ ├── ProductsViewController.swift │ │ │ │ │ └── ProductsViewControllerViewModel.swift │ │ │ │ ├── SubscriptionsViewController │ │ │ │ │ ├── SubscriptionsViewController.swift │ │ │ │ │ └── SubscriptionsViewControllerViewModel.swift │ │ │ │ └── ViewController │ │ │ │ │ ├── HostingController.swift │ │ │ │ │ └── ViewController.swift │ │ │ ├── Core │ │ │ │ ├── Constants │ │ │ │ │ ├── Palette.swift │ │ │ │ │ └── UIConstants.swift │ │ │ │ ├── Models │ │ │ │ │ ├── StoreButtonType.swift │ │ │ │ │ └── StoreButtonVisibility.swift │ │ │ │ └── Protocols │ │ │ │ │ ├── IModel.swift │ │ │ │ │ └── IPresenter.swift │ │ │ ├── Factories │ │ │ │ ├── ISubscriptionPriceViewModelFactory.swift │ │ │ │ └── SubscriptionPriceViewModelFactory.swift │ │ │ ├── Helpers │ │ │ │ ├── SUI │ │ │ │ │ ├── View+Contrast.swift │ │ │ │ │ ├── View+Paywall.swift │ │ │ │ │ ├── View+ProductViewStyle.swift │ │ │ │ │ ├── View+PurchaseCompletion.swift │ │ │ │ │ ├── View+PurchaseOption.swift │ │ │ │ │ ├── View+StoreButton.swift │ │ │ │ │ ├── View+StoreButtonViewFontWeight.swift │ │ │ │ │ ├── View+SubscriptionBackground.swift │ │ │ │ │ ├── View+SubscriptionControlStyle.swift │ │ │ │ │ ├── View+SubscriptionHeaderContentBackground.swift │ │ │ │ │ ├── View+SubscriptionMarketingContent.swift │ │ │ │ │ ├── View+SubscriptionPickerItemBackground.swift │ │ │ │ │ ├── View+SubscriptionPrivacyPolicyDestination.swift │ │ │ │ │ ├── View+SubscriptionPrivacyPolicyURL.swift │ │ │ │ │ ├── View+SubscriptionStoreButtonLabel.swift │ │ │ │ │ ├── View+SubscriptionTermsOfServiceDestination.swift │ │ │ │ │ ├── View+SubscriptionTermsOfServiceURL.swift │ │ │ │ │ ├── View+SubscriptionViewTint.swift │ │ │ │ │ └── View+TintColor.swift │ │ │ │ └── UIKit │ │ │ │ │ └── ViewController+Child.swift │ │ │ ├── Styles │ │ │ │ ├── BorderedButtonStyle.swift │ │ │ │ ├── PrimaryButtonStyle.swift │ │ │ │ ├── Product │ │ │ │ │ ├── CompactProductStyle.swift │ │ │ │ │ ├── Configuration │ │ │ │ │ │ └── ProductStyleConfiguration.swift │ │ │ │ │ ├── LargeProductStyle.swift │ │ │ │ │ └── Protocols │ │ │ │ │ │ ├── IProductStyle+Compact.swift │ │ │ │ │ │ ├── IProductStyle+Large.swift │ │ │ │ │ │ └── IProductStyle.swift │ │ │ │ └── Subscription │ │ │ │ │ ├── Configuration │ │ │ │ │ └── SubscriptionStoreControlStyleConfiguration.swift │ │ │ │ │ ├── Extensions │ │ │ │ │ ├── ISubscriptionControlStyle+Bordered.swift │ │ │ │ │ ├── ISubscriptionControlStyle+PickerSubscriptionStoreControlStyle+PickerSubscriptionStoreControlStyle.swift │ │ │ │ │ └── ISubscriptionControlStyle+ProminentPickerSubscriptionStoreControlStyle.swift │ │ │ │ │ ├── Protocols │ │ │ │ │ └── ISubscriptionControlStyle.swift │ │ │ │ │ └── SubscriptionStoreControlStyle │ │ │ │ │ ├── AutomaticSubscriptionControlStyle.swift │ │ │ │ │ ├── BorderedSubscriptionStoreControlStyle │ │ │ │ │ ├── BorderedSubscriptionStoreControlStyle.swift │ │ │ │ │ └── BorderedSubscriptionStoreControlStyleView.swift │ │ │ │ │ ├── ButtonSubscriptionStoreControlStyle │ │ │ │ │ └── ButtonSubscriptionStoreControlStyle.swift │ │ │ │ │ ├── CardButtonSubscriptionStoreControlStyle │ │ │ │ │ ├── CardButtonSubscriptionStoreControlStyle.swift │ │ │ │ │ └── CardButtonSubscriptionStoreControlView.swift │ │ │ │ │ ├── PickerSubscriptionStoreControlStyle │ │ │ │ │ ├── PickerSubscriptionStoreControlStyle.swift │ │ │ │ │ └── PickerSubscriptionStoreControlStyleView.swift │ │ │ │ │ └── ProminentPickerSubscriptionStoreControlStyle │ │ │ │ │ ├── ProminentPickerSubscriptionStoreControlStyle.swift │ │ │ │ │ └── ProminentPickerSubscriptionStoreControlStyleView.swift │ │ │ ├── ViewModifiers │ │ │ │ ├── ActivityIndicatorModifier.swift │ │ │ │ ├── BlurEffectModifier.swift │ │ │ │ ├── ErrorAlertViewModifier.swift │ │ │ │ ├── LoadViewModifier.swift │ │ │ │ └── PaywallViewModifier.swift │ │ │ └── Views │ │ │ │ ├── ActivityIndicator │ │ │ │ └── ActivityIndicatorView.swift │ │ │ │ ├── BlurVisualEffectView │ │ │ │ └── BlurVisualEffectView.swift │ │ │ │ ├── ImageView │ │ │ │ └── ImageView.swift │ │ │ │ ├── ProductPlaceholderView │ │ │ │ └── ProductPlaceholderView.swift │ │ │ │ └── SafariWebView │ │ │ │ └── SafariWebView.swift │ │ │ ├── Helpers │ │ │ ├── ViewWrapper.swift │ │ │ └── WrapperViewModel.swift │ │ │ └── Views │ │ │ ├── PaywallView │ │ │ └── PaywallView.swift │ │ │ ├── PoliciesButtonAssembly │ │ │ ├── PoliciesButtonAssembly.swift │ │ │ ├── PoliciesButtonView.swift │ │ │ ├── Styles │ │ │ │ ├── AnyPoliciesButtonStyle.swift │ │ │ │ ├── AutomaticPoliciesButtonStyle │ │ │ │ │ └── AutomaticPoliciesButtonStyle.swift │ │ │ │ ├── Configuration │ │ │ │ │ └── PoliciesButtonStyleConfiguration.swift │ │ │ │ ├── DefaultPoliciesButtonStyle │ │ │ │ │ ├── DefaultPoliciesButtonStyle.swift │ │ │ │ │ └── DefaultPoliciesButtonStyleView.swift │ │ │ │ ├── IPoliciesButtonStyle.swift │ │ │ │ └── TVPoliciesButtonStyle │ │ │ │ │ └── TVPoliciesButtonStyle.swift │ │ │ └── Views │ │ │ │ └── PoliciesUnavailableView.swift │ │ │ ├── ProductView │ │ │ ├── ProductPresenter.swift │ │ │ ├── ProductPurchaseService.swift │ │ │ ├── ProductView.swift │ │ │ ├── ProductViewAssembly.swift │ │ │ ├── ProductViewModel.swift │ │ │ ├── ProductViewModelFactory.swift │ │ │ ├── ProductViewType.swift │ │ │ ├── ProductWrapperView.swift │ │ │ ├── Strategies │ │ │ │ └── ProductStrategy.swift │ │ │ ├── SubscriptionDateComponentsFactory.swift │ │ │ └── Views │ │ │ │ └── ProductInfoView │ │ │ │ └── ProductInfoView.swift │ │ │ ├── ProductsView │ │ │ ├── ProductsPresenter.swift │ │ │ ├── ProductsView.swift │ │ │ ├── ProductsViewAssembly.swift │ │ │ ├── ProductsViewModel.swift │ │ │ ├── ProductsWrapperView.swift │ │ │ └── Views │ │ │ │ └── StoreUnavaliableView.swift │ │ │ ├── StoreButtonView │ │ │ ├── StoreButton.swift │ │ │ ├── StoreButtonAssembly.swift │ │ │ ├── StoreButtonPresenter.swift │ │ │ ├── StoreButtonView.swift │ │ │ └── StoreButtonViewModel.swift │ │ │ ├── StoreButtonsView │ │ │ └── StoreButtonsAssembly.swift │ │ │ └── SubscriptionsView │ │ │ ├── Styles │ │ │ ├── AnySubscriptionsWrapperViewStyle.swift │ │ │ └── SubscriptionsWrapperViewStyle │ │ │ │ ├── Configuration │ │ │ │ └── SubscriptionsWrapperViewStyleConfiguration.swift │ │ │ │ ├── ISubscriptionsWrapperViewStyle.swift │ │ │ │ └── Styles │ │ │ │ ├── Automatic │ │ │ │ └── AutomaticSubscriptionsWrapperViewStyle.swift │ │ │ │ ├── Compact │ │ │ │ ├── CompactSubscriptionWrapperView.swift │ │ │ │ └── CompactSubscriptionWrapperViewStyle.swift │ │ │ │ └── Full │ │ │ │ ├── FullSubscriptionsWrapperView.swift │ │ │ │ └── FullSubscriptionsWrapperViewStyle.swift │ │ │ ├── SubscriptionsAssembly.swift │ │ │ ├── SubscriptionsPresenter.swift │ │ │ ├── SubscriptionsView.swift │ │ │ ├── SubscriptionsViewModel.swift │ │ │ ├── SubscriptionsViewModelViewFactory.swift │ │ │ ├── SubscriptionsWrapperView.swift │ │ │ └── Views │ │ │ ├── LoadingView.swift │ │ │ ├── SubscriptionHeaderView.swift │ │ │ ├── SubscriptionToolbarView.swift │ │ │ └── SubscriptionView.swift │ ├── FlareUI.docc │ │ ├── Articles │ │ │ ├── creating-custom-product-style.md │ │ │ ├── displaying-products.md │ │ │ ├── displaying-subscriptions.md │ │ │ └── handling-transactions.md │ │ ├── FlareUI.md │ │ └── Resources │ │ │ └── Images │ │ │ ├── button_styles.png │ │ │ ├── content_buttons.png │ │ │ ├── subscription_view.png │ │ │ └── subscription_view@2x.png │ ├── Makefile │ ├── Resources │ │ ├── Assets │ │ │ ├── Assets.xcassets │ │ │ │ ├── Colors │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── dynamic_background.colorset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── gray.colorset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── system_background.colorset │ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Media.xcassets │ │ │ │ ├── Contents.json │ │ │ │ └── Media │ │ │ │ ├── Contents.json │ │ │ │ ├── checkmark.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── checkmark.circle.fill.svg │ │ │ │ ├── circle.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── circle.svg │ │ │ │ └── star.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── star.svg │ │ └── Localization │ │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ │ └── ru.lproj │ │ │ └── Localizable.strings │ └── swiftgen.yml └── FlareUIMock │ └── Mocks │ ├── FlareMock.swift │ ├── ProductPresenterMock.swift │ ├── ProductViewAssemblyMock.swift │ ├── ProductsPresenterMock.swift │ ├── StoreButtonAssemblyMock.swift │ ├── StoreButtonsAssemblyMock.swift │ └── SubscriptionsPresenterMock.swift ├── Tests ├── FlareTests │ └── UnitTests │ │ ├── Factories │ │ └── ReceiptRefreshFactoryTests.swift │ │ ├── FlareTests.swift │ │ ├── Helpers │ │ └── ProcessInfoTests.swift │ │ ├── Models │ │ ├── IAPErrorTests.swift │ │ ├── PaymentTransactionTests.swift │ │ ├── PromotionalOfferTests.swift │ │ └── SKProductTests.swift │ │ ├── Providers │ │ ├── CachingProductsProviderDecoratorTests.swift │ │ ├── ConfigurationProviderTests.swift │ │ ├── IAPProviderTests.swift │ │ ├── PaymentProviderTests.swift │ │ ├── ProductProviderTests.swift │ │ ├── PurchaseProviderTests.swift │ │ ├── ReceiptRefreshProviderTests.swift │ │ ├── RefundProviderTests.swift │ │ ├── RefundRequestProviderTests.swift │ │ ├── SortingProductsProviderDecoratorTests.swift │ │ └── SystemInfoProviderTests.swift │ │ └── TestHelpers │ │ ├── Extensions │ │ ├── Result+.swift │ │ ├── String+Data.swift │ │ └── XCTestCase+.swift │ │ ├── Fakes │ │ ├── Configuration+Fake.swift │ │ ├── SKProduct+Fake.swift │ │ └── StoreTransactionFake.swift │ │ ├── Helpers │ │ ├── AvailabilityChecker.swift │ │ ├── PurchaseManagerTestHelper.swift │ │ └── WindowSceneFactory.swift │ │ ├── Mocks │ │ ├── AppStoreReceiptProviderMock.swift │ │ ├── CacheProviderMock.swift │ │ ├── CacheProviderTests.swift │ │ ├── ConfigurationProviderMock.swift │ │ ├── EligibilityProviderMock.swift │ │ ├── FileManagerMock.swift │ │ ├── FlareDependenciesMock.swift │ │ ├── IAPProviderMock.swift │ │ ├── PaymentProviderMock.swift │ │ ├── PaymentQueueMock.swift │ │ ├── ProductProviderMock.swift │ │ ├── ProductResponseMock.swift │ │ ├── ProductsRequestMock.swift │ │ ├── PurchaseProviderMock.swift │ │ ├── ReceiptRefreshProviderMock.swift │ │ ├── ReceiptRefreshRequestFactory.swift │ │ ├── ReceiptRefreshRequestMock.swift │ │ ├── RedeemCodeProvider.swift │ │ ├── RefundProviderMock.swift │ │ ├── RefundRequestProviderMock.swift │ │ ├── SKProductMock.swift │ │ ├── ScenesHolderMock.swift │ │ ├── StoreTransactionMock.swift │ │ ├── SystemInfoProviderMock.swift │ │ └── UserDefaultsMock.swift │ │ └── Stubs │ │ └── StoreTransactionStub.swift ├── FlareUITests │ └── UnitTests │ │ ├── Core │ │ ├── Extensions │ │ │ └── ArrayExtensionsTests.swift │ │ └── SubscriptionPriceViewModelFactoryTests.swift │ │ ├── Fakes │ │ └── SubscriptionView.ViewModel+Fake.swift │ │ ├── Helpers │ │ ├── XCTestCase+.swift │ │ └── XCTestCase+Wait.swift │ │ ├── Mocks │ │ ├── DateComponentsFormatterMock.swift │ │ ├── ProductFetcherMock.swift │ │ ├── ProductPurchaseServiceMock.swift │ │ ├── SubscriptionDateComponentsFactoryMock.swift │ │ ├── SubscriptionPriceViewModelFactoryMock.swift │ │ └── SubscriptionsViewModelViewFactoryMock.swift │ │ └── Presentation │ │ ├── Product │ │ ├── ProductPresenterTests.swift │ │ ├── ProductStrategyTests.swift │ │ ├── ProductViewModelFactoryTests.swift │ │ └── SubscriptionDateComponentsFactoryTests.swift │ │ ├── Products │ │ └── ProductsPresenterTests.swift │ │ ├── StoreButton │ │ └── StoreButtonPresenterTests.swift │ │ └── Subscriptions │ │ └── SubscriptionsPresenterTests.swift ├── IntegrationTests │ ├── Flare.storekit │ ├── Helpers │ │ ├── Extensions │ │ │ ├── AsyncSequence+.swift │ │ │ ├── Result+.swift │ │ │ └── XCTestCase+.swift │ │ ├── Providers │ │ │ └── ProductProviderHelper.swift │ │ └── StoreSessionTestCase │ │ │ └── StoreSessionTestCase.swift │ └── Tests │ │ ├── EligibilityProviderTests.swift │ │ ├── FlareTests.swift │ │ └── StoreProductTests.swift ├── SnapshotTests │ ├── Helpers │ │ ├── SnapshotTestCase.swift │ │ └── ThemableView.swift │ ├── ProductInfoViewSnapshotTests.swift │ ├── ProductPlaceholderViewSnapshotTests.swift │ ├── ProductViewSnapshotTests.swift │ ├── ProductsViewSnapshotTests.swift │ ├── SubscriptionsViewSnapshotTests.swift │ └── __Snapshots__ │ │ ├── ProductInfoViewSnapshotTests │ │ ├── test_productInfoView_compactStyle_whenIconIsNil-iOS.1.png │ │ ├── test_productInfoView_compactStyle_whenIconIsNil-macOS.1.png │ │ ├── test_productInfoView_compactStyle_whenIconIsNil-tvOS.1.png │ │ ├── test_productInfoView_compactStyle_whenIconIsNotNil-iOS.1.png │ │ ├── test_productInfoView_compactStyle_whenIconIsNotNil-macOS.1.png │ │ ├── test_productInfoView_compactStyle_whenIconIsNotNil-tvOS.1.png │ │ ├── test_productInfoView_largeStyle_whenIconIsNil-iOS.1.png │ │ └── test_productInfoView_largeStyle_whenIconIsNotNil-iOS.1.png │ │ ├── ProductPlaceholderViewSnapshotTests │ │ ├── test_productPlaceholderView_compactStyle_whenIconIsHidden-iOS.1.png │ │ ├── test_productPlaceholderView_compactStyle_whenIconIsHidden-macOS.1.png │ │ ├── test_productPlaceholderView_compactStyle_whenIconIsHidden-tvOS.1.png │ │ ├── test_productPlaceholderView_compactStyle_whenIconIsVisible-iOS.1.png │ │ ├── test_productPlaceholderView_compactStyle_whenIconIsVisible-macOS.1.png │ │ ├── test_productPlaceholderView_compactStyle_whenIconIsVisible-tvOS.1.png │ │ ├── test_productPlaceholderView_largeStyle_whenIconIsHidden-iOS.1.png │ │ ├── test_productPlaceholderView_largeStyle_whenIconIsVisible-iOS.1.png │ │ ├── test_productPlaceholderView_whenIconIsHidden-macOS.1.png │ │ ├── test_productPlaceholderView_whenIconIsHidden-tvOS.1.png │ │ ├── test_productPlaceholderView_whenIconIsVisible-macOS.1.png │ │ └── test_productPlaceholderView_whenIconIsVisible-tvOS.1.png │ │ ├── ProductViewSnapshotTests │ │ ├── test_productView_customStyle_product-iOS.1.png │ │ ├── test_productView_customStyle_product-macOS.1.png │ │ ├── test_productView_customStyle_product-tvOS.1.png │ │ ├── test_productView_error-iOS.1.png │ │ ├── test_productView_error-macOS.1.png │ │ ├── test_productView_error-tvOS.1.png │ │ ├── test_productView_loading-iOS.1.png │ │ ├── test_productView_loading-macOS.1.png │ │ ├── test_productView_loading-tvOS.1.png │ │ ├── test_productView_product-iOS.1.png │ │ ├── test_productView_product-macOS.1.png │ │ └── test_productView_product-tvOS.1.png │ │ ├── ProductsViewSnapshotTests │ │ ├── test_productsView_error-iOS.1.png │ │ ├── test_productsView_error-macOS.1.png │ │ ├── test_productsView_error-tvOS.1.png │ │ ├── test_productsView_products-iOS.1.png │ │ ├── test_productsView_products-macOS.1.png │ │ ├── test_productsView_products-tvOS.1.png │ │ ├── test_productsView_products_withRestoreButtons-iOS.1.png │ │ ├── test_productsView_products_withRestoreButtons-macOS.1.png │ │ └── test_productsView_products_withRestoreButtons-tvOS.1.png │ │ └── SubscriptionsViewSnapshotTests │ │ ├── test_subscriptionsView_customStyle-iOS.1.png │ │ ├── test_subscriptionsView_customStyle-tvOS.1.png │ │ ├── test_subscriptionsView_defaultStyle-iOS.1.png │ │ └── test_subscriptionsView_defaultStyle-tvOS.1.png ├── TestPlans │ ├── AllTests.xctestplan │ ├── FlareUIUnitTests.xctestplan │ ├── IntegrationTests.xctestplan │ ├── SnapshotTests.xctestplan │ └── UnitTests.xctestplan └── UnitTestHostApp │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ └── Info.plist ├── codecov.yml ├── hooks └── pre-commit ├── project.yml └── scripts └── setup_build_tools.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: Report a reproducible bug or regression. 4 | title: 'Bug: ' 5 | labels: 'bug' 6 | 7 | --- 8 | 9 | 14 | 15 | Application version: 16 | 17 | ## Steps To Reproduce 18 | 19 | 1. 20 | 2. 21 | 22 | 27 | 28 | Link to code example: 29 | 30 | 37 | 38 | ## The current behavior 39 | 40 | 41 | ## The expected behavior -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🛠 Feature request 3 | about: If you have a feature request for the Firebase iOS SDK, file it here. 4 | labels: 'type: enhancement' 5 | --- 6 | 7 | **Feature description** 8 | Clearly and concisely describe the feature. 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/bug_template.md: -------------------------------------------------------------------------------- 1 | ## Bug description 2 | Clearly and concisely describe the problem. 3 | 4 | ## Solution description 5 | Describe your code changes in detail for reviewers. Explain the technical solution you have provided and how it fixes the issue case. 6 | 7 | ## Covered unit test cases 8 | - [x] yes 9 | - [x] no -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/feature_template.md: -------------------------------------------------------------------------------- 1 | ## Feature description 2 | Clearly and concisely describe the feature. 3 | 4 | ## Solution description 5 | Describe your code changes in detail for reviewers. 6 | 7 | ## Areas affected and ensured 8 | List out the areas affected by your code changes. 9 | 10 | ## Covered unit test cases 11 | - [x] yes 12 | - [x] no -------------------------------------------------------------------------------- /.github/actions/upload_test_coverage_report/action.yml: -------------------------------------------------------------------------------- 1 | name: Upload a Test Coverage Report 2 | 3 | inputs: 4 | filename: 5 | required: true 6 | type: string 7 | scheme_name: 8 | required: true 9 | type: string 10 | token: 11 | description: 'A CodeCov Token' 12 | required: true 13 | 14 | runs: 15 | using: "composite" 16 | 17 | steps: 18 | - name: Install dependencies 19 | run: | 20 | brew tap a7ex/homebrew-formulae 21 | brew install xcresultparser 22 | shell: bash 23 | 24 | - name: Convert the test report 25 | run: | 26 | xcresultparser -o cobertura "test_output/${{ inputs.filename }}.xcresult" > cobertura.xml 27 | shell: bash 28 | 29 | - name: Upload coverage reports to Codecov 30 | uses: codecov/codecov-action@v5.1.2 31 | with: 32 | token: ${{ inputs.token }} 33 | flags: ${{ inputs.scheme_name }} 34 | fail_ci_if_error: true 35 | verbose: true 36 | files: ./cobertura.xml 37 | 38 | - name: Upload artifact 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: ${{ inputs.filename }} 42 | path: test_output 43 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: github-actions 9 | directory: / 10 | open-pull-requests-limit: 10 11 | schedule: 12 | interval: daily 13 | time: '07:00' 14 | timezone: Europe/Berlin 15 | assignees: 16 | - nik3212 17 | reviewers: 18 | - nik3212 19 | 20 | - package-ecosystem: swift 21 | directory: / 22 | open-pull-requests-limit: 10 23 | schedule: 24 | interval: daily 25 | time: '07:00' 26 | timezone: Europe/Berlin 27 | assignees: 28 | - nik3212 29 | reviewers: 30 | - nik3212 -------------------------------------------------------------------------------- /.github/workflows/danger.yml: -------------------------------------------------------------------------------- 1 | name: Danger 2 | 3 | on: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled, unlabeled, edited] 6 | 7 | env: 8 | LC_CTYPE: en_US.UTF-8 9 | LANG: en_US.UTF-8 10 | 11 | jobs: 12 | run-danger: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: ruby setup 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 3.1.4 19 | bundler-cache: true 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | - name: Setup gems 23 | run: | 24 | gem install bundler 25 | bundle install --clean --path vendor/bundle 26 | - name: danger 27 | env: 28 | DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} 29 | run: bundle exec danger --verbose -------------------------------------------------------------------------------- /.github/workflows/publish-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy DocC 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: "pages" 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build: 14 | runs-on: macos-14 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | include: 19 | - target: "Flare" 20 | base_path: "flare" 21 | - target: "FlareUI" 22 | base_path: "flareui" 23 | steps: 24 | - name: Checkout 🛎️ 25 | uses: actions/checkout@v4 26 | with: 27 | # Fetch all history for all branches and tags. 28 | fetch-depth: 0 29 | - name: Build and Push Generated Documentation 30 | uses: space-code/oss-common-actions/.github/actions/publish_docc@main 31 | with: 32 | target: ${{ matrix.target }} 33 | output_path: ${{ matrix.base_path }} 34 | hosting_base_path: flare/${{ matrix.base_path }} 35 | -------------------------------------------------------------------------------- /.github/workflows/swiftlint.yml: -------------------------------------------------------------------------------- 1 | name: "swiftlint" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | pull_request: 9 | paths: 10 | - '.swiftlint.yml' 11 | - ".github/workflows/**" 12 | - "Package@swift-5.7.swift" 13 | - "Package@swift-5.8.swift" 14 | - "Package.swift" 15 | - "Source/**" 16 | - "Tests/**" 17 | 18 | jobs: 19 | SwiftLint: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: GitHub Action for SwiftLint 24 | uses: norio-nomura/action-swiftlint@3.2.1 25 | with: 26 | args: lint --config ./.swiftlint.yml --strict -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | danger.import_dangerfile(github: 'space-code/dangerfile') -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'danger' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 space-code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CHILD_MAKEFILES_DIRS = $(sort $(dir $(wildcard Sources/*/Makefile))) 2 | 3 | all: bootstrap 4 | 5 | bootstrap: hook 6 | mint bootstrap 7 | 8 | hook: 9 | ln -sf ../../hooks/pre-commit .git/hooks/pre-commit 10 | chmod +x .git/hooks/pre-commit 11 | 12 | mint: 13 | mint bootstrap 14 | 15 | lint: 16 | mint run swiftlint 17 | 18 | fmt: 19 | mint run swiftformat Sources Tests 20 | 21 | swiftgen: 22 | @for d in $(CHILD_MAKEFILES_DIRS); do ( cd $$d && make swiftgen; ); done 23 | 24 | generate: 25 | xcodegen generate 26 | 27 | setup_build_tools: 28 | sh scripts/setup_build_tools.sh 29 | 30 | build: 31 | swift build -c release --target Flare 32 | 33 | test: 34 | xcodebuild test -scheme "Flare" -destination "$(destination)" -testPlan AllTests clean -enableCodeCoverage YES 35 | 36 | .PHONY: all bootstrap hook mint lint fmt generate setup_build_tools strings build test -------------------------------------------------------------------------------- /Mintfile: -------------------------------------------------------------------------------- 1 | nicklockwood/SwiftFormat@0.54.0 2 | realm/SwiftLint@0.55.1 -------------------------------------------------------------------------------- /Resources/flare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Resources/flare.png -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Vulnerabilities 2 | 3 | This software is built with security and data privacy in mind to ensure your data is safe. We are grateful for security researchers and users reporting a vulnerability to us, first. To ensure that your request is handled in a timely manner and non-disclosure of vulnerabilities can be assured, please follow the below guideline. 4 | 5 | **Please do not report security vulnerabilities directly on GitHub. GitHub Issues can be publicly seen and therefore would result in a direct disclosure.** 6 | 7 | * Please address questions about data privacy, security concepts, and other media requests to the nv3212@gmail.com mailbox. -------------------------------------------------------------------------------- /Sources/Flare/Classes/Common/Types.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public typealias Closure = (T) -> Void 9 | public typealias Closure2 = (T, U) -> Void 10 | 11 | public typealias SendableClosure = @Sendable (T) -> Void 12 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/DI/IFlareDependencies.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// The package's dependencies. 9 | protocol IFlareDependencies { 10 | /// The IAP provider. 11 | var iapProvider: IIAPProvider { get } 12 | /// The configuration provider. 13 | var configurationProvider: IConfigurationProvider { get } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/Bundle+IAppStoreReceiptProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension Bundle: IAppStoreReceiptProvider {} 9 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/FileManager+IFileManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension FileManager: IFileManager {} 9 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/Formatters/NumberFormatter+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension NumberFormatter { 9 | static func numberFormatter(with locale: Locale) -> NumberFormatter { 10 | let formatter = NumberFormatter() 11 | formatter.numberStyle = .currency 12 | formatter.locale = locale 13 | return formatter 14 | } 15 | 16 | func numberFormatter(with currencyCode: String, locale: Locale = .autoupdatingCurrent) -> NumberFormatter { 17 | let formatter = NumberFormatter() 18 | formatter.numberStyle = .currency 19 | formatter.locale = locale 20 | formatter.currencyCode = currencyCode 21 | return formatter 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/Locale/Locale+CurrencyCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension Locale { 9 | var currencyCodeID: String? { 10 | #if swift(>=5.9) 11 | if #available(macOS 13, iOS 16, tvOS 16, watchOS 9, visionOS 1.0, *) { 12 | return self.currency?.identifier 13 | } else { 14 | return currencyCode 15 | } 16 | #else 17 | return currencyCode 18 | #endif 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/Product.SubscriptionInfo.Status+ISubscriptionInfoStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import struct StoreKit.Product 7 | 8 | // MARK: - ISubscriptionInfoStatus 9 | 10 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 11 | extension Product.SubscriptionInfo.Status: ISubscriptionInfoStatus { 12 | var renewalState: RenewalState { 13 | RenewalState(self.state) 14 | } 15 | 16 | var subscriptionRenewalInfo: VerificationResult { 17 | switch self.renewalInfo { 18 | case let .verified(renewalInfo): 19 | return .verified(.init(renewalInfo: renewalInfo)) 20 | case let .unverified(renewalInfo, error): 21 | return .unverified(.init(renewalInfo: renewalInfo), error) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/ProductType+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import StoreKit 8 | 9 | @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) 10 | extension ProductType { 11 | init(_ type: StoreKit.Product.ProductType) { 12 | switch type { 13 | case .consumable: 14 | self = .consumable 15 | case .nonConsumable: 16 | self = .nonConsumable 17 | case .nonRenewable: 18 | self = .nonRenewableSubscription 19 | case .autoRenewable: 20 | self = .autoRenewableSubscription 21 | default: 22 | self = .nonConsumable 23 | } 24 | } 25 | } 26 | 27 | extension ProductType { 28 | var productCategory: ProductCategory { 29 | switch self { 30 | case .consumable: 31 | return .nonSubscription 32 | case .nonConsumable: 33 | return .nonSubscription 34 | case .nonRenewableSubscription: 35 | return .subscription 36 | case .autoRenewableSubscription: 37 | return .subscription 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/SKProduct+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | extension SKProduct { 9 | public var localizedPrice: String? { 10 | formatter.string(from: price) 11 | } 12 | 13 | private var formatter: NumberFormatter { 14 | let formatter = NumberFormatter() 15 | formatter.numberStyle = .currency 16 | formatter.locale = priceLocale 17 | formatter.currencySymbol = priceLocale.currencySymbol 18 | return formatter 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/SKRequest+IReceiptRefreshRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | // MARK: - SKRequest + IReceiptRefreshRequest 9 | 10 | extension SKRequest: IReceiptRefreshRequest {} 11 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Extensions/SKRequest+Identifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | #if swift(>=6.0) 9 | private nonisolated(unsafe) var requestIdKey: UInt = 0 10 | #else 11 | private var requestIdKey: UInt = 0 12 | #endif 13 | 14 | extension SKRequest { 15 | var id: String { 16 | get { 17 | objc_getAssociatedObject(self, &requestIdKey) as? String ?? "" 18 | } 19 | set { 20 | objc_setAssociatedObject(self, &requestIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Foundation/UserDefaults/IUserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Protocol for managing `UserDefaults` operations. 9 | protocol IUserDefaults { 10 | /// Sets a `Codable` value in `UserDefaults` for a given key. 11 | /// 12 | /// - Parameters: 13 | /// - key: The key to associate with the Codable value. 14 | /// 15 | /// - codable: The Codable value to be stored. 16 | func set(key: String, codable: T) 17 | 18 | /// Retrieves a `Codable` value from `UserDefaults` for a given key. 19 | /// 20 | /// - Parameter key: The key associated with the desired Codable value. 21 | /// 22 | /// - Returns: The Codable value stored for the given key, or nil if not found. 23 | func get(key: String) -> T? 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Foundation/UserDefaults/UserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension UserDefaults: IUserDefaults { 9 | func set(key: String, codable: T) { 10 | guard let value = try? JSONEncoder().encode(codable) else { return } 11 | set(value, forKey: key) 12 | } 13 | 14 | func get(key: String) -> T? { 15 | let data = object(forKey: key) as? Data 16 | guard let data = data, let value = try? JSONDecoder().decode(T.self, from: data) else { return nil } 17 | return value 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Helpers/FileManager/IFileManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | protocol IFileManager { 9 | func fileExists(atPath path: String) -> Bool 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Helpers/PaymentQueue/SKPaymentQueue+PaymentQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | extension SKPaymentQueue: PaymentQueue { 9 | @objc 10 | public var canMakePayments: Bool { 11 | SKPaymentQueue.canMakePayments() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Helpers/ProcessInfo/ProcessInfo+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | #if DEBUG 9 | extension ProcessInfo { 10 | static var isRunningUnitTests: Bool { 11 | self[.XCTestConfigurationFile] != nil 12 | } 13 | } 14 | 15 | // MARK: - Extensions 16 | 17 | extension ProcessInfo { 18 | static subscript(key: String) -> String? { 19 | processInfo.environment[key] 20 | } 21 | } 22 | 23 | // MARK: - Constants 24 | 25 | private extension String { 26 | static let XCTestConfigurationFile = "XCTestConfigurationFilePath" 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Helpers/ScenesHolder/IScenesHolder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | #if canImport(UIKit) 7 | import UIKit 8 | #endif 9 | 10 | // MARK: - IScenesHolder 11 | 12 | /// A type that holds all connected scenes. 13 | @MainActor 14 | protocol IScenesHolder: Sendable { 15 | #if os(iOS) || VISION_OS 16 | /// The scenes that are connected to the app. 17 | var connectedScenes: Set { get } 18 | #endif 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Listeners/TransactionListener/ITransactionListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import StoreKit 8 | 9 | /// Protocol for objects that listen for transactions. 10 | protocol ITransactionListener: Sendable { 11 | /// Listen for incoming transactions asynchronously. 12 | func listenForTransaction() async 13 | 14 | /// Handle the purchase result asynchronously. 15 | /// 16 | /// - Parameters: 17 | /// - purchaseResult: The result of a StoreKit product purchase. 18 | /// - Returns: An optional `StoreTransaction` if handling is successful. 19 | /// 20 | /// - Note: Available on iOS 15.0+, tvOS 15.0+, macOS 12.0+, watchOS 8.0+. 21 | @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) 22 | func handle(purchaseResult: StoreKit.Product.PurchaseResult) async throws -> StoreTransaction? 23 | 24 | func set(delegate: TransactionListenerDelegate) async 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Listeners/TransactionListener/TransactionListenerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | protocol TransactionListenerDelegate: AnyObject, Sendable { 9 | func transactionListener( 10 | _ transactionListener: ITransactionListener, 11 | transactionDidUpdate result: Result 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/ExpirationReason.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import StoreKit 8 | 9 | // MARK: - ExpirationReason 10 | 11 | public enum ExpirationReason { 12 | case autoRenewDisabled 13 | case billingError 14 | case didNotConsentToPriceIncrease 15 | case productUnavailable 16 | case unknown 17 | } 18 | 19 | extension ExpirationReason { 20 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 21 | init(expirationReason: Product.SubscriptionInfo.RenewalInfo.ExpirationReason) { 22 | switch expirationReason { 23 | case .autoRenewDisabled: 24 | self = .autoRenewDisabled 25 | case .billingError: 26 | self = .billingError 27 | case .didNotConsentToPriceIncrease: 28 | self = .didNotConsentToPriceIncrease 29 | case .productUnavailable: 30 | self = .productUnavailable 31 | case .unknown: 32 | self = .unknown 33 | default: 34 | self = .unknown 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/FetchCachePolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Enum representing different cache policies for fetching data. 9 | public enum FetchCachePolicy: Sendable, Codable { 10 | /// Fetch the current data without using the cache. 11 | case fetch 12 | 13 | /// Use the cached data if available; otherwise, fetch the data. 14 | case cachedOrFetch 15 | 16 | /// The default cache policy, set to use cached data if available; otherwise, fetch the data. 17 | static let `default`: FetchCachePolicy = .cachedOrFetch 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/Internal/ProductsRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import StoreKit 8 | 9 | /// A class that represents a request to the App Store. 10 | final class ProductsRequest: ISKRequest { 11 | // MARK: Properties 12 | 13 | /// The request. 14 | private let request: SKRequest 15 | 16 | /// The request’s identifier. 17 | var id: String { request.id } 18 | 19 | // MARK: Initialization 20 | 21 | /// Creates a `ProductsRequest` instance. 22 | /// 23 | /// - Parameter request: The request. 24 | init(_ request: SKRequest) { 25 | self.request = request 26 | } 27 | 28 | // MARK: Hashable 29 | 30 | func hash(into hasher: inout Hasher) { 31 | hasher.combine(id) 32 | } 33 | 34 | // MARK: Equatable 35 | 36 | static func == (lhs: ProductsRequest, rhs: ProductsRequest) -> Bool { 37 | lhs.id == rhs.id 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/Internal/Protocols/ISKRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Type that represents a request to the App Store. 9 | protocol ISKRequest: Hashable { 10 | /// The request’s identifier. 11 | var id: String { get } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/Internal/Protocols/IStorePayment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - IStorePayment 9 | 10 | protocol IStorePayment: AnyObject, Sendable { 11 | var productIdentifier: String { get } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/Internal/Protocols/ISubscriptionInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// A protocol that defines the interface for retrieving subscription-related information. 9 | protocol ISubscriptionInfo { 10 | /// An asynchronous computed property that returns an array of the user's current subscription statuses. 11 | /// 12 | /// - Returns: An array of `SubscriptionInfoStatus` objects representing various subscription states. 13 | /// 14 | /// - Throws: An error if fetching the subscription status fails. 15 | var subscriptionStatus: [SubscriptionInfoStatus] { get async throws } 16 | 17 | /// An asynchronous computed property indicating whether the user is eligible for an introductory offer. 18 | /// 19 | /// - Returns: A `SubscriptionEligibility` value representing the user's eligibility status. 20 | var isEligibleForIntroOffer: SubscriptionEligibility { get async } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/Internal/Protocols/ISubscriptionInfoStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | protocol ISubscriptionInfoStatus { 9 | var renewalState: RenewalState { get } 10 | var subscriptionRenewalInfo: VerificationResult { get } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/Internal/SK1StorePayment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import StoreKit 8 | 9 | // MARK: - SK1StorePayment 10 | 11 | final class SK1StorePayment: @unchecked Sendable { 12 | // MARK: Properties 13 | 14 | let underlyingProduct: SKPayment 15 | 16 | // MARK: Initialization 17 | 18 | init(underlyingProduct: SKPayment) { 19 | self.underlyingProduct = underlyingProduct 20 | } 21 | } 22 | 23 | // MARK: IStorePayment 24 | 25 | extension SK1StorePayment: IStorePayment { 26 | var productIdentifier: String { 27 | underlyingProduct.productIdentifier 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/Internal/SK2SubscriptionInfoStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | // MARK: - SK2SubscriptionInfoStatus 9 | 10 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 11 | struct SK2SubscriptionInfoStatus { 12 | // MARK: Properties 13 | 14 | let underlyingStatus: Product.SubscriptionInfo.Status 15 | 16 | // MARK: Initialization 17 | 18 | init(underlyingStatus: Product.SubscriptionInfo.Status) { 19 | self.underlyingStatus = underlyingStatus 20 | } 21 | } 22 | 23 | // MARK: ISubscriptionInfoStatus 24 | 25 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 26 | extension SK2SubscriptionInfoStatus: ISubscriptionInfoStatus { 27 | var renewalState: RenewalState { 28 | underlyingStatus.renewalState 29 | } 30 | 31 | var subscriptionRenewalInfo: VerificationResult { 32 | underlyingStatus.subscriptionRenewalInfo 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/PriceIncreaseStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import StoreKit 8 | 9 | // MARK: - PriceIncreaseStatus 10 | 11 | public enum PriceIncreaseStatus { 12 | case noIncreasePending 13 | case pending 14 | case agreed 15 | } 16 | 17 | extension PriceIncreaseStatus { 18 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 19 | init(_ status: Product.SubscriptionInfo.RenewalInfo.PriceIncreaseStatus) { 20 | switch status { 21 | case .noIncreasePending: 22 | self = .noIncreasePending 23 | case .pending: 24 | self = .pending 25 | case .agreed: 26 | self = .agreed 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/ProductCategory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Enumeration representing different categories of products in an app. 9 | public enum ProductCategory: Int { 10 | /// A non-renewable or auto-renewable subscription. 11 | case subscription 12 | 13 | /// A consumable or non-consumable in-app purchase. 14 | case nonSubscription 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/ProductType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// The type of product, equivalent to StoreKit's `Product.ProductType`. 9 | public enum ProductType: Int { 10 | /// A consumable in-app purchase. 11 | case consumable 12 | 13 | /// A non-consumable in-app purchase. 14 | case nonConsumable 15 | 16 | /// A non-renewing subscription. 17 | case nonRenewableSubscription 18 | 19 | /// An auto-renewable subscription. 20 | case autoRenewableSubscription 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/RefundError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - RefundError 9 | 10 | /// It encompasses all types of refund errors. 11 | public enum RefundError: Error, Equatable { 12 | /// The duplicate refund request. 13 | case duplicateRequest 14 | /// The refund request failed. 15 | case failed 16 | } 17 | 18 | // MARK: LocalizedError 19 | 20 | extension RefundError: LocalizedError { 21 | public var errorDescription: String? { 22 | switch self { 23 | case .duplicateRequest: 24 | return L10n.RefundError.DuplicateRequest.description 25 | case .failed: 26 | return L10n.RefundError.Failed.description 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/RefundRequestStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// It encompasses all refund request states. 9 | public enum RefundRequestStatus: Sendable { 10 | /// A user cancelled the refund request. 11 | case userCancelled 12 | /// The request completed successfully. 13 | case success 14 | /// The refund request failed with an error. 15 | case failed(error: Error) 16 | /// The unknown error occurred. 17 | case unknown 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/RenewalState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | public enum RenewalState { 9 | case subscribed 10 | case expired 11 | case inBillingRetryPeriod 12 | case revoked 13 | case inGracePeriod 14 | case unknown 15 | 16 | // MARK: Initialization 17 | 18 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 19 | init(_ state: Product.SubscriptionInfo.RenewalState) { 20 | switch state { 21 | case .subscribed: 22 | self = .subscribed 23 | case .expired: 24 | self = .expired 25 | case .inBillingRetryPeriod: 26 | self = .inBillingRetryPeriod 27 | case .revoked: 28 | self = .revoked 29 | case .inGracePeriod: 30 | self = .inGracePeriod 31 | default: 32 | self = .unknown 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/SubscriptionEligibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Enumeration defining the eligibility status for a subscription 9 | public enum SubscriptionEligibility: Int, Sendable { 10 | /// Represents that the subscription is eligible for an offer 11 | case eligible 12 | 13 | /// Represents that the subscription is not eligible for an offer 14 | case nonEligible 15 | 16 | /// Represents that there is no offer available for the subscription 17 | case noOffer 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/SubscriptionInfoStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - SubscriptionInfoStatus 9 | 10 | public struct SubscriptionInfoStatus { 11 | // MARK: Properties 12 | 13 | let underlyingStatus: ISubscriptionInfoStatus 14 | 15 | // MARK: Initialization 16 | 17 | init(underlyingStatus: ISubscriptionInfoStatus) { 18 | self.underlyingStatus = underlyingStatus 19 | } 20 | } 21 | 22 | // MARK: ISubscriptionInfoStatus 23 | 24 | extension SubscriptionInfoStatus: ISubscriptionInfoStatus { 25 | public var renewalState: RenewalState { 26 | self.underlyingStatus.renewalState 27 | } 28 | 29 | public var subscriptionRenewalInfo: VerificationResult { 30 | self.underlyingStatus.subscriptionRenewalInfo 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Models/VerificationResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public enum VerificationResult { 9 | case verified(SignedType) 10 | case unverified(SignedType, Error) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/AppStoreReceiptProvider/IAppStoreReceiptProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Type that retrieves the App Store receipt URL. 9 | protocol IAppStoreReceiptProvider { 10 | /// The App Store receipt URL for the app. 11 | var appStoreReceiptURL: URL? { get } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/CacheProvider/CacheProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - CacheProvider 9 | 10 | /// A class provides caching functionality. 11 | final class CacheProvider { 12 | // MARK: Properties 13 | 14 | /// The user defaults. 15 | private let userDefaults: IUserDefaults 16 | 17 | // MARK: Initialization 18 | 19 | /// Creates a `CacheProvider` instance. 20 | /// 21 | /// - Parameter userDefaults: The user defaults. 22 | init(userDefaults: IUserDefaults = UserDefaults.standard) { 23 | self.userDefaults = userDefaults 24 | } 25 | } 26 | 27 | // MARK: ICacheProvider 28 | 29 | extension CacheProvider: ICacheProvider { 30 | func read(key: String) -> T? { 31 | userDefaults.get(key: key) 32 | } 33 | 34 | func write(key: String, value: T) { 35 | userDefaults.set(key: key, codable: value) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/CacheProvider/ICacheProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Type for a cache provider that supports reading and writing Codable values. 9 | protocol ICacheProvider { 10 | /// Reads a Codable value from the cache using the specified key. 11 | /// 12 | /// - Parameters: 13 | /// - key: The key associated with the value in the cache. 14 | /// - Returns: The Codable value associated with the key, or nil if not found. 15 | func read(key: String) -> T? 16 | 17 | /// Writes a Codable value to the cache using the specified key. 18 | /// 19 | /// - Parameters: 20 | /// - key: The key to associate with the value in the cache. 21 | /// - value: The Codable value to be stored in the cache. 22 | func write(key: String, value: T) 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/ConfigurationProvider/IConfigurationProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Type for providing configuration settings to an application. 9 | protocol IConfigurationProvider { 10 | /// The application username. 11 | var applicationUsername: String? { get } 12 | 13 | /// The cache policy for fetching data. 14 | var fetchCachePolicy: FetchCachePolicy { get } 15 | 16 | /// Configures the provider with the specified configuration settings. 17 | /// 18 | /// - Parameter configuration: The configuration settings to apply. 19 | func configure(with configuration: Configuration) 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/EligibilityProvider/IEligibilityProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Type that provides eligibility checking functionality. 9 | protocol IEligibilityProvider { 10 | /// Checks whether products are eligible for promotional offers 11 | /// 12 | /// - Parameter products: The products to be checked. 13 | /// 14 | /// - Returns: An array that contains information about the eligibility of products. 15 | @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) 16 | func checkEligibility(products: [StoreProduct]) async throws -> [String: SubscriptionEligibility] 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/ProductProvider/Decorators/CachingProductsProviderDecorator/ICachingProductsProviderDecorator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Type that caches retrieved products. 9 | protocol ICachingProductsProviderDecorator: IProductProvider {} 10 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/ProductProvider/Decorators/SortingProductsProviderDecorator/ISortingProductsProviderDecorator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | protocol ISortingProductsProviderDecorator: IProductProvider {} 9 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/ReceiptRefreshProvider/Factories/IReceiptRefreshRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// A type that represents a receipt refresh request. 9 | protocol IReceiptRefreshRequest: Sendable { 10 | /// The request's identifier. 11 | var id: String { get set } 12 | 13 | /// Performs receipt refreshing logic. 14 | func start() 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/ReceiptRefreshProvider/Factories/ReceiptRefreshRequestFactory/IReceiptRefreshRequestFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import protocol StoreKit.SKRequestDelegate 8 | 9 | /// A type that is responsible for create a receipt request. 10 | protocol IReceiptRefreshRequestFactory { 11 | /// Makes a new instance of `IReceiptRefreshRequest`. 12 | /// 13 | /// - Parameters: 14 | /// - requestID: The request's identifier. 15 | /// - delegate: The request's delegate. 16 | /// 17 | /// - Returns: A request. 18 | func make(requestID: String, delegate: SKRequestDelegate?) -> IReceiptRefreshRequest 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/ReceiptRefreshProvider/Factories/ReceiptRefreshRequestFactory/ReceiptRefreshRequestFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import class StoreKit.SKReceiptRefreshRequest 8 | import protocol StoreKit.SKRequestDelegate 9 | 10 | final class ReceiptRefreshRequestFactory: IReceiptRefreshRequestFactory { 11 | func make(requestID: String, delegate: SKRequestDelegate?) -> IReceiptRefreshRequest { 12 | let request = SKReceiptRefreshRequest() 13 | request.id = requestID 14 | request.delegate = delegate 15 | return request 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/ReceiptRefreshProvider/IReceiptRefreshProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | /// A type that can refresh the bundle's App Store receipt. 9 | protocol IReceiptRefreshProvider { 10 | /// The bundle’s App Store receipt. 11 | var receipt: String? { get } 12 | 13 | /// Refreshes the receipt, representing the user's transactions with your app. 14 | /// 15 | /// - Parameters: 16 | /// - requestID: The request identifier. 17 | /// - handler: The closure to be executed when the refresh operation ends. 18 | func refresh(requestID: String, handler: @escaping ReceiptRefreshHandler) 19 | 20 | /// Refreshes the receipt, representing the user's transactions with your app. 21 | /// 22 | /// - Parameter requestID: The request identifier. 23 | /// 24 | /// - Throws: `IAPError(error:)` if the request did fail with error. 25 | func refresh(requestID: String) async throws 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/RedeemCodeProvider/IRedeemCodeProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Protocol defining the requirements for a redeem code provider. 9 | protocol IRedeemCodeProvider { 10 | #if os(iOS) || VISION_OS 11 | /// Displays a sheet in the window scene that enables users to redeem 12 | /// a subscription offer code configured in App Store Connect. 13 | /// 14 | /// - Important: This method is available starting from iOS 16.0. 15 | /// - Note: This method is not available on macOS, watchOS, or tvOS. 16 | /// 17 | /// - Throws: An error if there is an issue with presenting the redeem code sheet. 18 | @available(iOS 16.0, *) 19 | @available(macOS, unavailable) 20 | @available(watchOS, unavailable) 21 | @available(tvOS, unavailable) 22 | func presentOfferCodeRedeemSheet() async throws 23 | #endif 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/RefundProvider/IRefundProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | /// A type that can refund purchases. 7 | protocol IRefundProvider { 8 | #if os(iOS) || VISION_OS 9 | /// Present the refund request sheet for the specified transaction in a window scene. 10 | /// 11 | /// - Parameter productID: The identifier of the transaction the user is requesting a refund for. 12 | /// 13 | /// - Returns: The result of the refund request. 14 | @available(iOS 15.0, *) 15 | @available(macOS, unavailable) 16 | @available(watchOS, unavailable) 17 | @available(tvOS, unavailable) 18 | @MainActor 19 | func beginRefundRequest(productID: String) async throws -> RefundRequestStatus 20 | #endif 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Flare/Classes/Providers/SystemInfoProvider/ISystemInfoProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | #if canImport(UIKit) 7 | import UIKit 8 | #endif 9 | 10 | // MARK: - ISystemInfoProvider 11 | 12 | /// A type that provides the system info. 13 | protocol ISystemInfoProvider: Sendable { 14 | #if os(iOS) || VISION_OS 15 | /// The current window scene. 16 | @MainActor 17 | var currentScene: UIWindowScene { get async throws } 18 | #endif 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Flare/Flare.docc/Articles/caching.md: -------------------------------------------------------------------------------- 1 | # Caching Products 2 | 3 | Learn how to cache products. 4 | 5 | ## Overview 6 | 7 | Caching products can improve the performance and user experience of your app by reducing the need to fetch product information from the App Store. In this guide, we'll explore how to cache products efficiently in your app. 8 | 9 | ## Implementing Product Caching 10 | 11 | By default, Flare uses cached data if available; otherwise, it fetches the products. If you want to change this behavior, you need to configure Flare with a custom caching policy. For this, Flare provides two options ``FetchCachePolicy/fetch`` and ``FetchCachePolicy/cachedOrFetch``. 12 | 13 | You can override the default behaviour passing a ``FetchCachePolicy/fetch`` with a configuration. 14 | 15 | ```swift 16 | Flare.default.configure(with: .init(Configuration(username: "username", fetchCachePolicy: .fetch))) 17 | ``` 18 | 19 | This configuration tells Flare to always fetch the latest data, ignoring any cached data. You can adjust this behavior as needed for your app. 20 | -------------------------------------------------------------------------------- /Sources/Flare/Flare.docc/Articles/logging.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | 3 | Learn how to log important events. 4 | 5 | ## Overview 6 | 7 | The `Flare` supports logging out of the box. It has a set of methods to facilitate logging, each accompanied by a detailed description. 8 | 9 | ### Enabling Logging 10 | 11 | > important: `Flare` uses the `log` package for logging functionality. See [Log Package](https://github.com/space-code/log) for more info. 12 | 13 | By default, `Flare` logs only `debug` or `info` events based on the package building scheme. The special logging level can be forced by setting ``IFlare/logLevel`` to Flare. 14 | 15 | ```swift 16 | Flare.shared.logLevel = .all 17 | ``` 18 | 19 | The logging can be turned off by setting ``IFlare/logLevel`` to `off`. 20 | -------------------------------------------------------------------------------- /Sources/Flare/Flare.docc/Articles/refund-purchase.md: -------------------------------------------------------------------------------- 1 | # Refund Purchase 2 | 3 | Learn how to process a refund through an iOS app. 4 | 5 | ## Refund a Purchase 6 | 7 | Starting with iOS 15, Flare now includes support for refunding purchases as part of StoreKit 2. Under the hood, `Flare` obtains the active window scene and displays the sheets on it. You can read more about the refunding process in the official [Apple documentation](https://developer.apple.com/documentation/storekit/transaction/3803220-beginrefundrequest/). 8 | 9 | Flare suggest to use ``IFlare/beginRefundRequest(productID:)`` for refunding purchase. 10 | 11 | ```swift 12 | let status = try await Flare.shared.beginRefundRequest(productID: "product_id") 13 | ``` 14 | 15 | > important: If an issue occurs during the refund process, this method throws an ``IAPError/refund(error:)`` error. 16 | 17 | Call this function from account settings or a help menu to enable customers to request a refund for an in-app purchase within your app. When you call this function, the system displays a refund sheet with the customer’s purchase details and list of reason codes for the customer to choose from. 18 | -------------------------------------------------------------------------------- /Sources/Flare/Makefile: -------------------------------------------------------------------------------- 1 | swiftgen: 2 | swiftgen -------------------------------------------------------------------------------- /Sources/Flare/swiftgen.yml: -------------------------------------------------------------------------------- 1 | input_dir: Resources 2 | output_dir: Classes/Generated 3 | strings: 4 | inputs: 5 | - Localizable.strings 6 | outputs: 7 | templateName: structured-swift5 8 | output: Strings.swift 9 | params: 10 | publicAccess: false -------------------------------------------------------------------------------- /Sources/FlareMock/Fakes/StoreTransaction+Fake.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | public extension StoreTransaction { 10 | static func fake() -> StoreTransaction { 11 | StoreTransaction(paymentTransaction: PaymentTransaction(PaymentTransactionMock())) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/AnyProductStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct AnyProductStyle: IProductStyle, @unchecked Sendable { 9 | // MARK: Properties 10 | 11 | /// A private property to hold the closure that creates the body of the view 12 | private var _makeBody: (Configuration) -> AnyView 13 | 14 | // MARK: Initialization 15 | 16 | /// Initializes the `AnyProductStyle` with a specific style conforming to `IProductStyle`. 17 | /// 18 | /// - Parameter style: A product style. 19 | init(style: some IProductStyle) { 20 | _makeBody = { configuration in 21 | AnyView(style.makeBody(configuration: configuration)) 22 | } 23 | } 24 | 25 | // MARK: IProductStyle 26 | 27 | /// Implements the makeBody method required by `IProductStyle`. 28 | func makeBody(configuration: Configuration) -> some View { 29 | _makeBody(configuration) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/AnySubscriptionControlStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct AnySubscriptionControlStyle: ISubscriptionControlStyle { 9 | // MARK: Properties 10 | 11 | let style: any ISubscriptionControlStyle 12 | 13 | // MARK: Initialization 14 | 15 | /// Initializes the `AnyProductStyle` with a specific style conforming to `IProductStyle`. 16 | /// 17 | /// - Parameter style: A product style. 18 | init(style: some ISubscriptionControlStyle) { 19 | self.style = style 20 | } 21 | 22 | // MARK: IProductStyle 23 | 24 | /// Implements the makeBody method required by `IProductStyle`. 25 | func makeBody(configuration: Configuration) -> some View { 26 | AnyView(style.makeBody(configuration: configuration)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/Assemblies/ProductAssemblyKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - ProductAssemblyKey 9 | 10 | private struct ProductAssemblyKey: EnvironmentKey { 11 | static let defaultValue: IProductViewAssembly? = nil 12 | } 13 | 14 | extension EnvironmentValues { 15 | var productViewAssembly: IProductViewAssembly? { 16 | get { self[ProductAssemblyKey.self] } 17 | set { self[ProductAssemblyKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/Assemblies/StoreButtonsAssemblyKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - StoreButtonsAssemblyKey 9 | 10 | private struct StoreButtonsAssemblyKey: EnvironmentKey { 11 | static var defaultValue: IStoreButtonsAssembly? { nil } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var storeButtonsAssembly: IStoreButtonsAssembly? { 16 | get { self[StoreButtonsAssemblyKey.self] } 17 | set { self[StoreButtonsAssemblyKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/BlurEffectStyleKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - BlurEffectStyleKey 9 | 10 | #if os(iOS) || os(tvOS) 11 | private struct BlurEffectStyleKey: EnvironmentKey { 12 | static let defaultValue: UIBlurEffect.Style = .light 13 | } 14 | 15 | extension EnvironmentValues { 16 | var blurEffectStyle: UIBlurEffect.Style { 17 | get { self[BlurEffectStyleKey.self] } 18 | set { self[BlurEffectStyleKey.self] = newValue } 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/PoliciesButtonStyleKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - PoliciesButtonStyleKey 9 | 10 | @available(watchOS, unavailable) 11 | private struct PoliciesButtonStyleKey: EnvironmentKey { 12 | static var defaultValue: AnyPoliciesButtonStyle { .init(style: AutomaticPoliciesButtonStyle()) } 13 | } 14 | 15 | @available(watchOS, unavailable) 16 | extension EnvironmentValues { 17 | var policiesButtonStyle: AnyPoliciesButtonStyle { 18 | get { self[PoliciesButtonStyleKey.self] } 19 | set { self[PoliciesButtonStyleKey.self] = newValue } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/ProductStyleKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - ProductStyleKey 9 | 10 | private struct ProductStyleKey: EnvironmentKey { 11 | static var defaultValue: AnyProductStyle { AnyProductStyle(style: CompactProductStyle()) } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var productViewStyle: AnyProductStyle { 16 | get { self[ProductStyleKey.self] } 17 | set { self[ProductStyleKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/PurchaseCompletionKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | import SwiftUI 8 | 9 | public typealias PurchaseCompletionHandler = @Sendable (StoreProduct, Result) -> Void 10 | 11 | // MARK: - PurchaseCompletionKey 12 | 13 | private struct PurchaseCompletionKey: EnvironmentKey { 14 | static let defaultValue: PurchaseCompletionHandler? = nil 15 | } 16 | 17 | extension EnvironmentValues { 18 | var purchaseCompletion: PurchaseCompletionHandler? { 19 | get { self[PurchaseCompletionKey.self] } 20 | set { self[PurchaseCompletionKey.self] = newValue } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/PurchaseOptionKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | import StoreKit 8 | import SwiftUI 9 | 10 | typealias PurchaseOptionHandler = @Sendable (StoreProduct) -> PurchaseOptions 11 | 12 | // MARK: - PurchaseOptionKey 13 | 14 | private struct PurchaseOptionKey: EnvironmentKey { 15 | static let defaultValue: PurchaseOptionHandler? = nil 16 | } 17 | 18 | extension EnvironmentValues { 19 | var purchaseOptions: PurchaseOptionHandler? { 20 | get { self[PurchaseOptionKey.self] } 21 | set { self[PurchaseOptionKey.self] = newValue } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/StoreButtonKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - StoreButtonKey 9 | 10 | private struct StoreButtonKey: EnvironmentKey { 11 | static var defaultValue: [StoreButtonType] { [] } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var storeButton: [StoreButtonType] { 16 | get { self[StoreButtonKey.self] } 17 | set { self[StoreButtonKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/StoreButtonViewFontWeightKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - StoreButtonViewFontWeightKey 9 | 10 | private struct StoreButtonViewFontWeightKey: EnvironmentKey { 11 | static let defaultValue: Font.Weight = .regular 12 | } 13 | 14 | extension EnvironmentValues { 15 | var storeButtonViewFontWeight: Font.Weight { 16 | get { self[StoreButtonViewFontWeightKey.self] } 17 | set { self[StoreButtonViewFontWeightKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionBackgroundKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionBackgroundKey 9 | 10 | private struct SubscriptionBackgroundKey: EnvironmentKey { 11 | static let defaultValue: Color = Palette.systemBackground 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionBackground: Color { 16 | get { self[SubscriptionBackgroundKey.self] } 17 | set { self[SubscriptionBackgroundKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionControlStyleKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionControlStyleKey 9 | 10 | @available(watchOS, unavailable) 11 | private struct SubscriptionControlStyleKey: EnvironmentKey { 12 | static var defaultValue: AnySubscriptionControlStyle { .init(style: .automatic) } 13 | } 14 | 15 | @available(watchOS, unavailable) 16 | extension EnvironmentValues { 17 | var subscriptionControlStyle: AnySubscriptionControlStyle { 18 | get { self[SubscriptionControlStyleKey.self] } 19 | set { self[SubscriptionControlStyleKey.self] = newValue } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionHeaderContentBackgroundKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionHeaderContentBackgroundKey 9 | 10 | private struct SubscriptionHeaderContentBackgroundKey: EnvironmentKey { 11 | static let defaultValue: Color = .clear 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionHeaderContentBackground: Color { 16 | get { self[SubscriptionHeaderContentBackgroundKey.self] } 17 | set { self[SubscriptionHeaderContentBackgroundKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionMarketingContentKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionMarketingContentKey 9 | 10 | private struct SubscriptionMarketingContentKey: EnvironmentKey { 11 | static var defaultValue: AnyView? { nil } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionMarketingContent: AnyView? { 16 | get { self[SubscriptionMarketingContentKey.self] } 17 | set { self[SubscriptionMarketingContentKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionPickerItemBackgroundKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionPickerItemBackgroundKey 9 | 10 | private struct SubscriptionPickerItemBackgroundKey: EnvironmentKey { 11 | static let defaultValue: Color = Palette.systemGray5 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionPickerItemBackground: Color { 16 | get { self[SubscriptionPickerItemBackgroundKey.self] } 17 | set { self[SubscriptionPickerItemBackgroundKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionPrivacyPolicyDestinationKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionPrivacyPolicyDestinationKey 9 | 10 | private struct SubscriptionPrivacyPolicyDestinationKey: EnvironmentKey { 11 | static var defaultValue: AnyView? { nil } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionPrivacyPolicyDestination: AnyView? { 16 | get { self[SubscriptionPrivacyPolicyDestinationKey.self] } 17 | set { self[SubscriptionPrivacyPolicyDestinationKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionPrivacyPolicyURLKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionPrivacyPolicyURLKey 9 | 10 | private struct SubscriptionPrivacyPolicyURLKey: EnvironmentKey { 11 | static let defaultValue: URL? = nil 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionPrivacyPolicyURL: URL? { 16 | get { self[SubscriptionPrivacyPolicyURLKey.self] } 17 | set { self[SubscriptionPrivacyPolicyURLKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionStoreButtonLabelKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionStoreButtonLabelKey 9 | 10 | private struct SubscriptionStoreButtonLabelKey: EnvironmentKey { 11 | static var defaultValue: SubscriptionStoreButtonLabel { .action } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionStoreButtonLabel: SubscriptionStoreButtonLabel { 16 | get { self[SubscriptionStoreButtonLabelKey.self] } 17 | set { self[SubscriptionStoreButtonLabelKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionTermsOfServiceDestinationKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionTermsOfServiceDestinationKey 9 | 10 | private struct SubscriptionTermsOfServiceDestinationKey: EnvironmentKey { 11 | static var defaultValue: AnyView? { nil } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionTermsOfServiceDestination: AnyView? { 16 | get { self[SubscriptionTermsOfServiceDestinationKey.self] } 17 | set { self[SubscriptionTermsOfServiceDestinationKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionTermsOfServiceURLKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionTermsOfServiceURLKey 9 | 10 | private struct SubscriptionTermsOfServiceURLKey: EnvironmentKey { 11 | static let defaultValue: URL? = nil 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionTermsOfServiceURL: URL? { 16 | get { self[SubscriptionTermsOfServiceURLKey.self] } 17 | set { self[SubscriptionTermsOfServiceURLKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionViewTintKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionViewTintKey 9 | 10 | private struct SubscriptionViewTintKey: EnvironmentKey { 11 | static let defaultValue: Color = .blue 12 | } 13 | 14 | extension EnvironmentValues { 15 | var subscriptionViewTint: Color { 16 | get { self[SubscriptionViewTintKey.self] } 17 | set { self[SubscriptionViewTintKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/SubscriptionsWrapperViewStyleKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - SubscriptionsWrapperViewStyleKey 9 | 10 | @available(watchOS, unavailable) 11 | private struct SubscriptionsWrapperViewStyleKey: EnvironmentKey { 12 | static var defaultValue: AnySubscriptionsWrapperViewStyle { 13 | AnySubscriptionsWrapperViewStyle(style: AutomaticSubscriptionsWrapperViewStyle()) 14 | } 15 | } 16 | 17 | @available(watchOS, unavailable) 18 | extension EnvironmentValues { 19 | var subscriptionsWrapperViewStyle: AnySubscriptionsWrapperViewStyle { 20 | get { self[SubscriptionsWrapperViewStyleKey.self] } 21 | set { self[SubscriptionsWrapperViewStyleKey.self] = newValue } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/EnvironmentKey/TintColorKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - TintColorKey 9 | 10 | private struct TintColorKey: EnvironmentKey { 11 | static var defaultValue: Color { .blue } 12 | } 13 | 14 | extension EnvironmentValues { 15 | var tintColor: Color { 16 | get { self[TintColorKey.self] } 17 | set { self[TintColorKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Extensions/Array+RemoveDuplicates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension Array where Element: Hashable { 9 | func removingDuplicates() -> [Element] { 10 | var set = Set() 11 | return filter { set.insert($0).inserted } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Extensions/String+SubSequence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension String { 9 | init?(_ substring: SubSequence?) { 10 | guard let substring else { return nil } 11 | self.init(substring) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Extensions/StringProtocol+Words.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension StringProtocol { 9 | var words: [SubSequence] { 10 | var byWords: [SubSequence] = [] 11 | enumerateSubstrings(in: startIndex..., options: .byWords) { _, range, _, _ in 12 | byWords.append(self[range]) 13 | } 14 | return byWords 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Extensions/View+EraseToAnyView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | extension View { 9 | func eraseToAnyView() -> AnyView { 10 | AnyView(self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Formatters/DateComponentsFormatter+Full.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension DateComponentsFormatter { 9 | static let full: IDateComponentsFormatter = { 10 | let formatter = DateComponentsFormatter() 11 | formatter.maximumUnitCount = 1 12 | formatter.unitsStyle = .full 13 | formatter.zeroFormattingBehavior = .dropAll 14 | return formatter 15 | }() 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Formatters/IDateComponentsFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - IDateComponentsFormatter 9 | 10 | /// A type that creates string representations of quantities of time. 11 | protocol IDateComponentsFormatter: Sendable { 12 | /// The bitmask of calendrical units such as day and month to include in the output string. 13 | var allowedUnits: NSCalendar.Unit { get set } 14 | 15 | /// Returns a formatted string based on the specified date component information. 16 | /// 17 | /// - Parameter from: A date components object containing the date and time information to format. 18 | /// 19 | /// - Returns: A formatted string representing the specified date information. 20 | func string(from: DateComponents) -> String? 21 | } 22 | 23 | // MARK: - DateComponentsFormatter + IDateComponentsFormatter 24 | 25 | extension DateComponentsFormatter: IDateComponentsFormatter {} 26 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Helpers/Array+StoreProduct.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | // MARK: - Extensions 9 | 10 | extension Array where Element: StoreProduct { 11 | func by(id: String) -> StoreProduct? { 12 | first(where: { $0.productIdentifier == id }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Helpers/Color+UIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | #if os(iOS) || os(tvOS) 9 | typealias UIColor = UIKit.UIColor 10 | #elseif os(macOS) 11 | typealias UIColor = NSColor 12 | #endif 13 | 14 | // swiftlint:disable identifier_name 15 | extension Color { 16 | func uiColor() -> UIColor { 17 | if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) { 18 | return UIColor(self) 19 | } 20 | 21 | let scanner = Scanner(string: description.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)) 22 | var hexNumber: UInt64 = 0 23 | var r: CGFloat = 0.0, g: CGFloat = 0.0, b: CGFloat = 0.0, a: CGFloat = 0.0 24 | 25 | let result = scanner.scanHexInt64(&hexNumber) 26 | if result { 27 | r = CGFloat((hexNumber & 0xFF00_0000) >> 24) / 255 28 | g = CGFloat((hexNumber & 0x00FF_0000) >> 16) / 255 29 | b = CGFloat((hexNumber & 0x0000_FF00) >> 8) / 255 30 | a = CGFloat(hexNumber & 0x0000_00FF) / 255 31 | } 32 | return UIColor(red: r, green: g, blue: b, alpha: a) 33 | } 34 | } 35 | 36 | // swiftlint:enable identifier_name 37 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Helpers/Error+IAP.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | extension Error { 9 | var iap: IAPError { 10 | if let error = self as? IAPError { 11 | return error 12 | } 13 | return .with(error: self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Helpers/Value.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | func value(default: T, tvOS: T? = nil, macOS: T? = nil, iOS: T? = nil, watchOS: T? = nil) -> T { 9 | #if os(iOS) 10 | return iOS ?? `default` 11 | #elseif os(macOS) 12 | return macOS ?? `default` 13 | #elseif os(tvOS) 14 | return tvOS ?? `default` 15 | #elseif os(watchOS) 16 | return watchOS ?? `default` 17 | #else 18 | return `default` 19 | #endif 20 | } 21 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Models/Internal/PriceDisplayFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Enum representing different formats for displaying prices. 9 | enum PriceDisplayFormat { 10 | /// Short format for displaying prices. 11 | case short 12 | 13 | /// Full format for displaying prices. 14 | case full 15 | } 16 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Models/Internal/ProductStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Enum representing different styles for displaying a product. 9 | enum ProductStyle { 10 | /// Compact style for displaying a product. 11 | case compact 12 | 13 | /// Large style for displaying a product. 14 | case large 15 | } 16 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Models/PaywallType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// An enum represents a paywall type. 9 | public enum PaywallType { 10 | /// Represents a paywall for subscriptions. 11 | case subscriptions(type: any Collection) 12 | 13 | /// Represents a paywall for specific products identified by their IDs. 14 | case products(productIDs: any Collection) 15 | } 16 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Models/PurchaseOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | /// Struct representing purchase options for a product. 9 | struct PurchaseOptions: @unchecked Sendable { 10 | // MARK: Properties 11 | 12 | /// Internal storage for purchase options. 13 | private var _options: Any? 14 | 15 | /// Purchase options as a set of `Product.PurchaseOption`. 16 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 17 | var options: Set? { 18 | _options as? Set 19 | } 20 | 21 | // MARK: Initialization 22 | 23 | /// Initializes the purchase options with the given set of options. 24 | /// 25 | /// - Parameter options: The set of purchase options to store. 26 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 27 | init(options: Set) { 28 | self._options = options 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Models/SubscriptionStatusVerifierType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Enum representing different types of subscription status verifiers. 9 | public enum SubscriptionStatusVerifierType { 10 | /// Verifier that automatically checks the subscription status. 11 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) 12 | case automatic 13 | 14 | /// Custom verifier implementing `ISubscriptionStatusVerifier` protocol. 15 | case custom(ISubscriptionStatusVerifier) 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Models/SubscriptionStoreButtonLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Enum representing the labels for subscription store buttons. 9 | public enum SubscriptionStoreButtonLabel { 10 | /// Multiline label for the button. 11 | case multiline 12 | 13 | /// Label displaying the price for the subscription. 14 | case price 15 | 16 | /// Label indicating the action of the button (e.g., "Subscribe"). 17 | case action 18 | 19 | /// Label displaying the display name of the subscription. 20 | case displayName 21 | } 22 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Models/UIConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Struct representing configuration settings for the UI. 9 | public struct UIConfiguration { 10 | // MARK: Properties 11 | 12 | /// The subscription status verifier type to use. 13 | public let subscriptionVerifier: SubscriptionStatusVerifierType 14 | 15 | // MARK: Initialization 16 | 17 | /// Initializes the UI configuration with the given subscription status verifier type. 18 | /// 19 | /// - Parameter subscriptionVerifier: The subscription status verifier type to use. 20 | public init(subscriptionVerifier: SubscriptionStatusVerifierType) { 21 | self.subscriptionVerifier = subscriptionVerifier 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Providers/ConfigurationProvider/ConfigurationProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - ConfigurationProvider 9 | 10 | /// A class responsible for providing configuration settings. 11 | final class ConfigurationProvider { 12 | // MARK: Properties 13 | 14 | /// The configuration for the UI module. 15 | private var configuration: UIConfiguration? 16 | } 17 | 18 | // MARK: IConfigurationProvider 19 | 20 | extension ConfigurationProvider: IConfigurationProvider { 21 | var subscriptionVerifierType: SubscriptionStatusVerifierType? { 22 | guard let configuration else { 23 | if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) { 24 | return .automatic 25 | } 26 | return nil 27 | } 28 | return configuration.subscriptionVerifier 29 | } 30 | 31 | func configure(with configuration: UIConfiguration) { 32 | self.configuration = configuration 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Providers/ConfigurationProvider/IConfigurationProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Type for providing configuration settings to an application. 9 | protocol IConfigurationProvider { 10 | /// The subscription verifier type. 11 | var subscriptionVerifierType: SubscriptionStatusVerifierType? { get } 12 | 13 | /// Configures the application with the provided UI configuration. 14 | /// 15 | /// - Parameter configuration: The UI configuration to apply. 16 | func configure(with configuration: UIConfiguration) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Providers/SubscriptionStatusProvider/ISubscriptionStatusVerifierProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Protocol for providing an object that can verify subscription status. 9 | protocol ISubscriptionStatusVerifierProvider { 10 | /// The subscription status verifier object. 11 | var subscriptionStatusVerifier: ISubscriptionStatusVerifier? { get } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Providers/SubscriptionStatusVerifier/ISubscriptionStatusVerifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | /// Protocol for verifying the subscription status of a store product. 9 | public protocol ISubscriptionStatusVerifier { 10 | /// Asynchronously validates the subscription status of the given store product. 11 | /// 12 | /// - Parameters: 13 | /// - storeProduct: The store product to validate. 14 | /// - Returns: A boolean value indicating whether the subscription is valid. 15 | /// - Throws: An error if the validation fails. 16 | func validate(_ storeProduct: StoreProduct) async throws -> Bool 17 | } 18 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Resolvers/SubscriptionStatusVerifierTypeResolver/ISubscriptionStatusVerifierTypeResolver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// Protocol for resolving an object that can verify subscription status based on a given type. 9 | protocol ISubscriptionStatusVerifierTypeResolver { 10 | /// Resolves an object that can verify subscription status based on the given type. 11 | /// 12 | /// - Parameter type: The type of subscription status verifier to resolve. 13 | /// - Returns: An object that can verify subscription status, or `nil` if not found. 14 | func resolve(_ type: SubscriptionStatusVerifierType) -> ISubscriptionStatusVerifier? 15 | } 16 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Core/Resolvers/SubscriptionStatusVerifierTypeResolver/SubscriptionStatusVerifierTypeResolver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// A class responsible for resolving an object that can verify subscription status based on a given type. 9 | final class SubscriptionStatusVerifierTypeResolver: ISubscriptionStatusVerifierTypeResolver { 10 | // MARK: ISubscriptionStatusVerifierTypeResolver 11 | 12 | func resolve(_ type: SubscriptionStatusVerifierType) -> (any ISubscriptionStatusVerifier)? { 13 | switch type { 14 | case .automatic: 15 | if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) { 16 | return SubscriptionStatusVerifier() 17 | } 18 | return nil 19 | case let .custom(subscriptionVerifier): 20 | return subscriptionVerifier 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/DI/Dependencies/FlareDependencies.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | final class FlareDependencies: IFlareDependencies { 9 | // MARK: IFlareDependencies 10 | 11 | lazy var configurationProvider: IConfigurationProvider = ConfigurationProvider() 12 | 13 | var iap: IFlare { 14 | Flare.shared 15 | } 16 | 17 | var subscriptionStatusVerifierProvider: ISubscriptionStatusVerifierProvider { 18 | SubscriptionStatusVerifierProvider( 19 | configurationProvider: self.configurationProvider, 20 | subscriptionStatusVerifierResolver: self.subscriptionStatusVerifierResolver 21 | ) 22 | } 23 | 24 | // MARK: Private 25 | 26 | private var subscriptionStatusVerifierResolver: ISubscriptionStatusVerifierTypeResolver { 27 | SubscriptionStatusVerifierTypeResolver() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/DI/Dependencies/IFlareDependencies.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | /// A type defines dependencies for the package. 9 | protocol IFlareDependencies { 10 | /// An IAP manager. 11 | var iap: IFlare { get } 12 | 13 | /// The configuration provider for FlareUI. 14 | var configurationProvider: IConfigurationProvider { get } 15 | 16 | /// The custom subscription verifier. 17 | var subscriptionStatusVerifierProvider: ISubscriptionStatusVerifierProvider { get } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/DI/PresentationAssembly/IPresentationAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// A type defines a presentation assembly. 9 | protocol IPresentationAssembly { 10 | /// A products view assembly. 11 | var productsViewAssembly: IProductsViewAssembly { get } 12 | 13 | /// A subscriptions view assembly. 14 | var subscritpionsViewAssembly: ISubscriptionsAssembly { get } 15 | 16 | /// A product view assembly. 17 | var productViewAssembly: IProductViewAssembly { get } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/IFlareUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// `IFlareUI` provides a way to configure the UI module. 9 | public protocol IFlareUI {} 10 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Controllers/Helpers/ColorRepresentation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | // swiftlint:disable file_types_order 7 | 8 | #if canImport(UIKit) 9 | import UIKit 10 | #elseif canImport(AppKit) 11 | import AppKit 12 | #endif 13 | 14 | #if os(iOS) || os(tvOS) 15 | public typealias ColorRepresentation = UIKit.UIColor 16 | #elseif os(macOS) 17 | public typealias ColorRepresentation = NSColor 18 | #endif 19 | // swiftlint:enable file_types_order 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Controllers/ProductViewController/ProductViewControllerViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | import SwiftUI 8 | 9 | @available(watchOS, unavailable) 10 | final class ProductViewControllerViewModel: ObservableObject { 11 | @Published var onInAppPurchaseCompletion: PurchaseCompletionHandler? 12 | @Published var inAppPurchaseOptions: PurchaseOptionHandler? 13 | @Published var productStyle = AnyProductStyle(style: CompactProductStyle()) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Controllers/ProductsViewController/ProductsViewControllerViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | import SwiftUI 8 | 9 | @available(watchOS, unavailable) 10 | final class ProductsViewControllerViewModel: ObservableObject { 11 | @Published var onInAppPurchaseCompletion: PurchaseCompletionHandler? 12 | @Published var visibleStoreButtons: [StoreButtonType] = [] 13 | @Published var hiddenStoreButtons: [StoreButtonType] = [] 14 | @Published var inAppPurchaseOptions: PurchaseOptionHandler? 15 | @Published var productStyle = AnyProductStyle(style: CompactProductStyle()) 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Controllers/ViewController/HostingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | #if canImport(UIKit) 7 | import UIKit 8 | #elseif canImport(Cocoa) 9 | import Cocoa 10 | #endif 11 | 12 | import SwiftUI 13 | 14 | #if os(iOS) || os(tvOS) 15 | typealias HostingController = UIHostingController 16 | #elseif os(macOS) 17 | typealias HostingController = NSHostingController 18 | #elseif os(watchOS) 19 | typealias HostingController = WKHostingController 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Controllers/ViewController/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | #if canImport(UIKit) 7 | import UIKit 8 | #elseif canImport(Cocoa) 9 | import Cocoa 10 | #endif 11 | 12 | import SwiftUI 13 | 14 | #if os(iOS) || os(tvOS) 15 | public typealias ViewController = UIViewController 16 | #elseif os(macOS) 17 | public typealias ViewController = NSViewController 18 | #elseif os(watchOS) 19 | public typealias ViewController = WKInterfaceController 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Core/Constants/UIConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension CGFloat { 9 | /// 4px 10 | static let cornerRadius4px = 4.0 11 | } 12 | 13 | extension CGFloat { 14 | /// 10px 15 | static let spacing10px = 10.0 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Core/Models/StoreButtonType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - StoreButtonType 9 | 10 | /// Enum representing different types of buttons in a store. 11 | public enum StoreButtonType: Sendable { 12 | /// Button for restoring purchases. 13 | @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) 14 | case restore 15 | 16 | /// Button for displaying store policies. 17 | case policies 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Core/Models/StoreButtonVisibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - StoreButtonVisibility 9 | 10 | /// Enum representing the visibility states of a store button. 11 | public enum StoreButtonVisibility { 12 | /// The button is visible. 13 | case visible 14 | 15 | /// The button is hidden. 16 | case hidden 17 | } 18 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Core/Protocols/IModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// A type that represents a default model object. 9 | protocol IModel { 10 | /// The associated type representing the state of the model. 11 | associatedtype State 12 | 13 | /// The current state of the model. 14 | var state: State { get } 15 | 16 | /// Function to set the state of the model and return a new instance with the updated state. 17 | /// 18 | /// - Parameter state: The new state to set. 19 | /// 20 | /// - Returns: A new instance of the model with the updated state. 21 | func setState(_ state: State) -> Self 22 | } 23 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Factories/ISubscriptionPriceViewModelFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | // MARK: - ISubscriptionPriceViewModelFactory 9 | 10 | /// Protocol for creating view models representing subscription prices. 11 | protocol ISubscriptionPriceViewModelFactory { 12 | /// Creates a string representing the price of a subscription product. 13 | /// 14 | /// - Parameters: 15 | /// - product: The subscription product. 16 | /// - format: The format in which to display the price. 17 | /// - Returns: A string representing the price. 18 | func make(_ product: StoreProduct, format: PriceDisplayFormat) -> String 19 | 20 | /// Retrieves the period of a subscription product. 21 | /// 22 | /// - Parameter product: The subscription product. 23 | /// - Returns: A string representing the subscription period. 24 | func period(from product: StoreProduct) -> String? 25 | } 26 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+Contrast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // swiftlint:disable identifier_name 9 | extension View { 10 | func contrast( 11 | _ backgroundColor: Color, 12 | lightColor: Color = .white, 13 | darkColor: Color = .black 14 | ) -> some View { 15 | var r, g, b, a: CGFloat 16 | (r, g, b, a) = (0, 0, 0, 0) 17 | backgroundColor.uiColor().getRed(&r, green: &g, blue: &b, alpha: &a) 18 | let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b 19 | return luminance < 0.6 ? foregroundColor(lightColor) : foregroundColor(darkColor) 20 | } 21 | } 22 | 23 | // swiftlint:enable identifier_name 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+Paywall.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// A SwiftUI extension to add a paywall to a view. 9 | public extension View { 10 | /// Adds a paywall to the view. 11 | /// 12 | /// - Parameters: 13 | /// - presented: A binding to control the presentation state of the paywall. 14 | /// - paywallType: The type of paywall to display. 15 | /// 16 | /// - Returns: A modified view with the paywall added. 17 | @available(watchOS, unavailable) 18 | func paywall(presented: Binding, paywallType: PaywallType) -> some View { 19 | modifier( 20 | PaywallViewModifier( 21 | paywallType: paywallType, 22 | presented: presented 23 | ) 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+ProductViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for applying a specific style to a view representing a product. 9 | public extension View { 10 | /// Sets the style for the product view. 11 | /// 12 | /// - Parameter style: The style to apply to the product view. 13 | /// 14 | /// - Returns: A modified view with the specified style applied. 15 | func productViewStyle(_ style: some IProductStyle) -> some View { 16 | environment(\.productViewStyle, AnyProductStyle(style: style)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+PurchaseCompletion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | import SwiftUI 8 | 9 | /// Extension for handling in-app purchase completions within a view. 10 | public extension View { 11 | /// Sets a completion handler for in-app purchase transactions. 12 | /// 13 | /// - Parameter completion: The completion handler to execute when an in-app purchase transaction completes. 14 | /// 15 | /// - Returns: A modified view with the specified completion handler. 16 | func onInAppPurchaseCompletion(completion: (@Sendable (StoreProduct, Result) -> Void)?) -> some View { 17 | environment(\.purchaseCompletion, completion) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+StoreButtonViewFontWeight.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the font weight of store button views within a view. 9 | extension View { 10 | /// Sets the font weight of store button views for the view. 11 | /// 12 | /// - Parameter weight: The font weight to apply to store button views. 13 | /// 14 | /// - Returns: A modified view with the specified font weight for store button views. 15 | func storeButtonViewFontWeight(_ weight: Font.Weight) -> some View { 16 | environment(\.storeButtonViewFontWeight, weight) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionBackground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the background color of subscription views within a view. 9 | public extension View { 10 | /// Sets the background color of subscription views for the view. 11 | /// 12 | /// - Parameter color: The background color to apply to subscription views. 13 | /// 14 | /// - Returns: A modified view with the specified background color for subscription views. 15 | @available(iOS 13.0, macOS 10.15, *) 16 | @available(tvOS, unavailable) 17 | @available(watchOS, unavailable) 18 | @available(visionOS, unavailable) 19 | func subscriptionBackground(_ color: Color) -> some View { 20 | environment(\.subscriptionBackground, color) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionHeaderContentBackground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the background color of subscription header content within a view. 9 | public extension View { 10 | /// Sets the background color of subscription header content for the view. 11 | /// 12 | /// - Parameter color: The background color to apply to subscription header content. 13 | /// 14 | /// - Returns: A modified view with the specified background color for subscription header content. 15 | @available(iOS 13.0, *) 16 | @available(macOS, unavailable) 17 | @available(tvOS, unavailable) 18 | @available(watchOS, unavailable) 19 | @available(visionOS, unavailable) 20 | func subscriptionHeaderContentBackground(_ color: Color) -> some View { 21 | environment(\.subscriptionHeaderContentBackground, color) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionMarketingContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the marketing content of a subscription within a view. 9 | public extension View { 10 | /// Sets the marketing content for the subscription view. 11 | /// 12 | /// - Parameter view: A closure that returns the marketing content as a view. 13 | /// 14 | /// - Returns: A modified view with the specified marketing content for the subscription. 15 | func subscriptionMarketingContent(@ViewBuilder view: () -> some View) -> some View { 16 | environment(\.subscriptionMarketingContent, view().eraseToAnyView()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionPickerItemBackground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the label style of subscription store buttons within a view. 9 | public extension View { 10 | /// Sets the label style for subscription store buttons for the view. 11 | /// 12 | /// - Parameter style: The label style to apply to subscription store buttons. 13 | /// 14 | /// - Returns: A modified view with the specified label style for subscription store buttons. 15 | @available(iOS 13.0, macOS 10.15, *) 16 | @available(tvOS, unavailable) 17 | @available(watchOS, unavailable) 18 | @available(visionOS, unavailable) 19 | func subscriptionButtonLabel(_ style: SubscriptionStoreButtonLabel) -> some View { 20 | environment(\.subscriptionStoreButtonLabel, style) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionPrivacyPolicyDestination.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the destination of the privacy policy for a subscription within a view. 9 | public extension View { 10 | /// Sets the destination for the privacy policy of the subscription view. 11 | /// 12 | /// - Parameter content: A closure that returns the view representing the destination. 13 | /// 14 | /// - Returns: A modified view with the specified destination for the privacy policy. 15 | func subscriptionPrivacyPolicyDestination(@ViewBuilder content: () -> some View) -> some View { 16 | environment(\.subscriptionPrivacyPolicyDestination, content().eraseToAnyView()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionPrivacyPolicyURL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the URL of the privacy policy for a subscription within a view. 9 | public extension View { 10 | /// Sets the URL for the privacy policy of the subscription view. 11 | /// 12 | /// - Parameter url: The URL of the privacy policy. 13 | /// 14 | /// - Returns: A modified view with the specified URL for the privacy policy. 15 | @available(iOS 13.0, *) 16 | @available(macOS, unavailable) 17 | @available(tvOS, unavailable) 18 | @available(watchOS, unavailable) 19 | @available(visionOS, unavailable) 20 | func subscriptionPrivacyPolicyURL(_ url: URL?) -> some View { 21 | environment(\.subscriptionPrivacyPolicyURL, url) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionStoreButtonLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the background color of subscription picker items within a view. 9 | public extension View { 10 | /// Sets the background color of subscription picker items for the view. 11 | /// 12 | /// - Parameter backgroundStyle: The background color to apply to subscription picker items. 13 | /// 14 | /// - Returns: A modified view with the specified background color for subscription picker items. 15 | @available(iOS 13.0, macOS 10.15, *) 16 | @available(tvOS, unavailable) 17 | @available(watchOS, unavailable) 18 | @available(visionOS, unavailable) 19 | func subscriptionPickerItemBackground(_ backgroundStyle: Color) -> some View { 20 | environment(\.subscriptionPickerItemBackground, backgroundStyle) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionTermsOfServiceDestination.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the destination of the terms of service for a subscription within a view. 9 | public extension View { 10 | /// Sets the destination for the terms of service of the subscription view. 11 | /// 12 | /// - Parameter content: A closure that returns the view representing the destination. 13 | /// 14 | /// - Returns: A modified view with the specified destination for the terms of service. 15 | func subscriptionTermsOfServiceDestination(@ViewBuilder content: () -> some View) -> some View { 16 | environment(\.subscriptionTermsOfServiceDestination, content().eraseToAnyView()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionTermsOfServiceURL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the URL of the terms of service for a subscription within a view. 9 | public extension View { 10 | /// Sets the URL for the terms of service of the subscription view. 11 | /// 12 | /// - Parameter url: The URL of the terms of service. 13 | /// 14 | /// - Returns: A modified view with the specified URL for the terms of service. 15 | @available(iOS 13.0, *) 16 | @available(macOS, unavailable) 17 | @available(tvOS, unavailable) 18 | @available(watchOS, unavailable) 19 | @available(visionOS, unavailable) 20 | func subscriptionTermsOfServiceURL(_ url: URL?) -> some View { 21 | environment(\.subscriptionTermsOfServiceURL, url) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+SubscriptionViewTint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the tint color of subscription views within a view. 9 | public extension View { 10 | /// Sets the tint color for subscription views for the view. 11 | /// 12 | /// - Parameter color: The tint color to apply to subscription views. 13 | /// 14 | /// - Returns: A modified view with the specified tint color for subscription views. 15 | func subscriptionViewTint(_ color: Color) -> some View { 16 | environment(\.subscriptionViewTint, color) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Helpers/SUI/View+TintColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// Extension for configuring the tint color of views within a view hierarchy. 9 | public extension View { 10 | /// Sets the tint color for views in the view hierarchy. 11 | /// 12 | /// - Parameter color: The tint color to apply to views. 13 | /// 14 | /// - Returns: A modified view with the specified tint color for views. 15 | func tintColor(_ color: Color) -> some View { 16 | environment(\.tintColor, color) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/BorderedButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - BorderedButtonStyle 9 | 10 | struct BorderedButtonStyle: ButtonStyle { 11 | // MARK: ButtonStyle 12 | 13 | func makeBody(configuration: Configuration) -> some View { 14 | configuration.label 15 | .padding(.horizontal, .horizontalPadding) 16 | .padding(.vertical, .verticalPadding) 17 | .background(Palette.gray) 18 | .foregroundColor(.blue) 19 | .mask(Capsule()) 20 | .opacity(configuration.isPressed ? 0.5 : 1.0) 21 | } 22 | } 23 | 24 | // MARK: Extensions 25 | 26 | extension ButtonStyle where Self == BorderedButtonStyle { 27 | static var bordered: Self { 28 | .init() 29 | } 30 | } 31 | 32 | // MARK: Constants 33 | 34 | private extension CGFloat { 35 | static let horizontalPadding = 16.0 36 | static let verticalPadding = 8.0 37 | } 38 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Product/Protocols/IProductStyle+Compact.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public extension IProductStyle where Self == CompactProductStyle { 9 | static var `default`: Self { 10 | CompactProductStyle() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Product/Protocols/IProductStyle+Large.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | @available(iOS 13.0, *) 9 | @available(macOS, unavailable) 10 | @available(watchOS, unavailable) 11 | @available(tvOS, unavailable) 12 | public extension IProductStyle where Self == LargeProductStyle { 13 | static var large: Self { 14 | LargeProductStyle() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Product/Protocols/IProductStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | /// A type that represents a product style. 9 | public protocol IProductStyle { 10 | /// The properties of an in-app store product. 11 | typealias Configuration = ProductStyleConfiguration 12 | 13 | /// A view that represents the body of an in-app store product. 14 | associatedtype Body: View 15 | 16 | /// Creates a view that represents the body of an in-app store product. 17 | /// - Parameter configuration: The properties of an in-app store product. 18 | @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Subscription/Extensions/ISubscriptionControlStyle+Bordered.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) 9 | @available(watchOS, unavailable) 10 | public extension ISubscriptionControlStyle where Self == ButtonSubscriptionStoreControlStyle { 11 | static var button: ButtonSubscriptionStoreControlStyle { 12 | ButtonSubscriptionStoreControlStyle() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Subscription/Extensions/ISubscriptionControlStyle+PickerSubscriptionStoreControlStyle+PickerSubscriptionStoreControlStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | @available(iOS 13.0, macOS 10.15, watchOS 7.0, *) 9 | @available(tvOS, unavailable) 10 | public extension ISubscriptionControlStyle where Self == PickerSubscriptionStoreControlStyle { 11 | static var picker: PickerSubscriptionStoreControlStyle { 12 | PickerSubscriptionStoreControlStyle() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Subscription/Extensions/ISubscriptionControlStyle+ProminentPickerSubscriptionStoreControlStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | @available(iOS 13.0, macOS 10.15, *) 9 | @available(tvOS, unavailable) 10 | @available(watchOS, unavailable) 11 | @available(visionOS, unavailable) 12 | public extension ISubscriptionControlStyle where Self == ProminentPickerSubscriptionStoreControlStyle { 13 | static var prominentPicker: ProminentPickerSubscriptionStoreControlStyle { 14 | ProminentPickerSubscriptionStoreControlStyle() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Subscription/Protocols/ISubscriptionControlStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | public protocol ISubscriptionControlStyle { 9 | /// A view that represents the body of an in-app subscription store control. 10 | associatedtype Body: View 11 | 12 | /// The properties of an in-app subscription store control. 13 | typealias Configuration = SubscriptionStoreControlStyleConfiguration 14 | 15 | /// Creates a view that represents the body of an in-app subscription store control. 16 | /// 17 | /// - Parameters: 18 | /// - configuration: The properties of an in-app subscription store control. 19 | @MainActor 20 | @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body 21 | } 22 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Styles/Subscription/SubscriptionStoreControlStyle/AutomaticSubscriptionControlStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - AutomaticSubscriptionControlStyle 9 | 10 | @available(watchOS, unavailable) 11 | struct AutomaticSubscriptionControlStyle: ISubscriptionControlStyle { 12 | // MARK: ISubscriptionControlStyle 13 | 14 | func makeBody(configuration: Configuration) -> some View { 15 | #if os(iOS) 16 | return ProminentPickerSubscriptionStoreControlStyle().makeBody(configuration: configuration) 17 | #else 18 | return ButtonSubscriptionStoreControlStyle().makeBody(configuration: configuration) 19 | #endif 20 | } 21 | } 22 | 23 | // MARK: - Extensions 24 | 25 | @available(watchOS, unavailable) 26 | extension ISubscriptionControlStyle where Self == AutomaticSubscriptionControlStyle { 27 | static var automatic: AutomaticSubscriptionControlStyle { 28 | AutomaticSubscriptionControlStyle() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/ViewModifiers/ActivityIndicatorModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - ActivityIndicatorModifier 9 | 10 | struct ActivityIndicatorModifier: ViewModifier { 11 | // MARK: Properties 12 | 13 | private let isLoading: Bool 14 | 15 | // MARK: Initialization 16 | 17 | init(isLoading: Bool) { 18 | self.isLoading = isLoading 19 | } 20 | 21 | // MARK: ViewModifier 22 | 23 | func body(content: Content) -> some View { 24 | if isLoading { 25 | ZStack(alignment: .center) { 26 | content 27 | .disabled(isLoading) 28 | .blur(radius: self.isLoading ? 3 : 0) 29 | 30 | LoadingView(type: .backgrouned, message: "Purchasing the subscription...") 31 | } 32 | } else { 33 | content 34 | } 35 | } 36 | } 37 | 38 | extension View { 39 | func activityIndicator(isLoading: Bool) -> some View { 40 | modifier(ActivityIndicatorModifier(isLoading: isLoading)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/ViewModifiers/BlurEffectModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | #if os(iOS) || os(tvOS) 9 | struct BlurEffectModifier: ViewModifier { 10 | init() {} 11 | 12 | func body(content: Content) -> some View { 13 | content 14 | .overlay(BlurVisualEffectView()) 15 | } 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Views/BlurVisualEffectView/BlurVisualEffectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - BlurVisualEffectView 9 | 10 | #if os(iOS) || os(tvOS) 11 | struct BlurVisualEffectView: UIViewRepresentable { 12 | func makeUIView(context: Context) -> UIVisualEffectView { 13 | UIVisualEffectView(effect: UIBlurEffect(style: context.environment.blurEffectStyle)) 14 | } 15 | 16 | func updateUIView(_ uiView: UIVisualEffectView, context: Context) { 17 | uiView.effect = UIBlurEffect(style: context.environment.blurEffectStyle) 18 | } 19 | } 20 | 21 | extension View { 22 | /// Creates a blur effect. 23 | func blurEffect() -> some View { 24 | ModifiedContent(content: self, modifier: BlurEffectModifier()) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Views/ImageView/ImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ImageView: View { 9 | // MARK: Properties 10 | 11 | private let systemName: String 12 | private let defaultImage: Image 13 | 14 | // MARK: Initialization 15 | 16 | init(systemName: String, defaultImage: Image) { 17 | self.systemName = systemName 18 | self.defaultImage = defaultImage 19 | } 20 | 21 | // MARK: View 22 | 23 | var body: some View { 24 | Group { 25 | if #available(macOS 11.0, iOS 14.0, tvOS 14.0, *) { 26 | Image(systemName: systemName) 27 | .resizable() 28 | } else { 29 | defaultImage 30 | .resizable() 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Components/Views/SafariWebView/SafariWebView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | #if os(iOS) 7 | import SafariServices 8 | import SwiftUI 9 | 10 | struct SafariWebView: UIViewControllerRepresentable { 11 | let url: URL 12 | 13 | func makeUIViewController(context _: Context) -> SFSafariViewController { 14 | SFSafariViewController(url: url) 15 | } 16 | 17 | func updateUIViewController(_: SFSafariViewController, context _: Context) {} 18 | } 19 | #endif 20 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Helpers/ViewWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - IViewWrapper 9 | 10 | /// A type defines a wrapper for a view. 11 | protocol IViewWrapper: View { 12 | associatedtype ViewModel 13 | 14 | /// Creates a new `IViewWrapper` instance. 15 | /// 16 | /// - Parameter viewModel: The view model. 17 | init(viewModel: ViewModel) 18 | } 19 | 20 | // MARK: - ViewWrapper 21 | 22 | struct ViewWrapper: View where ViewWrapper.ViewModel == ViewModel { 23 | // MARK: Properties 24 | 25 | @ObservedObject private var viewModel: WrapperViewModel 26 | 27 | // MARK: Initialization 28 | 29 | init(viewModel: WrapperViewModel) { 30 | self.viewModel = viewModel 31 | } 32 | 33 | // MARK: IViewWrapper 34 | 35 | var body: some View { 36 | ViewWrapper(viewModel: viewModel.model) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Helpers/WrapperViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /// An observable view model. 9 | final class WrapperViewModel: ObservableObject { 10 | // MARK: Properties 11 | 12 | /// The model object. 13 | @Published var model: T 14 | 15 | // MARK: Initialization 16 | 17 | /// Creates a `ViewModel` instance. 18 | /// 19 | /// - Parameter model: The model object. 20 | init(model: T) { 21 | self.model = model 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PaywallView/PaywallView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(watchOS, unavailable) 9 | struct PaywallView: View { 10 | // MARK: Properties 11 | 12 | private let presentationAssembly: IPresentationAssembly 13 | private let paywallType: PaywallType 14 | 15 | // MARK: Initialization 16 | 17 | init( 18 | paywallType: PaywallType, 19 | presentationAssembly: IPresentationAssembly = PresentationAssembly() 20 | ) { 21 | self.paywallType = paywallType 22 | self.presentationAssembly = presentationAssembly 23 | } 24 | 25 | // MARK: View 26 | 27 | var body: some View { 28 | switch paywallType { 29 | case let .subscriptions(productIDs): 30 | let productIDs: any Collection = productIDs 31 | presentationAssembly.subscritpionsViewAssembly.assemble(ids: productIDs) 32 | case let .products(productIDs): 33 | let productIDs: any Collection = productIDs 34 | presentationAssembly.productsViewAssembly.assemble(ids: productIDs) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/PoliciesButtonAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - IPoliciesButtonAssembly 9 | 10 | @available(watchOS, unavailable) 11 | protocol IPoliciesButtonAssembly { 12 | @MainActor 13 | func assemble() -> PoliciesButtonView 14 | } 15 | 16 | // MARK: - PoliciesButtonAssembly 17 | 18 | @available(watchOS, unavailable) 19 | final class PoliciesButtonAssembly: IPoliciesButtonAssembly { 20 | @MainActor 21 | func assemble() -> PoliciesButtonView { 22 | PoliciesButtonView() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/Styles/AnyPoliciesButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct AnyPoliciesButtonStyle: IPoliciesButtonStyle { 9 | // MARK: Properties 10 | 11 | let style: any IPoliciesButtonStyle 12 | 13 | // MARK: Initialization 14 | 15 | /// Initializes the `AnyPoliciesButtonStyle` with a specific style conforming to `IPoliciesButtonStyle`. 16 | /// 17 | /// - Parameter style: A product style. 18 | init(style: some IPoliciesButtonStyle) { 19 | self.style = style 20 | } 21 | 22 | // MARK: IPoliciesButtonStyle 23 | 24 | /// Implements the makeBody method required by `IPoliciesButtonStyle`. 25 | func makeBody(configuration: Configuration) -> some View { 26 | AnyView(style.makeBody(configuration: configuration)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/Styles/AutomaticPoliciesButtonStyle/AutomaticPoliciesButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(watchOS, unavailable) 9 | struct AutomaticPoliciesButtonStyle: IPoliciesButtonStyle { 10 | func makeBody(configuration: Configuration) -> some View { 11 | #if os(tvOS) 12 | return TVPoliciesButtonStyle().makeBody(configuration: configuration) 13 | #else 14 | return DefaultPoliciesButtonStyle().makeBody(configuration: configuration) 15 | #endif 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/Styles/Configuration/PoliciesButtonStyleConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct PoliciesButtonStyleConfiguration: Sendable { 9 | // MARK: Types 10 | 11 | struct ButtonView: View { 12 | var body: AnyView 13 | 14 | init(_ view: some View) { 15 | body = view.eraseToAnyView() 16 | } 17 | } 18 | 19 | // MARK: Properties 20 | 21 | let termsOfUseView: ButtonView 22 | let privacyPolicyView: ButtonView 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/Styles/DefaultPoliciesButtonStyle/DefaultPoliciesButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(iOS 13.0, macOS 10.15, *) 9 | @available(watchOS, unavailable) 10 | @available(tvOS, unavailable) 11 | struct DefaultPoliciesButtonStyle: IPoliciesButtonStyle { 12 | @MainActor 13 | func makeBody(configuration: Configuration) -> some View { 14 | DefaultPoliciesButtonStyleView(configuration: configuration) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/Styles/DefaultPoliciesButtonStyle/DefaultPoliciesButtonStyleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - DefaultPoliciesButtonStyleView 9 | 10 | struct DefaultPoliciesButtonStyleView: View { 11 | // MARK: Properties 12 | 13 | private let configuration: PoliciesButtonStyleConfiguration 14 | 15 | @Environment(\.tintColor) private var tintColor 16 | 17 | // MARK: Initialization 18 | 19 | init(configuration: PoliciesButtonStyleConfiguration) { 20 | self.configuration = configuration 21 | } 22 | 23 | // MARK: View 24 | 25 | var body: some View { 26 | HStack(spacing: .spacing) { 27 | configuration.termsOfUseView 28 | .foregroundColor(tintColor) 29 | 30 | Text(L10n.Common.Words.and) 31 | 32 | configuration.privacyPolicyView 33 | .foregroundColor(tintColor) 34 | } 35 | } 36 | } 37 | 38 | // MARK: - Constants 39 | 40 | private extension CGFloat { 41 | static let spacing = 3.0 42 | } 43 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/Styles/IPoliciesButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | protocol IPoliciesButtonStyle { 9 | /// A view that represents the body of an in-app subscription store control. 10 | associatedtype Body: View 11 | 12 | /// The properties of an in-app subscription store control. 13 | typealias Configuration = PoliciesButtonStyleConfiguration 14 | 15 | /// Creates a view that represents the body of an in-app subscription store control. 16 | /// 17 | /// - Parameter configuration: The properties of an in-app subscription store control. 18 | @MainActor 19 | @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body 20 | } 21 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/PoliciesButtonAssembly/Styles/TVPoliciesButtonStyle/TVPoliciesButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | // MARK: - TVPoliciesButtonStyle 9 | 10 | @available(tvOS 13.0, *) 11 | @available(macOS, unavailable) 12 | @available(watchOS, unavailable) 13 | @available(iOS, unavailable) 14 | struct TVPoliciesButtonStyle: IPoliciesButtonStyle { 15 | func makeBody(configuration: Configuration) -> some View { 16 | HStack(spacing: .spacing) { 17 | configuration.termsOfUseView 18 | configuration.privacyPolicyView 19 | } 20 | } 21 | } 22 | 23 | // MARK: - Constants 24 | 25 | private extension CGFloat { 26 | static let spacing = 60.0 27 | } 28 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/ProductView/ProductViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | import Foundation 8 | 9 | // MARK: - ProductViewModel 10 | 11 | /// A view model for managing a product. 12 | struct ProductViewModel: IModel, @unchecked Sendable { 13 | /// The state of the view model. 14 | enum State: Equatable { 15 | /// Loading state. 16 | case loading 17 | /// Loaded product state. 18 | case product(StoreProduct) 19 | /// Error state. 20 | case error(IAPError) 21 | } 22 | 23 | /// The current state of the view model. 24 | let state: State 25 | /// The presenter for the product. 26 | let presenter: IProductPresenter 27 | } 28 | 29 | extension ProductViewModel { 30 | /// Sets the state of the view model and returns a new instance with the updated state. 31 | /// 32 | /// - Parameter state: The new state of the view model. 33 | /// - Returns: A new `ProductViewModel` instance with the updated state. 34 | func setState(_ state: State) -> ProductViewModel { 35 | ProductViewModel(state: state, presenter: presenter) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/ProductView/ProductViewType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | /// An enumeration representing different types of product views. 9 | enum ProductViewType { 10 | /// A product view initialized with a `StoreProduct`. 11 | case product(StoreProduct) 12 | /// A product view initialized with a product ID. 13 | case productID(String) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/ProductView/Strategies/ProductStrategy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | 8 | // MARK: - IProductFetcherStrategy 9 | 10 | protocol IProductFetcherStrategy { 11 | func product() async throws -> StoreProduct 12 | } 13 | 14 | // MARK: - ProductStrategy 15 | 16 | final class ProductStrategy: IProductFetcherStrategy { 17 | // MARK: Properties 18 | 19 | private let iap: IFlare 20 | private let type: ProductViewType 21 | 22 | // MARK: Initialization 23 | 24 | init(type: ProductViewType, iap: IFlare) { 25 | self.type = type 26 | self.iap = iap 27 | } 28 | 29 | // MARK: IProductStrategy 30 | 31 | func product() async throws -> StoreProduct { 32 | switch type { 33 | case let .productID(id): 34 | let product = try await iap.fetch(productIDs: [id]).first 35 | 36 | guard let product else { throw IAPError.storeProductNotAvailable } 37 | 38 | return product 39 | case let .product(product): 40 | return product 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/StoreButtonView/StoreButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | enum StoreButton { 9 | @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) 10 | case restore 11 | } 12 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/StoreButtonView/StoreButtonPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | import Foundation 8 | 9 | // MARK: - IStoreButtonPresenter 10 | 11 | protocol IStoreButtonPresenter { 12 | @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) 13 | func restore() async throws 14 | } 15 | 16 | // MARK: - StoreButtonPresenter 17 | 18 | final class StoreButtonPresenter: IPresenter { 19 | // MARK: Properties 20 | 21 | private let iap: IFlare 22 | 23 | weak var viewModel: WrapperViewModel? 24 | 25 | // MARK: Initialization 26 | 27 | init(iap: IFlare) { 28 | self.iap = iap 29 | } 30 | } 31 | 32 | // MARK: IStoreButtonPresenter 33 | 34 | extension StoreButtonPresenter: IStoreButtonPresenter { 35 | @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) 36 | func restore() async throws { 37 | do { 38 | try await iap.restore() 39 | } catch { 40 | if let error = error as? IAPError, error != .paymentCancelled { 41 | throw error 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/StoreButtonView/StoreButtonViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // MARK: - StoreButtonViewModel 9 | 10 | struct StoreButtonViewModel: IModel, @unchecked Sendable { 11 | struct ViewModel: Equatable { 12 | let title: String 13 | } 14 | 15 | enum State: Equatable { 16 | case restore(viewModel: ViewModel) 17 | 18 | var title: String { 19 | switch self { 20 | case let .restore(viewModel): 21 | return viewModel.title 22 | } 23 | } 24 | } 25 | 26 | let state: State 27 | let presenter: IStoreButtonPresenter 28 | } 29 | 30 | extension StoreButtonViewModel { 31 | func setState(_ state: State) -> StoreButtonViewModel { 32 | StoreButtonViewModel(state: state, presenter: presenter) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/SubscriptionsView/Styles/AnySubscriptionsWrapperViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(watchOS, unavailable) 9 | struct AnySubscriptionsWrapperViewStyle: ISubscriptionsWrapperViewStyle, @unchecked Sendable { 10 | // MARK: Properties 11 | 12 | /// A private property to hold the closure that creates the body of the view 13 | private var style: any ISubscriptionsWrapperViewStyle 14 | 15 | // MARK: Initialization 16 | 17 | /// Initializes the `AnyProductStyle` with a specific style conforming to `IProductStyle`. 18 | /// 19 | /// - Parameter style: A product style. 20 | init(style: some ISubscriptionsWrapperViewStyle) { 21 | self.style = style 22 | } 23 | 24 | // MARK: IProductStyle 25 | 26 | /// Implements the makeBody method required by `IProductStyle`. 27 | func makeBody(configuration: Configuration) -> some View { 28 | AnyView(style.makeBody(configuration: configuration)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/SubscriptionsView/Styles/SubscriptionsWrapperViewStyle/Configuration/SubscriptionsWrapperViewStyleConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(watchOS, unavailable) 9 | // swiftlint:disable:next type_name 10 | struct SubscriptionsWrapperViewStyleConfiguration: Sendable { 11 | // MARK: Types 12 | 13 | struct Toolbar: View { 14 | let body: AnyView 15 | 16 | init(_ content: some View) { 17 | self.body = content.eraseToAnyView() 18 | } 19 | } 20 | 21 | // MARK: Properties 22 | 23 | let items: [SubscriptionView.ViewModel] 24 | let selectedID: String? 25 | let action: @Sendable (SubscriptionView.ViewModel) -> Void 26 | } 27 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/SubscriptionsView/Styles/SubscriptionsWrapperViewStyle/ISubscriptionsWrapperViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(watchOS, unavailable) 9 | protocol ISubscriptionsWrapperViewStyle { 10 | /// A view that represents the body of an in-app subscription store control. 11 | associatedtype Body: View 12 | 13 | /// The properties of an in-app subscription store control. 14 | typealias Configuration = SubscriptionsWrapperViewStyleConfiguration 15 | 16 | /// Creates a view that represents the body of an in-app subscription store control. 17 | /// 18 | /// - Parameter configuration: The properties of an in-app subscription store control. 19 | @MainActor 20 | @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body 21 | } 22 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/SubscriptionsView/Styles/SubscriptionsWrapperViewStyle/Styles/Automatic/AutomaticSubscriptionsWrapperViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(watchOS, unavailable) 9 | struct AutomaticSubscriptionsWrapperViewStyle: ISubscriptionsWrapperViewStyle { 10 | func makeBody(configuration: Configuration) -> some View { 11 | #if os(iOS) || os(macOS) 12 | return FullSubscriptionsWrapperViewStyle().makeBody(configuration: configuration) 13 | #else 14 | return CompactSubscriptionWrapperViewStyle().makeBody(configuration: configuration) 15 | #endif 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/SubscriptionsView/Styles/SubscriptionsWrapperViewStyle/Styles/Compact/CompactSubscriptionWrapperViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(tvOS 13.0, *) 9 | @available(macOS, unavailable) 10 | @available(iOS, unavailable) 11 | @available(watchOS, unavailable) 12 | @available(visionOS, unavailable) 13 | struct CompactSubscriptionWrapperViewStyle: ISubscriptionsWrapperViewStyle { 14 | func makeBody(configuration: Configuration) -> some View { 15 | CompactSubscriptionWrapperView(configuration: configuration) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/FlareUI/Classes/Presentation/Views/SubscriptionsView/Styles/SubscriptionsWrapperViewStyle/Styles/Full/FullSubscriptionsWrapperViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | @available(iOS 13.0, macOS 10.15, *) 9 | @available(watchOS, unavailable) 10 | @available(tvOS, unavailable) 11 | struct FullSubscriptionsWrapperViewStyle: ISubscriptionsWrapperViewStyle { 12 | @MainActor 13 | func makeBody(configuration: Configuration) -> some View { 14 | FullSubscriptionsWrapperView(configuration: configuration) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FlareUI/FlareUI.docc/Resources/Images/button_styles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Sources/FlareUI/FlareUI.docc/Resources/Images/button_styles.png -------------------------------------------------------------------------------- /Sources/FlareUI/FlareUI.docc/Resources/Images/content_buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Sources/FlareUI/FlareUI.docc/Resources/Images/content_buttons.png -------------------------------------------------------------------------------- /Sources/FlareUI/FlareUI.docc/Resources/Images/subscription_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Sources/FlareUI/FlareUI.docc/Resources/Images/subscription_view.png -------------------------------------------------------------------------------- /Sources/FlareUI/FlareUI.docc/Resources/Images/subscription_view@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Sources/FlareUI/FlareUI.docc/Resources/Images/subscription_view@2x.png -------------------------------------------------------------------------------- /Sources/FlareUI/Makefile: -------------------------------------------------------------------------------- 1 | swiftgen: 2 | swiftgen -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Assets.xcassets/Colors/gray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "247", 9 | "green" : "242", 10 | "red" : "242" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "30", 27 | "green" : "28", 28 | "red" : "28" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Media.xcassets/Media/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Media.xcassets/Media/checkmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "checkmark.circle.fill.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Media.xcassets/Media/checkmark.imageset/checkmark.circle.fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Media.xcassets/Media/circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "circle.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Media.xcassets/Media/circle.imageset/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/FlareUI/Resources/Assets/Media.xcassets/Media/star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "star.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/FlareUI/swiftgen.yml: -------------------------------------------------------------------------------- 1 | input_dir: Resources 2 | output_dir: Classes/Generated 3 | xcassets: 4 | - inputs: Assets/Assets.xcassets 5 | outputs: 6 | templateName: swift5 7 | output: Colors.swift 8 | params: 9 | publicAccess: false 10 | - inputs: Assets/Media.xcassets 11 | outputs: 12 | templateName: swift5 13 | output: Media.swift 14 | params: 15 | publicAccess: false 16 | enumName: Media 17 | strings: 18 | inputs: 19 | - Localization/en.lproj/Localizable.strings 20 | outputs: 21 | templateName: structured-swift5 22 | output: Strings.swift 23 | params: 24 | publicAccess: false -------------------------------------------------------------------------------- /Sources/FlareUIMock/Mocks/ProductPresenterMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | @testable import FlareUI 8 | import Foundation 9 | 10 | public final class ProductPresenterMock: IProductPresenter { 11 | public init() {} 12 | 13 | public func viewDidLoad() {} 14 | 15 | public var invokedPurchase = false 16 | public var invokedPurchaseCount = 0 17 | public var invokedPurchaseParameters: (options: PurchaseOptions?, Void)? 18 | public var invokedPurchaseParametersList = [(options: PurchaseOptions?, Void)]() 19 | public var stubbedPurchase: StoreTransaction = .fake() 20 | 21 | public func purchase(options: PurchaseOptions?) async throws -> StoreTransaction { 22 | invokedPurchase = true 23 | invokedPurchaseCount += 1 24 | invokedPurchaseParameters = (options, ()) 25 | invokedPurchaseParametersList.append((options, ())) 26 | return stubbedPurchase 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/FlareUIMock/Mocks/ProductsPresenterMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import FlareUI 7 | import Foundation 8 | 9 | public final class ProductsPresenterMock: IProductsPresenter { 10 | public init() {} 11 | 12 | public var invokedViewDidLoad = false 13 | public var invokedViewDidLoadCount = 0 14 | 15 | public func viewDidLoad() { 16 | invokedViewDidLoad = true 17 | invokedViewDidLoadCount += 1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FlareUIMock/Mocks/StoreButtonAssemblyMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import FlareUI 7 | 8 | public final class StoreButtonAssemblyMock: IStoreButtonAssembly { 9 | public init() {} 10 | 11 | public var invokedAssemble = false 12 | public var invokedAssembleCount = 0 13 | public var invokedAssembleParameters: (storeButtonType: StoreButton, Void)? 14 | public var invokedAssembleParametersList = [(storeButtonType: StoreButton, Void)]() 15 | public var stubbedAssembleResult: ViewWrapper! 16 | 17 | public func assemble(storeButtonType: StoreButton) -> ViewWrapper { 18 | invokedAssemble = true 19 | invokedAssembleCount += 1 20 | invokedAssembleParameters = (storeButtonType, ()) 21 | invokedAssembleParametersList.append((storeButtonType, ())) 22 | return stubbedAssembleResult 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/FlareUIMock/Mocks/StoreButtonsAssemblyMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import FlareUI 7 | import SwiftUI 8 | 9 | public final class StoreButtonsAssemblyMock: IStoreButtonsAssembly { 10 | public init() {} 11 | 12 | public var invokedAssemble = false 13 | public var invokedAssembleCount = 0 14 | public var invokedAssembleParameters: (storeButtonType: StoreButtonType, Void)? 15 | public var invokedAssembleParametersList = [(storeButtonType: StoreButtonType, Void)]() 16 | public var stubbedAssembleResult: AnyView! 17 | 18 | public func assemble(storeButtonType: StoreButtonType) -> AnyView { 19 | invokedAssemble = true 20 | invokedAssembleCount += 1 21 | invokedAssembleParameters = (storeButtonType, ()) 22 | invokedAssembleParametersList.append((storeButtonType, ())) 23 | return stubbedAssembleResult 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/Factories/ReceiptRefreshFactoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | import XCTest 9 | 10 | // MARK: - IReceiptRefreshRequestFactoryTests 11 | 12 | final class IReceiptRefreshRequestFactoryTests: XCTestCase { 13 | // MARK: Properties 14 | 15 | private var factory: ReceiptRefreshRequestFactory! 16 | 17 | // MARK: Initialization 18 | 19 | override func setUp() { 20 | super.setUp() 21 | factory = ReceiptRefreshRequestFactory() 22 | } 23 | 24 | override func tearDown() { 25 | factory = nil 26 | super.tearDown() 27 | } 28 | 29 | func test_thatFactoryMakesReceipt() { 30 | // when 31 | let receipt = factory.make(requestID: .requestID, delegate: nil) 32 | 33 | // then 34 | XCTAssertEqual(receipt.id, .requestID) 35 | } 36 | } 37 | 38 | // MARK: - Constants 39 | 40 | private extension String { 41 | static let requestID = "request_id" 42 | } 43 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/Helpers/ProcessInfoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import XCTest 8 | 9 | final class ProcessInfoTests: XCTestCase { 10 | func test_thatProcessInfoReturnsSsRunningUnitTestsEqualsToTrue_whenRuggingUnderTests() { 11 | XCTAssertTrue(ProcessInfo.isRunningUnitTests) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/Models/SKProductTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import FlareMock 8 | import Foundation 9 | import class StoreKit.SKProduct 10 | import XCTest 11 | 12 | // MARK: - SKProductTests 13 | 14 | final class SKProductTests: XCTestCase { 15 | func test_thatSKProductFormatsPriceValueAccoringToLocale() { 16 | // given 17 | let product = SKProductMock() 18 | product.stubbedPrice = NSDecimalNumber(value: UInt.price) 19 | product.stubbedPriceLocale = Locale(identifier: .localeID) 20 | 21 | // when 22 | let localizedPrice = product.localizedPrice 23 | 24 | // then 25 | XCTAssertEqual(localizedPrice, "$100.00") 26 | } 27 | } 28 | 29 | // MARK: - Constants 30 | 31 | private extension UInt { 32 | static let price = 100 33 | } 34 | 35 | private extension String { 36 | static let localeID = "en_US" 37 | } 38 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Extensions/Result+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension Result { 9 | var error: Failure? { 10 | switch self { 11 | case let .failure(error): 12 | return error 13 | default: 14 | return nil 15 | } 16 | } 17 | 18 | var success: Success? { 19 | switch self { 20 | case let .success(value): 21 | return value 22 | default: 23 | return nil 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Extensions/String+Data.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension String { 9 | var asData: Data { 10 | Data(utf8) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Extensions/XCTestCase+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | extension XCTestCase { 9 | func value(for closure: () async throws -> U) async -> U? { 10 | do { 11 | let value = try await closure() 12 | return value 13 | } catch { 14 | return nil 15 | } 16 | } 17 | 18 | func error(for closure: () async throws -> U) async -> T? { 19 | do { 20 | _ = try await closure() 21 | return nil 22 | } catch { 23 | return error as? T 24 | } 25 | } 26 | 27 | func result(for closure: () async throws -> U) async -> Result { 28 | do { 29 | let value = try await closure() 30 | return .success(value) 31 | } catch { 32 | return .failure(error as! T) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Fakes/Configuration+Fake.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | extension Configuration { 10 | static func fake(applicationUsername: String = "username") -> Configuration { 11 | Configuration(applicationUsername: applicationUsername) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Fakes/SKProduct+Fake.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import FlareMock 7 | import Foundation 8 | import StoreKit 9 | 10 | extension SKProduct { 11 | static func fake(id: String) -> SKProduct { 12 | let product = SKProductMock() 13 | product.stubbedProductIdentifier = id 14 | return product 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Fakes/StoreTransactionFake.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | 8 | extension StoreTransaction { 9 | static func fakeSK1(storeTransaction: IStoreTransaction? = nil) -> StoreTransaction { 10 | StoreTransaction(storeTransaction: storeTransaction ?? StoreTransactionStub()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Helpers/AvailabilityChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | enum AvailabilityChecker { 9 | static func iOS15APINotAvailableOrSkipTest() throws { 10 | if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { 11 | throw XCTSkip("Test only for older devices") 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Helpers/WindowSceneFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | #if os(iOS) || VISION_OS 7 | import UIKit 8 | 9 | enum WindowSceneFactory { 10 | @MainActor 11 | static func makeWindowScene() -> UIWindowScene { 12 | UIApplication.shared.connectedScenes.first as! UIWindowScene 13 | } 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/AppStoreReceiptProviderMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class AppStoreReceiptProviderMock: IAppStoreReceiptProvider { 10 | var invokedAppStoreReceiptURLGetter = false 11 | var invokedAppStoreReceiptURLGetterCount = 0 12 | var stubbedAppStoreReceiptURL: URL! 13 | 14 | var appStoreReceiptURL: URL? { 15 | invokedAppStoreReceiptURLGetter = true 16 | invokedAppStoreReceiptURLGetterCount += 1 17 | return stubbedAppStoreReceiptURL 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/CacheProviderMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class CacheProviderMock: ICacheProvider { 10 | var invokedRead = false 11 | var invokedReadCount = 0 12 | var invokedReadParameters: (key: String, Void)? 13 | var invokedReadParametersList = [(key: String, Void)]() 14 | var stubbedReadResult: Any! 15 | 16 | func read(key: String) -> T? { 17 | invokedRead = true 18 | invokedReadCount += 1 19 | invokedReadParameters = (key, ()) 20 | invokedReadParametersList.append((key, ())) 21 | return stubbedReadResult as? T 22 | } 23 | 24 | var invokedWrite = false 25 | var invokedWriteCount = 0 26 | var invokedWriteParameters: (key: String, value: Any)? 27 | var invokedWriteParametersList = [(key: String, value: Any)]() 28 | 29 | func write(key: String, value: T) { 30 | invokedWrite = true 31 | invokedWriteCount += 1 32 | invokedWriteParameters = (key, value) 33 | invokedWriteParametersList.append((key, value)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/EligibilityProviderMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class EligibilityProviderMock: IEligibilityProvider { 10 | var invokedCheckEligibility = false 11 | var invokedCheckEligibilityCount = 0 12 | var invokedCheckEligibilityParameters: (products: [StoreProduct], Void)? 13 | var invokedCheckEligibilityParametersList = [(products: [StoreProduct], Void)]() 14 | var stubbedCheckEligibility: [String: SubscriptionEligibility] = [:] 15 | 16 | func checkEligibility(products: [StoreProduct]) async throws -> [String: SubscriptionEligibility] { 17 | invokedCheckEligibility = true 18 | invokedCheckEligibilityCount += 1 19 | invokedCheckEligibilityParameters = (products, ()) 20 | invokedCheckEligibilityParametersList.append((products, ())) 21 | return stubbedCheckEligibility 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/FileManagerMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class FileManagerMock: IFileManager { 10 | var invokedFileExists = false 11 | var invokedFileExistsCount = 0 12 | var invokedFileExistsParameters: (path: String, Void)? 13 | var invokedFileExistsParametersList = [(path: String, Void)]() 14 | var stubbedFileExistsResult: Bool! = false 15 | 16 | func fileExists(atPath path: String) -> Bool { 17 | invokedFileExists = true 18 | invokedFileExistsCount += 1 19 | invokedFileExistsParameters = (path, ()) 20 | invokedFileExistsParametersList.append((path, ())) 21 | return stubbedFileExistsResult 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/FlareDependenciesMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class FlareDependenciesMock: IFlareDependencies { 10 | var invokedIapProviderGetter = false 11 | var invokedIapProviderGetterCount = 0 12 | var stubbedIapProvider: IIAPProvider! 13 | 14 | var iapProvider: IIAPProvider { 15 | invokedIapProviderGetter = true 16 | invokedIapProviderGetterCount += 1 17 | return stubbedIapProvider 18 | } 19 | 20 | var invokedConfigurationProviderGetter = false 21 | var invokedConfigurationProviderGetterCount = 0 22 | var stubbedConfigurationProvider: IConfigurationProvider! 23 | 24 | var configurationProvider: IConfigurationProvider { 25 | invokedConfigurationProviderGetter = true 26 | invokedConfigurationProviderGetterCount += 1 27 | return stubbedConfigurationProvider 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/ProductResponseMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | final class ProductResponseMock: SKProductsResponse, @unchecked Sendable { 9 | var invokedInvalidProductsIdentifiers = false 10 | var invokedInvalidProductsIdentifiersCount = 0 11 | var stubbedInvokedInvalidProductsIdentifiers: [String] = [] 12 | 13 | override var invalidProductIdentifiers: [String] { 14 | invokedInvalidProductsIdentifiers = true 15 | invokedInvalidProductsIdentifiersCount += 1 16 | return stubbedInvokedInvalidProductsIdentifiers 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/ProductsRequestMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | final class ProductsRequestMock: SKProductsRequest, @unchecked Sendable { 9 | var invokedStart = false 10 | var invokedStartCount = 0 11 | 12 | override func start() { 13 | invokedStart = true 14 | invokedStartCount += 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/ReceiptRefreshRequestFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | import protocol StoreKit.SKRequestDelegate 9 | 10 | final class ReceiptRefreshRequestFactoryMock: IReceiptRefreshRequestFactory { 11 | var invokedMake = false 12 | var invokedMakeCount = 0 13 | var invokedMakeParameters: (requestID: String, delegate: SKRequestDelegate?)? 14 | var invokedMakeParametersList = [(requestID: String, delegate: SKRequestDelegate?)]() 15 | var stubbedMakeResult: IReceiptRefreshRequest! 16 | 17 | func make(requestID: String, delegate: SKRequestDelegate?) -> IReceiptRefreshRequest { 18 | invokedMake = true 19 | invokedMakeCount += 1 20 | invokedMakeParameters = (requestID, delegate) 21 | invokedMakeParametersList.append((requestID, delegate)) 22 | return stubbedMakeResult 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/ReceiptRefreshRequestMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class ReceiptRefreshRequestMock: IReceiptRefreshRequest, @unchecked Sendable { 10 | var invokedIdSetter = false 11 | var invokedIdSetterCount = 0 12 | var invokedId: String? 13 | var invokedIdList = [String]() 14 | var invokedIdGetter = false 15 | var invokedIdGetterCount = 0 16 | var stubbedId: String! = "" 17 | 18 | var id: String { 19 | set { 20 | invokedIdSetter = true 21 | invokedIdSetterCount += 1 22 | invokedId = newValue 23 | invokedIdList.append(newValue) 24 | } 25 | get { 26 | invokedIdGetter = true 27 | invokedIdGetterCount += 1 28 | return stubbedId 29 | } 30 | } 31 | 32 | var invokedStart = false 33 | var invokedStartCount = 0 34 | var stubbedStartAction: (() -> Void)? 35 | 36 | func start() { 37 | invokedStart = true 38 | invokedStartCount += 1 39 | stubbedStartAction?() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/RedeemCodeProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class RedeemCodeProviderMock: IRedeemCodeProvider { 10 | var invokedPresentOfferCodeRedeemSheet = false 11 | var invokedPresentOfferCodeRedeemSheetCount = 0 12 | 13 | func presentOfferCodeRedeemSheet() async { 14 | invokedPresentOfferCodeRedeemSheet = true 15 | invokedPresentOfferCodeRedeemSheetCount += 1 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/RefundProviderMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | 8 | final class RefundProviderMock: IRefundProvider { 9 | var invokedBeginRefundRequest = false 10 | var invokedBeginRefundRequestCount = 0 11 | var invokedBeginRefundRequestParameters: (productID: String, Void)? 12 | var invokedBeginRefundRequestParametersList = [(productID: String, Void)]() 13 | var stubbedBeginRefundRequest: RefundRequestStatus! 14 | 15 | func beginRefundRequest(productID: String) async throws -> RefundRequestStatus { 16 | invokedBeginRefundRequest = true 17 | invokedBeginRefundRequestCount += 1 18 | invokedBeginRefundRequestParameters = (productID, ()) 19 | invokedBeginRefundRequestParametersList.append((productID, ())) 20 | return stubbedBeginRefundRequest 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/SKProductMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | import StoreKit 7 | 8 | final class SKProductMock: SKProduct, @unchecked Sendable { 9 | var invokedProductIdentifier = false 10 | var invokedProductIdentifierCount = 0 11 | var stubbedProductIdentifier: String = "product_id" 12 | 13 | override var productIdentifier: String { 14 | invokedProductIdentifier = true 15 | invokedProductIdentifierCount += 1 16 | return stubbedProductIdentifier 17 | } 18 | 19 | var stubbedPriceLocale: Locale = .autoupdatingCurrent 20 | 21 | override var priceLocale: Locale { 22 | stubbedPriceLocale 23 | } 24 | 25 | var stubbedPrice: NSDecimalNumber! 26 | 27 | override var price: NSDecimalNumber { 28 | stubbedPrice 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/ScenesHolderMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | #if canImport(UIKit) 8 | import UIKit 9 | #endif 10 | 11 | // MARK: - ScenesHolderMock 12 | 13 | final class ScenesHolderMock: IScenesHolder, @unchecked Sendable { 14 | #if os(iOS) || VISION_OS 15 | var invokedConnectedScenesGetter = false 16 | var invokedConnectedScenesGetterCount = 0 17 | var stubbedConnectedScenes: Set! = [] 18 | 19 | var connectedScenes: Set { 20 | invokedConnectedScenesGetter = true 21 | invokedConnectedScenesGetterCount += 1 22 | return stubbedConnectedScenes 23 | } 24 | #endif 25 | } 26 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/SystemInfoProviderMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2023 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | #if canImport(UIKit) 8 | import UIKit 9 | #endif 10 | 11 | // MARK: - SystemInfoProviderMock 12 | 13 | final class SystemInfoProviderMock: ISystemInfoProvider, @unchecked Sendable { 14 | #if os(iOS) || VISION_OS 15 | var invokedCurrentSceneGetter = false 16 | var invokedCurrentSceneGetterCount = 0 17 | var stubbedCurrentScene: Result! 18 | 19 | var currentScene: UIWindowScene { 20 | get throws { 21 | invokedCurrentSceneGetter = true 22 | invokedCurrentSceneGetterCount += 1 23 | switch stubbedCurrentScene { 24 | case let .success(scene): 25 | return scene 26 | case let .failure(error): 27 | throw error 28 | default: 29 | fatalError() 30 | } 31 | } 32 | } 33 | #endif 34 | } 35 | -------------------------------------------------------------------------------- /Tests/FlareTests/UnitTests/TestHelpers/Mocks/UserDefaultsMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import Flare 7 | import Foundation 8 | 9 | final class UserDefaultsMock: IUserDefaults { 10 | var invokedSet = false 11 | var invokedSetCount = 0 12 | var invokedSetParameters: (key: String, codable: Any)? 13 | var invokedSetParametersList = [(key: String, codable: Any)]() 14 | 15 | func set(key: String, codable: T) { 16 | invokedSet = true 17 | invokedSetCount += 1 18 | invokedSetParameters = (key, codable) 19 | invokedSetParametersList.append((key, codable)) 20 | } 21 | 22 | var invokedGet = false 23 | var invokedGetCount = 0 24 | var invokedGetParameters: (key: String, Void)? 25 | var invokedGetParametersList = [(key: String, Void)]() 26 | var stubbedGetResult: Any! 27 | 28 | func get(key: String) -> T? { 29 | invokedGet = true 30 | invokedGetCount += 1 31 | invokedGetParameters = (key, ()) 32 | invokedGetParametersList.append((key, ())) 33 | return stubbedGetResult as? T 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Core/Extensions/ArrayExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import FlareUI 7 | import XCTest 8 | 9 | final class ArrayExtensionsTests: XCTestCase { 10 | func test_thatArrayRemovesDuplicates() { 11 | // given 12 | let array = [10, 10, 9, 1, 3, 3, 7, 8, 7, 7, 7] 13 | 14 | // when 15 | let filteredArray = array.removingDuplicates() 16 | 17 | // then 18 | XCTAssertEqual(filteredArray, [10, 9, 1, 3, 7, 8]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Fakes/SubscriptionView.ViewModel+Fake.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import FlareUI 7 | import Foundation 8 | 9 | @available(watchOS, unavailable) 10 | extension SubscriptionView.ViewModel { 11 | static func fake(id: String? = nil) -> SubscriptionView.ViewModel { 12 | SubscriptionView.ViewModel( 13 | id: id ?? UUID().uuidString, 14 | title: "Title", 15 | price: "5,99$", 16 | description: "Description", 17 | isActive: true 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Helpers/XCTestCase+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | extension XCTestCase { 9 | func value(for closure: () async throws -> U) async -> U? { 10 | do { 11 | let value = try await closure() 12 | return value 13 | } catch { 14 | return nil 15 | } 16 | } 17 | 18 | func error(for closure: () async throws -> U) async -> T? { 19 | do { 20 | _ = try await closure() 21 | return nil 22 | } catch { 23 | return error as? T 24 | } 25 | } 26 | 27 | func result(for closure: () async throws -> U) async -> Result { 28 | do { 29 | let value = try await closure() 30 | return .success(value) 31 | } catch { 32 | return .failure(error as! T) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Helpers/XCTestCase+Wait.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | extension XCTestCase { 9 | func wait( 10 | _ condition: @escaping @autoclosure () -> (Bool), 11 | timeout: TimeInterval = 10 12 | ) { 13 | wait( 14 | for: [ 15 | XCTNSPredicateExpectation( 16 | predicate: NSPredicate(block: { _, _ in condition() }), object: nil 17 | ), 18 | ], 19 | timeout: timeout 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Mocks/ProductFetcherMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | @testable import FlareUI 8 | import Foundation 9 | 10 | final class ProductFetcherMock: IProductFetcherStrategy { 11 | var invokedProduct = false 12 | var invokedProductCount = 0 13 | var stubbedThrowProduct: Error? 14 | var stubbedProduct: StoreProduct! 15 | 16 | func product() async throws -> StoreProduct { 17 | invokedProduct = true 18 | invokedProductCount += 1 19 | if let stubbedThrowProduct = stubbedThrowProduct { 20 | throw stubbedThrowProduct 21 | } 22 | return stubbedProduct 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Mocks/ProductPurchaseServiceMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | @testable import FlareUI 8 | 9 | final class ProductPurchaseServiceMock: IProductPurchaseService { 10 | var invokedPurchase = false 11 | var invokedPurchaseCount = 0 12 | var invokedPurchaseParameters: (product: StoreProduct, options: PurchaseOptions?)? 13 | var invokedPurchaseParametersList = [(product: StoreProduct, options: PurchaseOptions?)]() 14 | var stubbedPurchaseError: Error? 15 | var stubbedPurchase: StoreTransaction = .fake() 16 | 17 | func purchase(product: StoreProduct, options: PurchaseOptions?) async throws -> StoreTransaction { 18 | invokedPurchase = true 19 | invokedPurchaseCount += 1 20 | invokedPurchaseParameters = (product, options) 21 | invokedPurchaseParametersList.append((product, options)) 22 | if let stubbedPurchaseError { 23 | throw stubbedPurchaseError 24 | } 25 | return stubbedPurchase 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Mocks/SubscriptionDateComponentsFactoryMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | @testable import FlareUI 8 | import Foundation 9 | 10 | final class SubscriptionDateComponentsFactoryMock: ISubscriptionDateComponentsFactory { 11 | var invokedDateComponents = false 12 | var invokedDateComponentsCount = 0 13 | var invokedDateComponentsParameters: (subscription: SubscriptionPeriod, Void)? 14 | var invokedDateComponentsParametersList = [(subscription: SubscriptionPeriod, Void)]() 15 | var stubbedDateComponentsResult: DateComponents! 16 | 17 | func dateComponents(for subscription: SubscriptionPeriod) -> DateComponents { 18 | invokedDateComponents = true 19 | invokedDateComponentsCount += 1 20 | invokedDateComponentsParameters = (subscription, ()) 21 | invokedDateComponentsParametersList.append((subscription, ())) 22 | return stubbedDateComponentsResult 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Mocks/SubscriptionsViewModelViewFactoryMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Flare 7 | @testable import FlareUI 8 | 9 | @available(watchOS, unavailable) 10 | final class SubscriptionsViewModelViewFactoryMock: ISubscriptionsViewModelViewFactory { 11 | var invokedMake = false 12 | var invokedMakeCount = 0 13 | var invokedMakeParameters: (products: [StoreProduct], Void)? 14 | var invokedMakeParametersList = [(products: [StoreProduct], Void)]() 15 | var stubbedMake: [SubscriptionView.ViewModel] = [] 16 | 17 | func make(_ products: [StoreProduct]) async throws -> [SubscriptionView.ViewModel] { 18 | invokedMake = true 19 | invokedMakeCount += 1 20 | invokedMakeParameters = (products, ()) 21 | invokedMakeParametersList.append((products, ())) 22 | return stubbedMake 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/FlareUITests/UnitTests/Presentation/StoreButton/StoreButtonPresenterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | @testable import FlareUI 7 | import FlareUIMock 8 | import XCTest 9 | 10 | final class StoreButtonPresenterTests: XCTestCase { 11 | // MARK: Properties 12 | 13 | private var iapMock: FlareMock! 14 | 15 | private var sut: StoreButtonPresenter! 16 | 17 | // MARK: XCTestCase 18 | 19 | override func setUp() { 20 | super.setUp() 21 | iapMock = FlareMock() 22 | sut = StoreButtonPresenter(iap: iapMock) 23 | } 24 | 25 | override func tearDown() { 26 | iapMock = nil 27 | sut = nil 28 | super.tearDown() 29 | } 30 | 31 | // MARK: Tests 32 | 33 | @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) 34 | func test_thatPresenterRestoresTransactions() async throws { 35 | // when 36 | try await sut.restore() 37 | 38 | // then 39 | XCTAssertEqual(iapMock.invokedRestoreCount, 1) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/Helpers/Extensions/AsyncSequence+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) 9 | extension AsyncSequence { 10 | /// Returns the elements of the asynchronous sequence. 11 | func extractValues() async rethrows -> [Element] { 12 | try await reduce(into: []) { 13 | $0.append($1) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/Helpers/Extensions/Result+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | extension Result { 9 | var error: Failure? { 10 | switch self { 11 | case let .failure(error): 12 | return error 13 | default: 14 | return nil 15 | } 16 | } 17 | 18 | var success: Success? { 19 | switch self { 20 | case let .success(value): 21 | return value 22 | default: 23 | return nil 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/Helpers/Extensions/XCTestCase+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | 8 | extension XCTestCase { 9 | func value(for closure: () async throws -> U) async -> U? { 10 | do { 11 | let value = try await closure() 12 | return value 13 | } catch { 14 | return nil 15 | } 16 | } 17 | 18 | func error(for closure: () async throws -> U) async -> T? { 19 | do { 20 | _ = try await closure() 21 | return nil 22 | } catch { 23 | return error as? T 24 | } 25 | } 26 | 27 | func result(for closure: () async throws -> U) async -> Result { 28 | do { 29 | let value = try await closure() 30 | return .success(value) 31 | } catch { 32 | return .failure(error as! T) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/SnapshotTests/Helpers/ThemableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | #if os(macOS) 7 | import SwiftUI 8 | 9 | final class ThemableView: NSHostingView { 10 | required init(rootView: Content, appearance: NSAppearance?) { 11 | super.init(rootView: rootView) 12 | self.appearance = appearance 13 | } 14 | 15 | @available(*, unavailable) 16 | required init?(coder _: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | @MainActor required init(rootView _: Content) { 21 | fatalError("init(rootView:) has not been implemented") 22 | } 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNil-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNil-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNil-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNil-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNil-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNil-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNotNil-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNotNil-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNotNil-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNotNil-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNotNil-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_compactStyle_whenIconIsNotNil-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_largeStyle_whenIconIsNil-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_largeStyle_whenIconIsNil-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_largeStyle_whenIconIsNotNil-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductInfoViewSnapshotTests/test_productInfoView_largeStyle_whenIconIsNotNil-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsHidden-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsHidden-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsHidden-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsHidden-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsHidden-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsHidden-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsVisible-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsVisible-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsVisible-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsVisible-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsVisible-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_compactStyle_whenIconIsVisible-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_largeStyle_whenIconIsHidden-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_largeStyle_whenIconIsHidden-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_largeStyle_whenIconIsVisible-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_largeStyle_whenIconIsVisible-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsHidden-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsHidden-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsHidden-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsHidden-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsVisible-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsVisible-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsVisible-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductPlaceholderViewSnapshotTests/test_productPlaceholderView_whenIconIsVisible-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_customStyle_product-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_customStyle_product-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_customStyle_product-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_customStyle_product-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_customStyle_product-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_customStyle_product-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_error-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_error-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_error-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_error-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_error-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_error-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_loading-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_loading-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_loading-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_loading-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_loading-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_loading-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_product-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_product-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_product-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_product-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_product-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductViewSnapshotTests/test_productView_product-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_error-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_error-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_error-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_error-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_error-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_error-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products_withRestoreButtons-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products_withRestoreButtons-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products_withRestoreButtons-macOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products_withRestoreButtons-macOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products_withRestoreButtons-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/ProductsViewSnapshotTests/test_productsView_products_withRestoreButtons-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_customStyle-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_customStyle-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_customStyle-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_customStyle-tvOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_defaultStyle-iOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_defaultStyle-iOS.1.png -------------------------------------------------------------------------------- /Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_defaultStyle-tvOS.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/space-code/flare/9be161fe72f6af3965d18973df9f9476f4319a8d/Tests/SnapshotTests/__Snapshots__/SubscriptionsViewSnapshotTests/test_subscriptionsView_defaultStyle-tvOS.1.png -------------------------------------------------------------------------------- /Tests/TestPlans/AllTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "1CA61D79-6551-4DA7-8DEF-CC28563C2658", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : { 13 | "targets" : [ 14 | { 15 | "containerPath" : "container:Flare.xcodeproj", 16 | "identifier" : "AAEFF2D6694AA197C07481DA", 17 | "name" : "Flare" 18 | } 19 | ] 20 | }, 21 | "targetForVariableExpansion" : { 22 | "containerPath" : "container:Flare.xcodeproj", 23 | "identifier" : "AAEFF2D6694AA197C07481DA", 24 | "name" : "Flare" 25 | } 26 | }, 27 | "testTargets" : [ 28 | { 29 | "target" : { 30 | "containerPath" : "container:Flare.xcodeproj", 31 | "identifier" : "2053CB2B5F4780EC86D0DE04", 32 | "name" : "FlareTests" 33 | } 34 | }, 35 | { 36 | "target" : { 37 | "containerPath" : "container:Flare.xcodeproj", 38 | "identifier" : "5A649E8F4319C5B59E9588FD", 39 | "name" : "IntegrationTests" 40 | } 41 | } 42 | ], 43 | "version" : 1 44 | } 45 | -------------------------------------------------------------------------------- /Tests/TestPlans/FlareUIUnitTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "982AD05B-EBD5-4A98-A373-D1868847261D", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : { 13 | "targets" : [ 14 | { 15 | "containerPath" : "container:Flare.xcodeproj", 16 | "identifier" : "8D5BAE0D59CA24F5E8E0C695", 17 | "name" : "FlareUI" 18 | } 19 | ] 20 | }, 21 | "targetForVariableExpansion" : { 22 | "containerPath" : "container:Flare.xcodeproj", 23 | "identifier" : "8D5BAE0D59CA24F5E8E0C695", 24 | "name" : "FlareUI" 25 | } 26 | }, 27 | "testTargets" : [ 28 | { 29 | "skippedTests" : [ 30 | "ProductInfoViewSnapshotTests", 31 | "ProductPlaceholderViewSnapshotTests", 32 | "ProductViewSnapshotTests", 33 | "ProductsViewSnapshotTests", 34 | "SnapshotTestCase" 35 | ], 36 | "target" : { 37 | "containerPath" : "container:Flare.xcodeproj", 38 | "identifier" : "514A62DAD52F32058E8084C4", 39 | "name" : "FlareUITests" 40 | } 41 | } 42 | ], 43 | "version" : 1 44 | } 45 | -------------------------------------------------------------------------------- /Tests/TestPlans/IntegrationTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "1CA61D79-6551-4DA7-8DEF-CC28563C2658", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : { 13 | "targets" : [ 14 | { 15 | "containerPath" : "container:Flare.xcodeproj", 16 | "identifier" : "AAEFF2D6694AA197C07481DA", 17 | "name" : "Flare" 18 | } 19 | ] 20 | }, 21 | "targetForVariableExpansion" : { 22 | "containerPath" : "container:Flare.xcodeproj", 23 | "identifier" : "AAEFF2D6694AA197C07481DA", 24 | "name" : "Flare" 25 | } 26 | }, 27 | "testTargets" : [ 28 | { 29 | "target" : { 30 | "containerPath" : "container:Flare.xcodeproj", 31 | "identifier" : "5A649E8F4319C5B59E9588FD", 32 | "name" : "IntegrationTests" 33 | } 34 | } 35 | ], 36 | "version" : 1 37 | } 38 | -------------------------------------------------------------------------------- /Tests/TestPlans/SnapshotTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "982AD05B-EBD5-4A98-A373-D1868847261D", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : { 13 | "targets" : [ 14 | { 15 | "containerPath" : "container:Flare.xcodeproj", 16 | "identifier" : "8D5BAE0D59CA24F5E8E0C695", 17 | "name" : "FlareUI" 18 | } 19 | ] 20 | }, 21 | "targetForVariableExpansion" : { 22 | "containerPath" : "container:Flare.xcodeproj", 23 | "identifier" : "8D5BAE0D59CA24F5E8E0C695", 24 | "name" : "FlareUI" 25 | } 26 | }, 27 | "testTargets" : [ 28 | { 29 | "target" : { 30 | "containerPath" : "container:Flare.xcodeproj", 31 | "identifier" : "12A0F956FEAD55CFA5B3AF45", 32 | "name" : "FlareUISnapshotTests" 33 | } 34 | } 35 | ], 36 | "version" : 1 37 | } 38 | -------------------------------------------------------------------------------- /Tests/TestPlans/UnitTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "1CA61D79-6551-4DA7-8DEF-CC28563C2658", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : { 13 | "targets" : [ 14 | { 15 | "containerPath" : "container:Flare.xcodeproj", 16 | "identifier" : "AAEFF2D6694AA197C07481DA", 17 | "name" : "Flare" 18 | } 19 | ] 20 | }, 21 | "targetForVariableExpansion" : { 22 | "containerPath" : "container:Flare.xcodeproj", 23 | "identifier" : "AAEFF2D6694AA197C07481DA", 24 | "name" : "Flare" 25 | } 26 | }, 27 | "testTargets" : [ 28 | { 29 | "target" : { 30 | "containerPath" : "container:Flare.xcodeproj", 31 | "identifier" : "2053CB2B5F4780EC86D0DE04", 32 | "name" : "FlareTests" 33 | } 34 | } 35 | ], 36 | "version" : 1 37 | } 38 | -------------------------------------------------------------------------------- /Tests/UnitTestHostApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flare 3 | // Copyright © 2024 Space Code. All rights reserved. 4 | // 5 | 6 | import SwiftUI 7 | 8 | #if os(macOS) 9 | 10 | import Cocoa 11 | 12 | @main 13 | class AppDelegate: NSObject, NSApplicationDelegate {} 14 | 15 | #elseif os(watchOS) 16 | 17 | @main 18 | struct TestApp: App { 19 | var body: some Scene { 20 | WindowGroup { 21 | Text("Hello World") 22 | } 23 | } 24 | } 25 | 26 | #else 27 | @main 28 | class AppDelegate: UIResponder, UIApplicationDelegate {} 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /Tests/UnitTestHostApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/UnitTestHostApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /scripts/setup_build_tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | which -s xcodegen 4 | if [[ $? != 0 ]] ; then 5 | # Install xcodegen 6 | echo "Installing xcodegen." 7 | brew install xcodegen 8 | fi --------------------------------------------------------------------------------