diff --git a/.github/workflows/build-example.yml b/.github/workflows/build-example.yml
new file mode 100644
index 0000000..f2e7923
--- /dev/null
+++ b/.github/workflows/build-example.yml
@@ -0,0 +1,32 @@
+name: Build Example
+
+on:
+ push:
+ branches: [ develop ]
+ pull_request:
+ branches: [ main ]
+
+env:
+ DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer
+ PROJECT_DIR: Example
+ PROJECT_NAME: Example.xcodeproj
+ iOSSCHEME: Example
+
+jobs:
+
+ example:
+ name: Run examples
+ runs-on: macOS-latest
+ strategy:
+ matrix:
+ iosDestination: ['platform=iOS Simulator,OS=16,name=iPhone X','platform=iOS Simulator,OS=17.0,name=iPhone 14']
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Build iOS
+ run: |
+ xcodebuild clean build -project "${{ env.PROJECT_DIR }}/${{ env.PROJECT_NAME }}" -scheme "${{ env.iOSSCHEME }}" | xcpretty
+ env:
+ destination: ${{ matrix.iosDestination }}
diff --git a/.swiftformat b/.swiftformat
index 2f6ed6f..21f72f9 100644
--- a/.swiftformat
+++ b/.swiftformat
@@ -1,2 +1,2 @@
---swiftversion 5.7
+--swiftversion 5.8
--disable preferKeyPath
\ No newline at end of file
diff --git a/Example/.gitignore b/Example/.gitignore
new file mode 100644
index 0000000..4d62f3e
--- /dev/null
+++ b/Example/.gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+/.idea/
\ No newline at end of file
diff --git a/Example/.swiftformat b/Example/.swiftformat
new file mode 100644
index 0000000..2f6ed6f
--- /dev/null
+++ b/Example/.swiftformat
@@ -0,0 +1,2 @@
+--swiftversion 5.7
+--disable preferKeyPath
\ No newline at end of file
diff --git a/Example/.swiftlint.yml b/Example/.swiftlint.yml
new file mode 100644
index 0000000..9f9669f
--- /dev/null
+++ b/Example/.swiftlint.yml
@@ -0,0 +1,27 @@
+excluded: # paths to ignore during linting. Takes precedence over `included`.
+ - Carthage
+ - Pods
+opt_in_rules:
+ - empty_count
+ - file_header
+ - explicit_init
+ - closure_spacing
+ - overridden_super_call
+ - redundant_nil_coalescing
+ - private_outlet
+ - nimble_operator
+ - attributes
+ - operator_usage_whitespace
+ - first_where
+ - object_literal
+ - number_separator
+ - prohibited_super_call
+ - fatal_error_message
+ - vertical_parameter_alignment_on_call
+ - let_var_whitespace
+identifier_name:
+ excluded:
+ - id
+line_length: 196
+number_separator:
+ minimum_length: 5
diff --git a/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..5b26f04
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.961",
+ "green" : "0.537",
+ "red" : "0.173"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AlternateAppIcon1.appiconset/Contents.json b/Example/Example/Assets.xcassets/AlternateAppIcon1.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AlternateAppIcon1.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AlternateAppIcon2.appiconset/Contents.json b/Example/Example/Assets.xcassets/AlternateAppIcon2.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AlternateAppIcon2.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AlternateAppIcon3.appiconset/Contents.json b/Example/Example/Assets.xcassets/AlternateAppIcon3.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AlternateAppIcon3.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AlternateAppIcon4.appiconset/Contents.json b/Example/Example/Assets.xcassets/AlternateAppIcon4.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AlternateAppIcon4.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AlternateAppIcon5.appiconset/Contents.json b/Example/Example/Assets.xcassets/AlternateAppIcon5.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AlternateAppIcon5.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AlternateAppIcon6.appiconset/Contents.json b/Example/Example/Assets.xcassets/AlternateAppIcon6.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AlternateAppIcon6.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AlternateAppIcon7.appiconset/Contents.json b/Example/Example/Assets.xcassets/AlternateAppIcon7.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AlternateAppIcon7.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..532cd72
--- /dev/null
+++ b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,63 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Assets.xcassets/Contents.json b/Example/Example/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Example/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Example/Example/Assets.xcassets/OnbardingBackground.imageset/Contents.json b/Example/Example/Assets.xcassets/OnbardingBackground.imageset/Contents.json
new file mode 100644
index 0000000..409cb43
--- /dev/null
+++ b/Example/Example/Assets.xcassets/OnbardingBackground.imageset/Contents.json
@@ -0,0 +1,36 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/ExampleApp.swift b/Example/Example/ExampleApp.swift
new file mode 100644
index 0000000..498cb60
--- /dev/null
+++ b/Example/Example/ExampleApp.swift
@@ -0,0 +1,120 @@
+//
+// Copyright © 2023 Alexander Romanov
+// ExampleApp.swift, created on 25.09.2023
+//
+
+import Factory
+import OversizeKit
+import OversizeServices
+import OversizeUI
+import SwiftUI
+
+@main
+struct ExampleApp: App {
+
+ @Injected(\.appStateService) var appStateService: AppStateService
+ @ObservedObject private var router = Router()
+ @StateObject private var appSettingsViewModel = AppSettingsViewModel()
+ let pub = NotificationCenter.default.publisher(for: NSNotification.Name("Deeplink"))
+
+ var body: some Scene {
+ WindowGroup {
+ TabView(selection: $router.tab) {
+ NavigationStack(path: $router.mainPath) {
+ MainView()
+ .navigationDestination(for: Screen.self) { destination in
+ router.resolve(pathItem: destination)
+ }
+ }
+ .tag(RootTab.main)
+ .tabItem {
+ RootTab.main.image
+ .renderingMode(.template)
+ Text(RootTab.main.title)
+ }
+
+ NavigationStack(path: $router.secondaryPath) {
+ EmptyView()
+ .navigationDestination(for: Screen.self) { destination in
+ router.resolve(pathItem: destination)
+ }
+ }
+ .tag(RootTab.secondary)
+ .tabItem {
+ RootTab.secondary.image
+ .renderingMode(.template)
+ Text(RootTab.secondary.title)
+ }
+
+ NavigationStack(path: $router.tertiaryPath) {
+ EmptyView()
+ .navigationDestination(for: Screen.self) { destination in
+ router.resolve(pathItem: destination)
+ }
+ }
+ .tag(RootTab.tertiary)
+ .tabItem {
+ RootTab.tertiary.image
+ .renderingMode(.template)
+ Text(RootTab.tertiary.title)
+ }
+
+ NavigationStack(path: $router.quaternaryPath) {
+ EmptyView()
+ .navigationDestination(for: Screen.self) { destination in
+ router.resolve(pathItem: destination)
+ }
+ }
+ .tag(RootTab.quaternary)
+ .tabItem {
+ RootTab.quaternary.image
+ .renderingMode(.template)
+ Text(RootTab.quaternary.title)
+ }
+
+ NavigationStack(path: $router.settingsPath) {
+ SettingsView {
+ AppSettingsView()
+ }
+ .navigationDestination(for: Screen.self) { destination in
+ router.resolve(pathItem: destination)
+ }
+ }
+ .tag(RootTab.settings)
+ .tabItem {
+ RootTab.settings.image
+ .renderingMode(.template)
+ Text(RootTab.settings.title)
+ }
+ }
+ .appLaunch {
+ OnboardingView()
+ }
+ .hud(router.hudText, isPresented: $router.isShowHud)
+ .sheet(item: $router.sheet) { sheet in
+ router.resolveSheet(pathItem: sheet, detents: router.sheetDetents, dragIndicator: router.dragIndicator, dismissDisabled: router.dismissDisabled)
+ .hud(router.hudText, isPresented: $router.isShowHud)
+ .alert(item: $router.alert) { $0.alert }
+ .environmentObject(appSettingsViewModel)
+ .systemServices()
+ }
+ .fullScreenCover(item: $router.fullScreenCover) { fullScreenCover in
+ router.resolve(pathItem: fullScreenCover)
+ .hud(router.hudText, isPresented: $router.isShowHud)
+ .alert(item: $router.alert) { $0.alert }
+ .environmentObject(appSettingsViewModel)
+ .systemServices()
+ }
+ .alert(item: $router.alert) { $0.alert }
+ .onOpenURL { router.handle($0) }
+ .environmentObject(router)
+ .environmentObject(appSettingsViewModel)
+ .onReceive(pub) { output in
+ if let userInfo = output.userInfo, let info = userInfo["link"] {
+ let url = URL(string: info as! String)!
+ router.handle(url)
+ }
+ }
+ }
+ }
+}
diff --git a/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Resources/AppConfig.plist b/Example/Example/Resources/AppConfig.plist
new file mode 100644
index 0000000..55a1ad6
--- /dev/null
+++ b/Example/Example/Resources/AppConfig.plist
@@ -0,0 +1,258 @@
+
+
+
+
+ Links
+
+ App
+
+ Url
+ https://oversize.app/example
+ AppStoreID
+
+ TelegramChat
+
+
+ Developer
+
+ Name
+ Alexander Romanov
+ Url
+ romanov.cc
+ Email
+ hello@oversize.app
+ Fecebook
+
+ Telegram
+
+
+ Company
+
+ Name
+ Oversize
+ Url
+ https://oversize.app
+ CDNUrl
+ https://cdn.oversize.app
+ Email
+ support@oversize.app
+ Facebook
+ oversizedev
+ Telegram
+ oversizeapp
+ Dribbble
+ oversizedesign
+ Instagram
+ oversize.design
+ Twitter
+ oversizedesign
+ AppStoreID
+ 1459928735
+
+
+ FeatureFlags
+
+ Onboarding
+
+ Apperance
+
+ StoreKit
+
+ CloudKit
+
+ HealthKit
+
+ FaceID
+
+ FaceIDSecureCodes
+
+ Notifications
+
+ Lookscreen
+
+ Vibration
+
+ Sounds
+
+ Spotlight
+
+ BlurMinimize
+
+ AlertPINCode
+
+ CVVCodes
+
+ BruteForceSecure
+
+ PhotoBreaker
+
+ AlternateAppIcons
+ 7
+ PremiumFeatures
+
+ BlurMinimize
+
+ Lookscreen
+
+
+
+ Store
+
+ BannerLabel
+ Store.BannerLabelDressWeathrt
+ SubscriptionsName
+ Pro
+ SubscriptionsDescription
+ No limits, without Ads, iСloud, application design setting and much more
+ ProductIdentifiers
+
+ app.oversize.Example.monthly
+ app.oversize.Example.yearly
+ app.oversize.Example.forever
+ app.oversize.Example.offerYearly
+
+ Features
+
+
+ image
+ Solid/ShoppingandEcommerce/Megaphone
+ title
+ No Ads
+ subtitle
+ Advertising will not be displayed
+ topScreenAlignment
+
+ illustrationURL
+ https://cdn.oversize.design/assets/illustrations/objects/tools/speaker/large.png
+ backgroundColor
+ FF7A20
+
+
+ image
+ Solid/DateandTime/Calendar02
+ title
+ No limits for subtasks
+ subtitle
+ Save any number of subtasks
+ illustrationURL
+ https://cdn.oversize.design/assets/illustrations/objects/education/calendar-b/large.png
+ backgroundColor
+ FB616B
+
+
+ image
+ Solid/UserInterface/Lock
+ title
+ PIN code
+ subtitle
+ Increase the security of personal data
+ illustrationURL
+ https://cdn.oversize.design/assets/illustrations/objects/houseware/lock-a/large.png
+ backgroundColor
+ FF5795
+
+
+ image
+ Solid/Weather/Cloudy01
+ title
+ Sync with iCloud
+ subtitle
+ Data will not be lost
+ illustrationURL
+ https://cdn.oversize.design/assets/illustrations/objects/tools/cloud/large.png
+ backgroundColor
+ AF7AFF
+
+
+ image
+ Solid/Design/Brush
+ title
+ Custumize theme and fonts
+ subtitle
+ Setting app design
+ illustrationURL
+ https://cdn.oversize.design/assets/illustrations/objects/tools/bucket/large.png
+ backgroundColor
+ 5E92FE
+
+
+ image
+ Solid/Design/Palette
+ title
+ Alternate app icon
+ subtitle
+ Choose from a selection app icons for your homescreen
+ illustrationURL
+ https://cdn.oversize.design/assets/illustrations/objects/tools/roller/large.png
+ backgroundColor
+ 5E92FE
+
+
+ image
+ Solid/FoodandDrinks/CupToGo
+ title
+ Help developer :)
+ subtitle
+ Coffe for single worker
+ illustrationURL
+ https://cdn.oversize.design/assets/illustrations/objects/food/coffee-cup/large.png
+ backgroundColor
+ 3FB4FF
+
+
+
+ Onboarding
+
+ Pages
+
+
+ image
+
+ title
+
+ subtitle
+
+
+
+
+ Apps
+
+
+ Id
+ 6443709281
+ Name
+ Scale Down
+ Title
+ Control weight easily, follow the successes of every month
+ Subtitle
+ It is very difficult to reset everything, but if you move in small steps every day, then it is easier to reach the goal. The Scale Down application will help with this, with it the whole process is clearly visible
+ Path
+ scaledown
+
+
+ Id
+ 1477792790
+ Name
+ PIN Wallet
+ Title
+ Allows you to store information about your bank cards
+ Subtitle
+ All data is saved in the phone or is possible synchronization with iCloud
+ Path
+ pinwallet
+
+
+ Id
+ 1552617598
+ Name
+ Dress Weather
+ Title
+ How often are you smell in the rain?
+ Subtitle
+ We will tell you when to take an umbrella
+ Path
+ dressweather
+
+
+
+
diff --git a/Example/Example/Resources/Info.plist b/Example/Example/Resources/Info.plist
new file mode 100644
index 0000000..688fcf7
--- /dev/null
+++ b/Example/Example/Resources/Info.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ ITSAppUsesNonExemptEncryption
+
+ NSFaceIDUsageDescription
+ $(PRODUCT_NAME) Authentication with TouchId or FaceID
+
+
diff --git a/Example/Example/Resources/Products.storekit b/Example/Example/Resources/Products.storekit
new file mode 100644
index 0000000..f9dfc04
--- /dev/null
+++ b/Example/Example/Resources/Products.storekit
@@ -0,0 +1,175 @@
+{
+ "identifier" : "D65ED633",
+ "nonRenewingSubscriptions" : [
+
+ ],
+ "products" : [
+ {
+ "displayPrice" : "19.99",
+ "familyShareable" : false,
+ "internalID" : "A413F754",
+ "localizations" : [
+ {
+ "description" : "",
+ "displayName" : "",
+ "locale" : "en_US"
+ }
+ ],
+ "productID" : "app.oversize.Example.forever",
+ "referenceName" : "Forever",
+ "type" : "NonConsumable"
+ }
+ ],
+ "settings" : {
+ "_failTransactionsEnabled" : false,
+ "_storeKitErrors" : [
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Load Products"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Purchase"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Verification"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "App Store Sync"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Subscription Status"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "App Transaction"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Manage Subscriptions Sheet"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Refund Request Sheet"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Offer Code Redeem Sheet"
+ }
+ ]
+ },
+ "subscriptionGroups" : [
+ {
+ "id" : "A866A70B",
+ "localizations" : [
+
+ ],
+ "name" : "Pro",
+ "subscriptions" : [
+ {
+ "adHocOffers" : [
+
+ ],
+ "codeOffers" : [
+
+ ],
+ "displayPrice" : "9.99",
+ "familyShareable" : false,
+ "groupNumber" : 1,
+ "internalID" : "917AE642",
+ "introductoryOffer" : {
+ "internalID" : "09E486B7",
+ "paymentMode" : "free",
+ "subscriptionPeriod" : "P1W"
+ },
+ "localizations" : [
+ {
+ "description" : "",
+ "displayName" : "",
+ "locale" : "en_US"
+ }
+ ],
+ "productID" : "app.oversize.Example.yearly",
+ "recurringSubscriptionPeriod" : "P1Y",
+ "referenceName" : "Yearly",
+ "subscriptionGroupID" : "A866A70B",
+ "type" : "RecurringSubscription"
+ },
+ {
+ "adHocOffers" : [
+
+ ],
+ "codeOffers" : [
+
+ ],
+ "displayPrice" : "0.99",
+ "familyShareable" : false,
+ "groupNumber" : 1,
+ "internalID" : "DCD986A0",
+ "introductoryOffer" : {
+ "internalID" : "AD2453D4",
+ "paymentMode" : "free",
+ "subscriptionPeriod" : "P3D"
+ },
+ "localizations" : [
+ {
+ "description" : "",
+ "displayName" : "",
+ "locale" : "en_US"
+ }
+ ],
+ "productID" : "app.oversize.Example.monthly",
+ "recurringSubscriptionPeriod" : "P1M",
+ "referenceName" : "Monthly",
+ "subscriptionGroupID" : "A866A70B",
+ "type" : "RecurringSubscription"
+ },
+ {
+ "adHocOffers" : [
+
+ ],
+ "codeOffers" : [
+
+ ],
+ "displayPrice" : "3.99",
+ "familyShareable" : false,
+ "groupNumber" : 1,
+ "internalID" : "DA6156FB",
+ "introductoryOffer" : {
+ "internalID" : "ED52E0DA",
+ "paymentMode" : "free",
+ "subscriptionPeriod" : "P1W"
+ },
+ "localizations" : [
+ {
+ "description" : "",
+ "displayName" : "",
+ "locale" : "en_US"
+ }
+ ],
+ "productID" : "app.oversize.Example.offerYearly",
+ "recurringSubscriptionPeriod" : "P1Y",
+ "referenceName" : "Yearly Offer",
+ "subscriptionGroupID" : "A866A70B",
+ "type" : "RecurringSubscription"
+ }
+ ]
+ }
+ ],
+ "version" : {
+ "major" : 3,
+ "minor" : 0
+ }
+}
diff --git a/Example/Example/Resources/Scripts/swiftgen.yml b/Example/Example/Resources/Scripts/swiftgen.yml
new file mode 100644
index 0000000..1cd638c
--- /dev/null
+++ b/Example/Example/Resources/Scripts/swiftgen.yml
@@ -0,0 +1,14 @@
+#strings:
+# inputs: ${PROJECT_DIR}/${PROJECT_NAME}/Resources/en.lproj/Localizable.strings
+# outputs:
+# - templateName: structured-swift5
+# output: ${PROJECT_DIR}/${PROJECT_NAME}/Resources/SwiftGen/Localizable.swift
+# params:
+# enumName: L
+xcassets:
+ - inputs: ${PROJECT_DIR}/${PROJECT_NAME}/Resources/Assets.xcassets
+ outputs:
+ - templateName: swift5
+ output: ${PROJECT_DIR}/${PROJECT_NAME}/Resources/SwiftGen/Assets.swift
+ params:
+ enumName: Asset
diff --git a/Example/Example/Router/Alerts.swift b/Example/Example/Router/Alerts.swift
new file mode 100644
index 0000000..7d97763
--- /dev/null
+++ b/Example/Example/Router/Alerts.swift
@@ -0,0 +1,48 @@
+//
+// Copyright © 2023 Alexander Romanov
+// Alerts.swift, created on 25.09.2023
+//
+
+import OversizeLocalizable
+import OversizeServices
+import SwiftUI
+
+enum RootAlert: Identifiable {
+ case dismiss(_ action: () -> Void)
+ case delete(_ action: () -> Void)
+ case appError(error: AppError)
+
+ var id: String {
+ switch self {
+ case .dismiss:
+ return "dismiss"
+ case .delete:
+ return "delete"
+ case .appError:
+ return "appError"
+ }
+ }
+
+ var alert: Alert {
+ switch self {
+ case let .dismiss(action):
+ return Alert(
+ title: Text("Are you sure you want to dismiss?"),
+ primaryButton: .destructive(Text("Dismiss"), action: action),
+ secondaryButton: .cancel()
+ )
+ case let .delete(action):
+ return Alert(
+ title: Text("Are you sure you want to delete?"),
+ primaryButton: .destructive(Text("\(L10n.Button.delete)"), action: action),
+ secondaryButton: .cancel()
+ )
+ case let .appError(error: error):
+ return Alert(
+ title: Text(error.title),
+ message: Text(error.subtitle.valueOrEmpty),
+ dismissButton: .cancel()
+ )
+ }
+ }
+}
diff --git a/Example/Example/Router/Router.swift b/Example/Example/Router/Router.swift
new file mode 100644
index 0000000..e359b20
--- /dev/null
+++ b/Example/Example/Router/Router.swift
@@ -0,0 +1,214 @@
+//
+// Copyright © 2023 Alexander Romanov
+// Router.swift, created on 25.09.2023
+//
+
+import SwiftUI
+
+@MainActor
+final class Router: ObservableObject {
+
+ // Route and Tabs
+ @Published var mainPath: [Screen] = []
+ @Published var secondaryPath: [Screen] = []
+ @Published var tertiaryPath: [Screen] = []
+ @Published var quaternaryPath: [Screen] = []
+ @Published var settingsPath: [Screen] = []
+ @Published var tab: RootTab = .main
+
+ // Sheets
+ @Published var sheet: Screen?
+ @Published var fullScreenCover: Screen?
+ @Published private(set) var sheetDetents: Set = []
+ @Published private(set) var dragIndicator: Visibility = .hidden
+ @Published private(set) var dismissDisabled: Bool = false
+
+ // Hud
+ @Published var isShowHud: Bool = false
+ @Published var hudText: String = ""
+
+ // Alert
+ @Published var alert: RootAlert? = nil
+}
+
+// MARK: - Alert
+
+extension Router {
+ func presentAlert(_ alert: RootAlert) {
+ self.alert = alert
+ }
+
+ func dismissAlert() {
+ alert = nil
+ }
+}
+
+// MARK: - HUD
+
+extension Router {
+ func presentHud(_ text: String) {
+ hudText = text
+ isShowHud = true
+ }
+}
+
+// MARK: - Route and Tabs
+
+extension Router {
+ func changeTab(_ tab: RootTab) {
+ self.tab = tab
+ }
+
+ func move(_ screen: Screen) {
+ switch tab {
+ case .main:
+ mainPath.append(screen)
+ case .secondary:
+ secondaryPath.append(screen)
+ case .tertiary:
+ tertiaryPath.append(screen)
+ case .quaternary:
+ quaternaryPath.append(screen)
+ case .settings:
+ settingsPath.append(screen)
+ }
+ }
+
+ func backToRoot() {
+ switch tab {
+ case .main:
+ mainPath.removeAll()
+ case .secondary:
+ secondaryPath.removeAll()
+ case .tertiary:
+ tertiaryPath.removeAll()
+ case .quaternary:
+ quaternaryPath.removeAll()
+ case .settings:
+ settingsPath.removeAll()
+ }
+ }
+
+ func back(_ count: Int = 1) {
+ switch tab {
+ case .main:
+ let pathCount = mainPath.count - count
+ if pathCount > -1 {
+ mainPath.removeLast(count)
+ }
+ case .secondary:
+ let pathCount = secondaryPath.count - count
+ if pathCount > -1 {
+ secondaryPath.removeLast(count)
+ }
+ case .tertiary:
+ let pathCount = tertiaryPath.count - count
+ if pathCount > -1 {
+ tertiaryPath.removeLast(count)
+ }
+ case .quaternary:
+ let pathCount = quaternaryPath.count - count
+ if pathCount > -1 {
+ quaternaryPath.removeLast(count)
+ }
+ case .settings:
+ let pathCount = settingsPath.count - count
+ if pathCount > -1 {
+ settingsPath.removeLast(count)
+ }
+ }
+ }
+}
+
+// MARK: - Sheets
+
+extension Router {
+ func present(_ sheet: Screen, fullScreen: Bool = false) {
+ if fullScreen {
+ if fullScreenCover != nil {
+ fullScreenCover = nil
+ }
+ fullScreenCover = sheet
+ } else {
+ restSheet()
+ self.sheet = sheet
+ }
+ }
+
+ func present(_ sheet: Screen, detents: Set = [.large], indicator: Visibility = .hidden, dismissDisabled: Bool = false) {
+ restSheet()
+ sheetDetents = detents
+ dragIndicator = indicator
+ self.dismissDisabled = dismissDisabled
+ self.sheet = sheet
+ }
+
+ func dismiss() {
+ sheet = nil
+ fullScreenCover = nil
+ }
+
+ func dismissSheet() {
+ sheet = nil
+ }
+
+ func dismissFullScreenCover() {
+ fullScreenCover = nil
+ }
+
+ func dismissDisabled(_ isDismissDisabled: Bool = true) {
+ dismissDisabled = isDismissDisabled
+ }
+
+ private func restSheet() {
+ if sheet != nil {
+ sheet = nil
+ }
+ if fullScreenCover != nil {
+ fullScreenCover = nil
+ }
+ if dragIndicator != .hidden {
+ dragIndicator = .hidden
+ }
+ if dismissDisabled {
+ dismissDisabled = false
+ }
+ if sheetDetents.isEmpty == false {
+ sheetDetents = []
+ }
+ }
+}
+
+extension Router {
+ func handle(_ url: URL) {
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let host = components.host else {
+ return
+ }
+ var pathComponents = components.path.components(separatedBy: "/")
+ pathComponents.removeFirst()
+
+ switch host {
+ case "settings":
+ changeTab(.settings)
+ backToRoot()
+ case "premium":
+ present(.premium)
+ default:
+ break
+ }
+ }
+}
+
+extension Screen: Hashable, Equatable {
+ static func == (lhs: Screen, rhs: Screen) -> Bool {
+ if lhs.id == rhs.id {
+ return true
+ } else {
+ return false
+ }
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(id)
+ }
+}
diff --git a/Example/Example/Router/Screens.swift b/Example/Example/Router/Screens.swift
new file mode 100644
index 0000000..db98182
--- /dev/null
+++ b/Example/Example/Router/Screens.swift
@@ -0,0 +1,50 @@
+//
+// Copyright © 2023 Alexander Romanov
+// Screens.swift, created on 25.09.2023
+//
+
+import OversizeComponents
+import OversizeKit
+import SwiftUI
+
+enum Screen {
+ case settings
+ case premium
+}
+
+extension Screen: Identifiable {
+ var id: String {
+ switch self {
+ case .settings:
+ return "settings"
+ case .premium:
+ return "premium"
+ }
+ }
+}
+
+extension Router {
+ @ViewBuilder
+ func resolve(pathItem: Screen) -> some View {
+ switch pathItem {
+ case .settings:
+ SettingsView {
+ AppSettingsView()
+ }
+ case .premium:
+ StoreView()
+ }
+ }
+
+ func resolveSheet(
+ pathItem: Screen,
+ detents: Set,
+ dragIndicator: Visibility = .automatic,
+ dismissDisabled: Bool
+ ) -> some View {
+ resolve(pathItem: pathItem)
+ .presentationDetents(detents)
+ .presentationDragIndicator(dragIndicator)
+ .interactiveDismissDisabled(dismissDisabled)
+ }
+}
diff --git a/Example/Example/Router/Tabs.swift b/Example/Example/Router/Tabs.swift
new file mode 100644
index 0000000..cadcacf
--- /dev/null
+++ b/Example/Example/Router/Tabs.swift
@@ -0,0 +1,59 @@
+//
+// Copyright © 2023 Alexander Romanov
+// Tabs.swift, created on 25.09.2023
+//
+
+import SwiftUI
+
+public enum RootTab: String {
+ case main
+ case secondary
+ case tertiary
+ case quaternary
+ case settings
+
+ var id: String {
+ switch self {
+ case .main:
+ return "home"
+ case .secondary:
+ return "secondary"
+ case .tertiary:
+ return "tertiary"
+ case .quaternary:
+ return "quaternary"
+ case .settings:
+ return "settings"
+ }
+ }
+
+ var title: String {
+ switch self {
+ case .main:
+ return "Home"
+ case .secondary:
+ return "Secondary"
+ case .tertiary:
+ return "Tertiary"
+ case .quaternary:
+ return "Quaternary"
+ case .settings:
+ return "Settings"
+ }
+ }
+
+ var image: Image {
+ switch self {
+ case .main:
+ return Image(systemName: "")
+ case .secondary:
+ return Image(systemName: "")
+ case .tertiary:
+ return Image(systemName: "")
+ case .quaternary:
+ return Image(systemName: "")
+ case .settings:
+ return Image(systemName: "")
+ }
+ }
+}
diff --git a/Example/Example/Screens/AppSettings/AppSettingsPageView.swift b/Example/Example/Screens/AppSettings/AppSettingsPageView.swift
new file mode 100644
index 0000000..f5d6dfe
--- /dev/null
+++ b/Example/Example/Screens/AppSettings/AppSettingsPageView.swift
@@ -0,0 +1,37 @@
+//
+// Copyright © 2023 Alexander Romanov
+// AppSettingsPageView.swift, created on 25.09.2023
+//
+
+import OversizeUI
+import SwiftUI
+
+struct AppSettingsPageView: View {
+ @StateObject var viewModel: AppSettingsPageViewModel
+
+ init() {
+ _viewModel = StateObject(wrappedValue: AppSettingsPageViewModel())
+ }
+
+ var body: some View {
+ PageView("Option") {
+ SectionView {
+ VStack(spacing: .zero) {
+ Row("Default option") {
+ Image(systemName: "")
+ }
+ }
+ }
+ .sectionContentCompactRowMargins()
+ }
+ .leadingBar {
+ BarButton(.back)
+ }
+ }
+}
+
+struct AppSettingsPageView_ViewPreviews: PreviewProvider {
+ static var previews: some View {
+ AppSettingsPageView()
+ }
+}
diff --git a/Example/Example/Screens/AppSettings/AppSettingsPageViewModel.swift b/Example/Example/Screens/AppSettings/AppSettingsPageViewModel.swift
new file mode 100644
index 0000000..820bc81
--- /dev/null
+++ b/Example/Example/Screens/AppSettings/AppSettingsPageViewModel.swift
@@ -0,0 +1,11 @@
+//
+// Copyright © 2023 Alexander Romanov
+// AppSettingsPageViewModel.swift, created on 25.09.2023
+//
+
+import SwiftUI
+
+@MainActor
+class AppSettingsPageViewModel: ObservableObject {
+ @AppStorage("AppState.Option") var option: String = ""
+}
diff --git a/Example/Example/Screens/AppSettings/AppSettingsView.swift b/Example/Example/Screens/AppSettings/AppSettingsView.swift
new file mode 100644
index 0000000..679db07
--- /dev/null
+++ b/Example/Example/Screens/AppSettings/AppSettingsView.swift
@@ -0,0 +1,31 @@
+//
+// Copyright © 2023 Alexander Romanov
+// AppSettingsView.swift, created on 25.09.2023
+//
+
+import OversizeUI
+import SwiftUI
+
+struct AppSettingsView: View {
+ @EnvironmentObject var viewModel: AppSettingsViewModel
+
+ var body: some View {
+ Group {
+ NavigationLink(destination: AppSettingsPageView()) {
+ Row("Option") {
+ Image(systemName: "")
+ }
+ .rowArrow()
+
+ .multilineTextAlignment(.leading)
+ }
+ .buttonStyle(.row)
+ }
+ }
+}
+
+struct AppSettings_ViewPreviews: PreviewProvider {
+ static var previews: some View {
+ AppSettingsView()
+ }
+}
diff --git a/Example/Example/Screens/AppSettings/AppSettingsViewModel.swift b/Example/Example/Screens/AppSettings/AppSettingsViewModel.swift
new file mode 100644
index 0000000..64065f9
--- /dev/null
+++ b/Example/Example/Screens/AppSettings/AppSettingsViewModel.swift
@@ -0,0 +1,10 @@
+//
+// Copyright © 2023 Alexander Romanov
+// AppSettingsViewModel.swift, created on 25.09.2023
+//
+
+import Foundation
+import SwiftUI
+
+@MainActor
+class AppSettingsViewModel: ObservableObject {}
diff --git a/Example/Example/Screens/Main/MainView.swift b/Example/Example/Screens/Main/MainView.swift
new file mode 100644
index 0000000..835ffad
--- /dev/null
+++ b/Example/Example/Screens/Main/MainView.swift
@@ -0,0 +1,35 @@
+//
+// Copyright © 2023 Alexander Romanov
+// MainView.swift, created on 25.09.2023
+//
+
+import Factory
+import OversizeKit
+import OversizeLocalizable
+import OversizeServices
+import OversizeUI
+import SwiftUI
+
+struct MainView: View {
+
+ @Injected(\.appStateService) var appStateService: AppStateService
+ @Environment(\.screenSize) var screenSize
+ @EnvironmentObject var router: Router
+ @EnvironmentObject var appSettins: AppSettingsViewModel
+ @StateObject var viewModel: MainViewModel
+
+ init() {
+ _viewModel = StateObject(wrappedValue: MainViewModel())
+ }
+
+ var body: some View {
+ Text("Hello, Oversize Kit!")
+ }
+
+}
+
+struct MainView_Previews: PreviewProvider {
+ static var previews: some View {
+ MainView()
+ }
+}
diff --git a/Example/Example/Screens/Main/MainViewModel.swift b/Example/Example/Screens/Main/MainViewModel.swift
new file mode 100644
index 0000000..dee89aa
--- /dev/null
+++ b/Example/Example/Screens/Main/MainViewModel.swift
@@ -0,0 +1,11 @@
+//
+// Copyright © 2023 Alexander Romanov
+// MainViewModel.swift, created on 25.09.2023
+//
+
+import SwiftUI
+
+@MainActor
+class MainViewModel: ObservableObject {
+
+}
diff --git a/Example/Example/Screens/Onboarding/OnboardingView.swift b/Example/Example/Screens/Onboarding/OnboardingView.swift
new file mode 100644
index 0000000..3767f1d
--- /dev/null
+++ b/Example/Example/Screens/Onboarding/OnboardingView.swift
@@ -0,0 +1,172 @@
+//
+// Copyright © 2023 Alexander Romanov
+// OnboardingView.swift, created on 25.09.2023
+//
+
+import OversizeServices
+import OversizeUI
+import SwiftUI
+import Factory
+
+struct OnboardingView: View {
+ @Injected(\.appStateService) var appStateService: AppStateService
+ @Environment(\.screenSize) var screenSize: ScreenSize
+ @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
+ @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
+
+ var body: some View {
+ if horizontalSizeClass == .compact, verticalSizeClass == .regular {
+ phoneRegular
+ } else if horizontalSizeClass == .regular, verticalSizeClass == .compact {
+ phoneCompact
+ } else {
+ ipad
+ }
+ }
+
+ var phoneRegular: some View {
+ ZStack {
+ VStack {
+ Spacer()
+ Image("OnbardingBackground", bundle: .main)
+
+ Spacer()
+
+ VStack(spacing: .medium) {
+ Text("Welcome to\nExample")
+ .largeTitle()
+ .onBackgroundHighEmphasisForegroundColor()
+
+ Text("Welcome text")
+ .title2(.semibold)
+ .onBackgroundMediumEmphasisForegroundColor()
+ }
+ .paddingContent(.horizontal)
+ .multilineTextAlignment(.center)
+
+ Spacer()
+
+ Button("Contine") {
+ appStateService.completedOnbarding()
+ }
+ .buttonStyle(.primary)
+ .accent()
+ .paddingContent(.bottom)
+ .paddingContent(.horizontal)
+ .elevation(.z2)
+ }
+
+ // VStack {
+ //
+ // Spacer()
+ // Image("OnbardingBackground", bundle: .main)
+ // Spacer()
+ // }
+ }
+ .ignoresSafeArea(edges: .top)
+ .background {
+ Color.backgroundSecondary.ignoresSafeArea()
+ }
+ }
+
+ var phoneCompact: some View {
+ HStack(spacing: .zero) {
+ Image("OnbardingBackground", bundle: .main)
+
+ Divider()
+
+ VStack {
+ Spacer()
+
+ VStack(spacing: .medium) {
+ Text("Welcome to\nExample")
+ .largeTitle()
+ .onBackgroundHighEmphasisForegroundColor()
+
+ Text("Welcome text")
+ .title2(.semibold)
+ .onBackgroundMediumEmphasisForegroundColor()
+ }
+ .paddingContent(.horizontal)
+ .multilineTextAlignment(.center)
+
+ Spacer()
+
+ HStack {
+ Spacer()
+ Button("Contine") {
+ appStateService.completedOnbarding()
+ }
+ .buttonStyle(.primary)
+ .accent()
+ .elevation(.z2)
+ .frame(maxWidth: 300)
+ .paddingContent(.horizontal)
+ .paddingContent(.bottom)
+ Spacer()
+ }
+ }
+ }
+ .background {
+ Color.backgroundSecondary.ignoresSafeArea()
+ }
+ }
+
+ var ipad: some View {
+ ZStack {
+ VStack {
+ VStack {}
+ .frame(maxHeight: screenSize.height < 1000 ? 320 : 490)
+
+ Spacer()
+
+ VStack(spacing: .medium) {
+ Text("Example")
+ .largeTitle()
+ .onBackgroundHighEmphasisForegroundColor()
+
+ Text("Welcome text")
+ .title2(.semibold)
+ .onBackgroundMediumEmphasisForegroundColor()
+ }
+ .paddingContent(.horizontal)
+ .multilineTextAlignment(.center)
+
+ Spacer()
+
+ Button("Contine") {
+ appStateService.completedOnbarding()
+ }
+ .buttonStyle(.primary)
+ .accent()
+ .paddingContent(.bottom)
+ .paddingContent(.horizontal)
+ .elevation(.z2)
+ }
+
+ VStack {
+ Surface {
+ Image("OnbardingBackground", bundle: .main)
+ .offset(y: -44)
+ .frame(width: screenSize.height < 1000 ? 320 : 490, height: screenSize.height < 1000 ? 320 : 490)
+ .cornerRadius(.xLarge)
+ .clipped()
+ }
+ .elevation(.z2)
+ .surfaceContentMargins(.zero)
+ .controlRadius(.xLarge)
+ .padding(.top, .large)
+ Spacer()
+ }
+ }
+ .background {
+ Color.backgroundSecondary.ignoresSafeArea()
+ }
+ }
+}
+
+struct OnboardingView_Previews: PreviewProvider {
+ static var previews: some View {
+ OnboardingView()
+ }
+}
diff --git a/Package.swift b/Package.swift
index c71d0ea..3d81c15 100644
--- a/Package.swift
+++ b/Package.swift
@@ -12,12 +12,7 @@ let productionDependencies: [PackageDescription.Package.Dependency] = { [
.package(url: "https://github.com/oversizedev/OversizeResources.git", .upToNextMajor(from: "2.0.0")),
.package(url: "https://github.com/hmlongco/Factory.git", .upToNextMajor(from: "2.1.3")),
.package(url: "https://github.com/lorenzofiamingo/swiftui-cached-async-image.git", .upToNextMajor(from: "2.1.1")),
- /*
- .package(name: "OversizeNetwork", path: "../OversizeNetwork"),
- .package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.0")),
- .package(url: "https://github.com/apple/swift-openapi-urlsession", .upToNextMinor(from: "0.1.0")),
- .package(url: "https://github.com/oversizedev/OversizeNetwork.git", .upToNextMajor(from: "0.1.0"))
- */
+ .package(url: "https://github.com/oversizedev/OversizeNetwork.git", .upToNextMajor(from: "0.6.0"))
] }()
let developmentDependencies: [PackageDescription.Package.Dependency] = { [
@@ -30,10 +25,6 @@ let developmentDependencies: [PackageDescription.Package.Dependency] = { [
.package(name: "OversizeNetwork", path: "../OversizeNetwork"),
.package(url: "https://github.com/lorenzofiamingo/swiftui-cached-async-image.git", .upToNextMajor(from: "2.1.1")),
.package(url: "https://github.com/hmlongco/Factory.git", .upToNextMajor(from: "2.1.3")),
- /*
- .package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.0")),
- .package(url: "https://github.com/apple/swift-openapi-urlsession", .upToNextMinor(from: "0.1.0")),
- */
] }()
let package = Package(
@@ -81,14 +72,7 @@ let package = Package(
.product(name: "OversizeServices", package: "OversizeServices"),
.product(name: "CachedAsyncImage", package: "swiftui-cached-async-image"),
.product(name: "OversizeCore", package: "OversizeCore"),
- /*
- .product(name: "OversizeNetwork", package: "OversizeNetwork"),
- .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
- .product(
- name: "OpenAPIURLSession",
- package: "swift-openapi-urlsession"
- ),
- */
+ .product(name: "OversizeNetwork", package: "OversizeNetwork"),
]
),
.target(
diff --git a/Sources/OversizeAdsKit/AdView.swift b/Sources/OversizeAdsKit/AdView.swift
index 2dbd06c..4a41716 100644
--- a/Sources/OversizeAdsKit/AdView.swift
+++ b/Sources/OversizeAdsKit/AdView.swift
@@ -6,6 +6,7 @@
import CachedAsyncImage
import OversizeCore
import OversizeKit
+import OversizeNetwork
import OversizeServices
import OversizeUI
import SwiftUI
@@ -21,24 +22,36 @@ public struct AdView: View {
}
public var body: some View {
- if isPremium { EmptyView() } else {
+ switch viewModel.state {
+ case .initial:
+ VStack {}
+ .task {
+ if !isPremium {
+ await viewModel.fetchAd()
+ }
+ }
+ case let .result(appAd) :
#if os(iOS)
Surface {
isShowProduct.toggle()
} label: {
- premiumBanner
+ premiumBanner(appAd: appAd)
}
- .surfaceContentInsets(.xSmall)
- .appStoreOverlay(isPresent: $isShowProduct, appId: viewModel.appAd?.id ?? "")
+ .surfaceContentMargins(.xSmall)
+ .appStoreOverlay(isPresent: $isShowProduct, appId: appAd.appStoreId)
+
#else
EmptyView()
#endif
+
+ case .loading, .error:
+ EmptyView()
}
}
- var premiumBanner: some View {
+ func premiumBanner(appAd: Components.Schemas.AppShort) -> some View {
HStack(spacing: .zero) {
- CachedAsyncImage(url: URL(string: "\(Info.links?.company.cdnString ?? "")/assets/apps/\(viewModel.appAd?.path ?? "")/icon.png"), urlCache: .imageCache, content: {
+ CachedAsyncImage(url: URL(string: "\(Info.links?.company.cdnString ?? "")/assets/apps/\(appAd.address)/icon.png"), urlCache: .imageCache, content: {
$0
.resizable()
.frame(width: 64, height: 64)
@@ -62,7 +75,7 @@ public struct AdView: View {
VStack(alignment: .leading, spacing: .xxxSmall) {
HStack {
- Text(viewModel.appAd?.name ?? "")
+ Text(appAd.name)
.subheadline(.bold)
.onSurfaceHighEmphasisForegroundColor()
@@ -72,7 +85,7 @@ public struct AdView: View {
}
}
- Text(viewModel.appAd?.title ?? "")
+ Text(appAd.title)
.subheadline()
.onSurfaceMediumEmphasisForegroundColor()
}
diff --git a/Sources/OversizeAdsKit/AdViewModel.swift b/Sources/OversizeAdsKit/AdViewModel.swift
index ad0ee09..5beacfe 100644
--- a/Sources/OversizeAdsKit/AdViewModel.swift
+++ b/Sources/OversizeAdsKit/AdViewModel.swift
@@ -3,25 +3,39 @@
// AdViewModel.swift, created on 30.06.2023
//
+import Factory
+import OversizeNetwork
import OversizeServices
import SwiftUI
-// import OversizeNetwork
-// import Factory
@MainActor
public class AdViewModel: ObservableObject {
- let appAd = Info.all?.apps.filter { $0.id != Info.app.appStoreID }.randomElement()
- /*
- @Injected(\.networkService) var networkService
+ @Injected(\.networkService) var networkService
- public func fetchAdBanners() async {
- let status = await networkService.fetchAdsBanners()
- switch status {
- case .success(let banners):
- appAd = banners.filter { $0.id != Int(Info.app.appStoreID ?? "") }.randomElement()
- case .failure:
- break
- }
- }
- */
+ @Published var state = State.initial
+
+ public init() {}
+
+ public func fetchAd() async {
+ let result = await networkService.fetchApps()
+ switch result {
+ case let .success(ads):
+ guard let ad = ads.filter({ $0.appStoreId != Info.app.appStoreID }).randomElement() else {
+ state = .error(.custom(title: "Not ad"))
+ return
+ }
+ state = .result(ad)
+ case let .failure(error):
+ state = .error(error)
+ }
+ }
+}
+
+extension AdViewModel {
+ enum State {
+ case initial
+ case loading
+ case result(Components.Schemas.AppShort)
+ case error(AppError)
+ }
}
diff --git a/Sources/OversizeCalendarKit/CreateEventScreen/AttachmentScreen/AttachmentView.swift b/Sources/OversizeCalendarKit/CreateEventScreen/AttachmentScreen/AttachmentView.swift
index 74fa535..e9404bc 100644
--- a/Sources/OversizeCalendarKit/CreateEventScreen/AttachmentScreen/AttachmentView.swift
+++ b/Sources/OversizeCalendarKit/CreateEventScreen/AttachmentScreen/AttachmentView.swift
@@ -25,7 +25,7 @@ public struct AttachmentView: View {
}
}
}
- .surfaceContentRowInsets()
+ .surfaceContentRowMargins()
}
.backgroundSecondary()
.leadingBar {
diff --git a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift
index c6549ab..188b9cf 100644
--- a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift
+++ b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift
@@ -136,7 +136,7 @@ public struct CreateEventView: View {
}
.surfaceBorderColor(Color.surfaceSecondary)
.surfaceBorderWidth(1)
- .surfaceContentInsets(.init(horizontal: .xSmall, vertical: .xSmall))
+ .surfaceContentMargins(.init(horizontal: .xSmall, vertical: .xSmall))
.controlRadius(.large)
}
@@ -191,11 +191,11 @@ public struct CreateEventView: View {
viewModel.repitRule = .never
viewModel.repitEndRule = .never
}
- .surfaceContentInsets(.init(horizontal: .small, vertical: .medium))
+ .surfaceContentMargins(.init(horizontal: .small, vertical: .medium))
}
.surfaceBorderColor(Color.surfaceSecondary)
.surfaceBorderWidth(1)
- .surfaceContentInsets(.zero)
+ .surfaceContentMargins(.zero)
.controlRadius(.large)
}
}
@@ -216,7 +216,7 @@ public struct CreateEventView: View {
.rowClearButton(style: .onSurface) {
viewModel.members.remove(email)
}
- .rowContentInset(.small)
+ .rowContentMargins(.small)
.overlay(alignment: .bottomLeading) {
Rectangle()
.fillSurfaceSecondary()
@@ -228,7 +228,7 @@ public struct CreateEventView: View {
}
.surfaceBorderColor(Color.surfaceSecondary)
.surfaceBorderWidth(1)
- .surfaceContentInsets(.zero)
+ .surfaceContentMargins(.zero)
.controlRadius(.large)
}
}
@@ -250,7 +250,7 @@ public struct CreateEventView: View {
.rowClearButton(style: .onSurface) {
viewModel.alarms.remove(alarm)
}
- .surfaceContentInsets(.init(horizontal: .small, vertical: .medium))
+ .surfaceContentMargins(.init(horizontal: .small, vertical: .medium))
.overlay(alignment: .bottomLeading) {
Rectangle()
.fillSurfaceSecondary()
@@ -262,7 +262,7 @@ public struct CreateEventView: View {
}
.surfaceBorderColor(Color.surfaceSecondary)
.surfaceBorderWidth(1)
- .surfaceContentInsets(.zero)
+ .surfaceContentMargins(.zero)
.controlRadius(.large)
}
}
@@ -285,7 +285,7 @@ public struct CreateEventView: View {
viewModel.locationName = nil
viewModel.location = nil
}
- .rowContentInset(.init(horizontal: .small, vertical: .xSmall))
+ .rowContentMargins(.init(horizontal: .small, vertical: .xSmall))
}
}
@@ -308,7 +308,7 @@ public struct CreateEventView: View {
}
.surfaceBorderColor(Color.surfaceSecondary)
.surfaceBorderWidth(1)
- .surfaceContentInsets(.zero)
+ .surfaceContentMargins(.zero)
.controlRadius(.large)
}
}
diff --git a/Sources/OversizeKit/SettingsKit/Views/About/AboutView.swift b/Sources/OversizeKit/SettingsKit/Views/About/AboutView.swift
index eb367ef..871647e 100644
--- a/Sources/OversizeKit/SettingsKit/Views/About/AboutView.swift
+++ b/Sources/OversizeKit/SettingsKit/Views/About/AboutView.swift
@@ -367,7 +367,7 @@ import SwiftUI
}
.buttonStyle(.scale)
.frame(maxWidth: 300)
- .innerPadding(.xSmall)
+ .controlMargin(.xSmall)
.paddingContent(.horizontal)
}