├── .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 |
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 |
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
--------------------------------------------------------------------------------